org.openanzo.jdbc.container.query.AnzoBGPQuery.java Source code

Java tutorial

Introduction

Here is the source code for org.openanzo.jdbc.container.query.AnzoBGPQuery.java

Source

/*******************************************************************************
 * Copyright (c) 2008 Cambridge Semantics Incorporated.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Cambridge Semantics Incorporated - initial API and implementation
 *******************************************************************************/
package org.openanzo.jdbc.container.query;

import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.glitter.query.PatternSolution;
import org.openanzo.glitter.query.Projection;
import org.openanzo.glitter.query.QueryInformation;
import org.openanzo.glitter.query.QueryType;
import org.openanzo.glitter.query.SolutionSet;
import org.openanzo.glitter.syntax.abstrakt.Expression;
import org.openanzo.glitter.syntax.abstrakt.TreeNode;
import org.openanzo.glitter.util.Glitter;
import org.openanzo.jdbc.container.CoreDBConfiguration;
import org.openanzo.jdbc.container.sql.GlitterSQL;
import org.openanzo.jdbc.layout.CompositeNodeLayout;
import org.openanzo.jdbc.layout.NodeLiteralLayout;
import org.openanzo.jdbc.layout.NodeType;
import org.openanzo.jdbc.query.IRdbValue;
import org.openanzo.jdbc.query.NoSolutionsException;
import org.openanzo.jdbc.query.SQLQueryConstants;
import org.openanzo.jdbc.utils.PreparedStatementProvider;
import org.openanzo.rdf.Bindable;
import org.openanzo.rdf.Constants;
import org.openanzo.rdf.MemVariable;
import org.openanzo.rdf.TriplePattern;
import org.openanzo.rdf.TriplePatternComponent;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.rdf.Variable;
import org.openanzo.rdf.Constants.GRAPHS;
import org.openanzo.rdf.utils.HashSetMultiHashMap;
import org.openanzo.rdf.utils.Pair;
import org.openanzo.rdf.utils.Predicate;
import org.slf4j.LoggerFactory;

/**
 * {@link AnzoBGPQuery} is a base class for Anzo Glitter implementations that share similar approaches to solving Glitter queries. (Mainly used for code
 * factoring / reuse.)
 * 
 * @author lee <lee@cambridgesemantics.com>
 * 
 */
public abstract class AnzoBGPQuery {

    static private final String datasetTableAlias = "ds";

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(AnzoBGPQuery.class);

    private final QueryInformation query;

    private TreeNode thisNode = null;

    private final boolean distinctSolutions;

    private long defaultGraphs = -1;

    /**
     * map column aliases (foo in "bar AS foo") to the bindable objects that are being selected for
     */
    private final HashMap<String, Bindable> columnsToBindables;

    /**
     * map bindable objects to the relational columns ("table alias"."column") where it occurs
     */
    private final HashMap<Bindable, List<String>> variableColumns;

    /**
     * map bindable objects to the relational columns ("table alias"."column") where it occurs This map is for variables in @@
     */
    private final HashMap<Bindable, List<String>> variableColumnsExtra;

    /**
     * for each set of triple patterns, map bindable objects to the relational columns ("table alias"."column") where it occurs
     */
    private final List<Map<Bindable, List<String>>> variableColumnsOptional;

    /**
     * keep track of the triples that will make up this query
     */
    private final ArrayList<TripleInstance> triples;

    /**
     * keep track of the extraTriples that will make up this query
     */
    private final ArrayList<TripleInstance> extraTriples;

    /**
     * keep track of the sets of optional triples that will make up this query
     */
    private final ArrayList<List<TripleInstance>> optionalTriples;

    /**
     * keep track of the sets of equals bindables
     */
    private final ArrayList<Pair<Bindable, Bindable>> equalsBindables;

    /**
     * keep track of the sets of equals bindables
     */
    private final ArrayList<Pair<Bindable, Bindable>> notEqualsBindables;

    /**
     * Keep track of all the bindables in equals and notEquals
     */
    private final HashSet<Bindable> equalityBindables = new HashSet<Bindable>();

    /**
     * Keep track of all the isIRI bindables in equals and notEquals
     */
    private final HashSet<Pair<Bindable, Boolean>> isIRIBindables = new HashSet<Pair<Bindable, Boolean>>();

    /**
     * Keep track of all the isLiteral bindables in equals and notEquals
     */
    private final HashSet<Pair<Bindable, Boolean>> isLiteralBindables = new HashSet<Pair<Bindable, Boolean>>();

    /**
     * Keep track of all the isBlank bindables in equals and notEquals
     */
    private final HashSet<Pair<Bindable, Boolean>> isBlankBindables = new HashSet<Pair<Bindable, Boolean>>();

    /**
     * A set of all variables and blanknodes that occur in optional triple patterns in this query. We need this because "extra" triples that also occur in an
     * optional need to be handled as normal constraints rather than as "add-on" triples
     */
    private final HashSet<Bindable> allOptionalBindables;

    /**
     * Map from variable to the string to search for as a LIKE match
     */
    private final HashMap<Bindable, String> likeMatches;

    /**
     * <tt>selectedColumns</tt> builds up a list of the bindables that are being selected "for the outside". <tt>getSQL</tt>, <tt>getExtraSQL</tt>, and
     * <tt>getOuterJoinString</tt> all add to it or modify it.
     */
    private final List<Bindable> selectedBindables;

    /**
     * If non-null, the name of the table that has intermediate results from a preliminary getSQL query
     */
    private String temporaryTable = null;

    /**
     * <tt>namedGraphVariable</tt> and <tt>namedGraphIRI</tt> effectively comprise a C-style union (which can itself be null)
     */
    private Variable namedGraphVariable = null;

    /**
     * <tt>namedGraphVariable</tt> and <tt>namedGraphIRI</tt> effectively comprise a C-style union (which can itself be null)
     */
    private URI namedGraphIRI = null;

    private SolutionSet requiredBindings = null;

    protected AnzoBGPQuery(QueryInformation qi) {
        this.query = qi;

        this.variableColumns = new HashMap<Bindable, List<String>>();
        this.columnsToBindables = new HashMap<String, Bindable>();
        this.triples = new ArrayList<TripleInstance>();
        this.variableColumnsExtra = new HashMap<Bindable, List<String>>();
        this.extraTriples = new ArrayList<TripleInstance>();
        this.variableColumnsOptional = new ArrayList<Map<Bindable, List<String>>>();
        this.optionalTriples = new ArrayList<List<TripleInstance>>();
        this.likeMatches = new HashMap<Bindable, String>();
        this.allOptionalBindables = new HashSet<Bindable>();
        this.selectedBindables = new ArrayList<Bindable>();
        this.equalsBindables = new ArrayList<Pair<Bindable, Bindable>>();
        this.notEqualsBindables = new ArrayList<Pair<Bindable, Bindable>>();

        this.distinctSolutions = this.query.getQueryType() == QueryType.SELECT
                && ((Projection) this.query.getQueryResultForm()).isDistinct();
    }

    // Subclasses must implement specific ways of getting connection and node layout (for ID lookup) objects
    abstract protected Connection getConnection();

    abstract protected CoreDBConfiguration getConfiguration();

    abstract protected PreparedStatementProvider getPreparedStatementProvider();

    abstract protected String getQueryHint(String tableName);

    abstract protected CompositeNodeLayout getNodeLayout();

    abstract protected String getStatementTable();

    abstract protected String getDefaultGraphsTable();

    abstract protected String getNamedGraphsTable();

    abstract protected long getNumberOfValidDefaultGraphs();

    abstract protected Collection<Long> getValidDefaultGraphs() throws AnzoException;

    abstract protected String getLiteralTable();

    abstract protected boolean bypassAcls();

    abstract protected GraphSetType getNamedGraphsType();

    abstract protected GraphSetType getDefaultGraphsType();

    /**
     * Set the TreeNode that this query is processing
     * 
     * @param node
     *            the TreeNode that this query is processing
     */
    public void setThisNode(TreeNode node) {
        this.thisNode = node;
    }

    /**
     * Set the graph variable for the current node
     * 
     * @param variable
     *            the graph variable for the current node
     */
    public void setGraphVariable(Variable variable) {
        this.namedGraphVariable = variable;
    }

    /**
     * Set the requiredBindings
     * 
     * @param requiredBindings
     */
    public void setRequiredBindings(SolutionSet requiredBindings) {
        this.requiredBindings = requiredBindings;
    }

    /**
     * Set the graph URI for the current node
     * 
     * @param uri
     *            the graph URI for the current node
     * @throws NoSolutionsException
     *             if the provided named graph URI does not exist in the system
     */
    public void setNamedGraph(URI uri) throws NoSolutionsException {
        this.namedGraphIRI = uri;
        if (!GRAPHS.ALL_NAMEDGRAPHS.equals(namedGraphIRI) && !GRAPHS.ALL_METADATAGRAPHS.equals(namedGraphIRI)
                && !GRAPHS.ALL_GRAPHS.equals(namedGraphIRI)) {
            long id = 0;
            if (uri instanceof IRdbValue) {
                id = ((IRdbValue) uri).getId();
                if (id != 0) {
                    return;
                } else {
                }
            } else {
                try {
                    Long lid = getNodeLayout().fetchId(Constants.valueFactory.createURI(uri.toString()),
                            getConnection());
                    id = (lid != null) ? lid.longValue() : 0;
                } catch (AnzoException rdbe) {
                    throw new NoSolutionsException(rdbe);
                }
            }
            if (id == 0) {
                throw new NoSolutionsException();
            }
        }
    }

    /**
     * Sets the number of graphs that comprise the default graph. This provides an important optimization in certain cases when unused variables can be ignored
     * in SQL projections.
     * 
     * @param defaultGraphs
     */
    public void setDefaultGraphCount(long defaultGraphs) {
        this.defaultGraphs = defaultGraphs;
    }

    /**
     * 
     * @return how many actual graphs comprise the default graph, or -1 if unknown
     */
    public long getDefaultGraphCount() {
        return this.defaultGraphs;
    }

    /**
     * Returns the bindable object that corresponds with a specific column alias that was selected by this query
     * 
     * @param alias
     * @return
     */
    protected Bindable getBindableForAlias(String alias) {
        return columnsToBindables.get(alias);
    }

    /**
     * 
     * @return The regular triples that this BGP query handles.
     */
    public ArrayList<TripleInstance> getTriples() {
        return this.triples;
    }

    /**
     * 
     * @return The extra (attribute retrieving) triples that this BGP query handles.
     */
    public ArrayList<TripleInstance> getExtraTriples() {
        return this.extraTriples;
    }

    /**
     * 
     * @return The set of optional triples that this BGP query handles.
     */
    public ArrayList<List<TripleInstance>> getOptionalTriples() {
        return this.optionalTriples;
    }

    /**
     * Add a regular triple pattern to this query
     * 
     * @param tp
     *            triple pattern to add
     */
    public void addTriplePattern(TriplePattern tp) {
        TriplePatternComponent s = tp.getSubject(), p = tp.getPredicate(), o = tp.getObject();
        TripleInstance ti = new TripleInstance(s, p, o);
        this.triples.add(ti);
    }

    /**
     * Add an extra triple patter to this query. An extra triple pattern is any triple pattern with two or three variables, such that exactly one of the
     * variables occurs in another triple pattern and the other one or two variables only appear in this triple pattern. Usually, 'extra' triple patterns in a
     * query are used to extract properties of an object located via other parts of the query.
     * 
     * @param s
     *            Subject value or variable
     * @param p
     *            Property value or variable
     * @param o
     *            Object value or variable
     */
    public void addExtraTriplePattern(TriplePatternComponent s, TriplePatternComponent p,
            TriplePatternComponent o) {
        TripleInstance ti = new TripleInstance(s, p, o);
        this.extraTriples.add(ti);
    }

    /**
     * Add an pattern for the special textLike predicate
     * 
     * @param variable
     *            variable for subject of pattern
     * @param matchText
     *            string to match in like query
     */
    public void addLikeMatch(Variable variable, String matchText) {
        likeMatches.put(variable, matchText);
    }

    /**
     * Add an optional triple pattern to this query. These optional patterns are (left) joined to the results of the regular and extra pattern queries.
     * 
     * @param tp
     *            triple pattern to add
     */
    protected void addOptionalPattern(TriplePattern tp) {
        TriplePatternComponent s = tp.getSubject(), p = tp.getPredicate(), o = tp.getObject();
        TripleInstance ti = new TripleInstance(s, p, o);

        List<TripleInstance> triples = new ArrayList<TripleInstance>();
        triples.add(ti);

        // we maintain parallel structure - for each list of optional triple there is a corresponding
        // map from bindables to columns at the same index in variableColumnsOptional
        this.optionalTriples.add(triples);
        this.variableColumnsOptional.add(new HashMap<Bindable, List<String>>());

        // 2: optional triple
        catalog(ti.s, ti.tripleTableAlias + ".SUBJECT", 2);
        catalog(ti.p, ti.tripleTableAlias + ".PREDICATE", 2);
        catalog(ti.o, ti.tripleTableAlias + ".OBJECT", 2);
    }

    /**
     * Add an optional triple patterns to this query. These optional patterns are (left) joined to the results of the regular and extra pattern queries.
     * 
     * @param tps
     *            triple patterns to add
     */
    protected void addOptionalPatterns(List<TriplePattern> tps) {
        List<TripleInstance> triples = new ArrayList<TripleInstance>();
        this.optionalTriples.add(triples);
        this.variableColumnsOptional.add(new HashMap<Bindable, List<String>>());
        for (TriplePattern tp : tps) {
            TriplePatternComponent s = tp.getSubject(), p = tp.getPredicate(), o = tp.getObject();
            TripleInstance ti = new TripleInstance(s, p, o);
            triples.add(ti);
            catalog(ti.s, ti.getTripleTableAlias() + ".SUBJECT", 2);
            catalog(ti.p, ti.getTripleTableAlias() + ".PREDICATE", 2);
            catalog(ti.o, ti.getTripleTableAlias() + ".OBJECT", 2);
        }
    }

    private void populateSelectedColumnsFromMap(List<String> selectedColumns,
            Map<Bindable, List<String>> bindableMap, Predicate<Bindable> selectPredicate) {
        for (Entry<Bindable, List<String>> e : bindableMap.entrySet()) {
            Bindable bindable = e.getKey();
            if (!this.selectedBindables.contains(bindable) && selectPredicate.satisfies(bindable)) { // 2)
                selectedColumns.add(e.getValue().get(0) + " AS \"" + bindable2alias(bindable) + "\""); // 3)
                this.selectedBindables.add(bindable);
            }
        }
    }

    private boolean populateSelectedColumnsWithUnit(List<String> selectedColumns) {
        if (selectedColumns.size() == 0) {
            selectedColumns.add(SQLQueryConstants.glitterUnitTableColumnFull + " AS \""
                    + SQLQueryConstants.glitterIgnoredVariable + "\"");
            return true;
        }
        return false;
    }

    protected List<String> populateConstraintsFromTriples(List<String> constraints,
            Iterable<TripleInstance> triples, String alternativeColumnForNamedGraphs) throws NoSolutionsException {
        ArrayList<String> columns = new ArrayList<String>();
        if (alternativeColumnForNamedGraphs == null) {
            //We don't use a dataset table if acls are disabled, and only going against one of the all constants
            if (!bypassAcls() || (!useDefaultDataset() && getNamedGraphsType() == GraphSetType.LISTED)) {
                alternativeColumnForNamedGraphs = datasetTableAlias + ".ID";
            }
        }
        boolean hasTriples = false;
        boolean thisOne = false;
        for (TripleInstance ti : triples) {
            hasTriples = true;
            constraints.addAll(ti.getConstantRestrictions());

            //This will be true if acls are disabled, and only going against one of the all* constants
            if (alternativeColumnForNamedGraphs == null) {
                alternativeColumnForNamedGraphs = ti.getTripleTableAlias() + ".NAMEDGRAPHID";
                thisOne = true;
            }
            columns.add(ti.getTripleTableAlias() + ".NAMEDGRAPHID");

            if (!useDefaultDataset()) {
                switch (getNamedGraphsType()) {
                case ALL_GRAPHS:
                    break;
                case ALL_METADATA_GRAPHS:
                    constraints.add(ti.getTripleTableAlias() + ".METADATA = 1");
                    break;
                case ALL_NAMED_GRAPHS:
                    constraints.add(ti.getTripleTableAlias() + ".METADATA = 0");
                    break;
                }
                if (!thisOne)
                    constraints
                            .add(ti.getTripleTableAlias() + ".NAMEDGRAPHID = " + alternativeColumnForNamedGraphs);
            }

        }
        if (hasTriples && namedGraphIRI != null)
            constraints.add(alternativeColumnForNamedGraphs + " = " + getId(namedGraphIRI));
        //If not using a dataset table because acls are disabled, and only going against one of the all* constants, then use metadata column within statements table
        if (useDefaultDataset()) {
            if (bypassAcls()) {
                switch (getDefaultGraphsType()) {
                case ALL_GRAPHS:
                    return columns;
                case ALL_METADATA_GRAPHS:
                    for (TripleInstance ti : triples) {
                        constraints.add(ti.getTripleTableAlias() + ".METADATA = 1");
                    }
                    return columns;
                case ALL_NAMED_GRAPHS:
                    for (TripleInstance ti : triples) {
                        constraints.add(ti.getTripleTableAlias() + ".METADATA = 0");
                    }
                    return columns;
                case LISTED:
                    break;
                }
            }
            if (getNumberOfValidDefaultGraphs() > 100) {
                for (TripleInstance ti : triples) {
                    constraints.add(getNamedGraphInQuery(ti.getTripleTableAlias() + ".NAMEDGRAPHID"));
                }
            } else {
                try {
                    Collection<Long> graphs = getValidDefaultGraphs();
                    StringBuilder sb = new StringBuilder();
                    for (Iterator<Long> graphsIter = graphs.iterator(); graphsIter.hasNext();) {
                        sb.append(graphsIter.next().toString());
                        if (graphsIter.hasNext()) {
                            sb.append(',');
                        }
                    }
                    String defaultGraphsSQL = "IN (" + sb.toString() + ")";
                    for (TripleInstance ti : triples) {
                        constraints.add(ti.getTripleTableAlias() + ".NAMEDGRAPHID " + defaultGraphsSQL);
                    }
                } catch (AnzoException ae) {
                    throw new NoSolutionsException(ae);
                }
            }
        }
        return columns;

    }

    protected String getNamedGraphInQuery(String columnName) {
        return columnName + " IN(SELECT ID FROM " + getDefaultGraphsTable() + ")";
    }

    protected void populateConstraintsFromMap(List<String> constraints, Map<Bindable, List<String>> bindableMap) {
        for (Entry<Bindable, List<String>> e : bindableMap.entrySet()) {
            String canonicalColumn = e.getValue().get(0);
            for (int i = 1; i < e.getValue().size(); i++)
                constraints.add(canonicalColumn + " = " + e.getValue().get(i)); // 5)
        }
    }

    static final String[] constraintName = { "TEMP_CONSTRAINT0", "TEMP_CONSTRAINT1", "TEMP_CONSTRAINT2",
            "TEMP_CONSTRAINT3" };

    int c = 0;

    protected boolean populateRequiredBindings(List<String> constraints, Map<Bindable, List<String>> bindableMap)
            throws AnzoException {
        if (requiredBindings != null && requiredBindings.size() > 0) {
            HashSetMultiHashMap<Bindable, Value> values = new HashSetMultiHashMap<Bindable, Value>();
            HashMap<Bindable, Integer> valueCount = new HashMap<Bindable, Integer>();
            int requiredBindingCount = 0;

            for (PatternSolution element : requiredBindings) {
                Bindable[] bees = element.getBoundDomain(true);
                if (bees.length > 0) {
                    requiredBindingCount++;
                }
                for (Bindable bindable : bees) {
                    values.put(bindable, element.getBinding(bindable));
                    Integer i = valueCount.get(bindable);
                    valueCount.put(bindable, i != null ? i + 1 : 1);
                }
            }
            // the constraint on a particular variable/bindable is only applicable if the bindable has a value
            // in every required binding solution.
            for (Entry<Bindable, List<String>> e : bindableMap.entrySet()) {
                if (values.containsKey(e.getKey()) && valueCount.get(e.getKey()) == requiredBindingCount) {
                    StringBuilder options = new StringBuilder();
                    Collection<Value> collection = values.get(e.getKey());
                    if (collection.size() < 125) {
                        if (collection.size() > 1) {
                            for (Iterator<Value> vals = collection.iterator(); vals.hasNext();) {
                                Value val = vals.next();
                                if (val instanceof IRdbValue) {
                                    options.append(((IRdbValue) val).getId());
                                    if (vals.hasNext()) {
                                        options.append(",");
                                    }
                                } else {
                                    Long id = getNodeLayout().fetchId(val, getConnection());
                                    if (id != null) {
                                        options.append(id);
                                        if (vals.hasNext()) {
                                            options.append(",");
                                        }
                                    } else {
                                        return false;
                                    }
                                }
                            }
                            for (String columnName : e.getValue()) {
                                constraints.add(columnName + " IN (" + options.toString() + ")"); // 5)
                            }
                        } else {
                            Value val = collection.iterator().next();
                            if (val instanceof IRdbValue) {
                                for (String columnName : e.getValue()) {
                                    constraints.add(columnName + " = " + ((IRdbValue) val).getId() + ""); // 5)
                                }
                            } else {
                                Long id = getNodeLayout().fetchId(val, getConnection());
                                if (id != null) {
                                    for (String columnName : e.getValue()) {
                                        constraints.add(columnName + " = " + id + ""); // 5)
                                    }
                                } else {
                                    return false;
                                }
                            }
                        }
                    } else if (c < 4) {
                        GlitterSQL.BatchInsertIdToTempTable statement = new GlitterSQL.BatchInsertIdToTempTable(
                                getConnection(), getPreparedStatementProvider(),
                                getConfiguration().getSessionPrefix(), constraintName[c]);
                        for (Iterator<Value> vals = collection.iterator(); vals.hasNext();) {
                            Value val = vals.next();
                            if (val instanceof IRdbValue) {
                                statement.addEntry(((IRdbValue) val).getId());
                            } else {
                                Long id = getNodeLayout().fetchId(val, getConnection());
                                if (id != null) {
                                    statement.addEntry(id);
                                } else {
                                    return false;
                                }
                            }
                        }
                        statement.executeStatement();
                        for (String columnName : e.getValue()) {
                            constraints
                                    .add(columnName + " IN (SELECT " + getQueryHint(constraintName[c]) + " ID FROM "
                                            + getConfiguration().getSessionPrefix() + constraintName[c] + ")");
                        }
                        c++;
                        statement.close();
                    }
                }
            }
        }
        return true;
    }

    protected void populateFilterConstraintsFromMap(List<String> constraints,
            Map<Bindable, List<String>> bindableMap) {
        for (Pair<Bindable, Bindable> pair : equalsBindables) {
            if (bindableMap.containsKey(pair.first) && bindableMap.containsKey(pair.second)) {
                List<String> e1 = bindableMap.get(pair.first);
                List<String> e2 = bindableMap.get(pair.second);
                for (String column1 : e1) {
                    for (String column2 : e2) {
                        constraints.add(column1 + " = " + column2); // 5)
                    }
                }
            }
        }
        for (Pair<Bindable, Bindable> pair : notEqualsBindables) {
            if (bindableMap.containsKey(pair.first) && bindableMap.containsKey(pair.second)) {
                List<String> e1 = bindableMap.get(pair.first);
                List<String> e2 = bindableMap.get(pair.second);
                for (String column1 : e1) {
                    for (String column2 : e2) {
                        constraints.add(column1 + " != " + column2); // 5)
                    }
                }
            }
        }

        for (Pair<Bindable, Boolean> bindable : isIRIBindables) {
            if (bindableMap.containsKey(bindable.first)) {
                List<String> e1 = bindableMap.get(bindable.first);
                for (String column1 : e1) {
                    if (bindable.second) {
                        constraints.add("((" + column1 + " < " + NodeType.URI.getTypeMask() + " OR " + column1
                                + " > " + NodeType.URI.getMaxValue() + ") AND (" + column1 + " < "
                                + NodeType.LONG_URI.getTypeMask() + " OR " + column1 + " > "
                                + NodeType.LONG_URI.getMaxValue() + "))"); // 5)
                    } else {
                        constraints.add("((" + column1 + " > " + NodeType.URI.getTypeMask() + " AND " + column1
                                + " < " + NodeType.URI.getMaxValue() + ") OR (" + column1 + " > "
                                + NodeType.LONG_URI.getTypeMask() + " AND " + column1 + " < "
                                + NodeType.LONG_URI.getMaxValue() + "))"); // 5)
                    }
                }
            }
        }
        for (Pair<Bindable, Boolean> bindable : isLiteralBindables) {
            if (bindableMap.containsKey(bindable.first)) {
                List<String> e1 = bindableMap.get(bindable.first);
                for (String column1 : e1) {
                    if (bindable.second) {
                        constraints.add("(" + column1 + " < " + NodeType.LITERAL.getTypeMask() + " OR " + column1
                                + " > " + NodeType.TYPED_LONG_LITERAL.getMaxValue() + ")"); // 5)
                    } else {
                        constraints.add("(" + column1 + " > " + NodeType.LITERAL.getTypeMask() + " AND " + column1
                                + " < " + NodeType.TYPED_LONG_LITERAL.getMaxValue() + ")"); // 5)
                    }
                }
            }
        }
        for (Pair<Bindable, Boolean> bindable : isBlankBindables) {
            if (bindableMap.containsKey(bindable.first)) {
                List<String> e1 = bindableMap.get(bindable.first);
                for (String column1 : e1) {
                    if (bindable.second) {
                        constraints.add("(" + column1 + " < " + NodeType.BLANK.getTypeMask() + " OR " + column1
                                + " > " + NodeType.BLANK.getMaxValue() + ")"); // 5)
                    } else {
                        constraints.add("(" + column1 + " > " + NodeType.BLANK.getTypeMask() + " AND " + column1
                                + " < " + NodeType.BLANK.getMaxValue() + ")"); // 5)
                    }
                }
            }
        }
    }

    /**
     * Get the SQL string for this BGP. This can take the form of selecting variables (and blank nodes) relevant to the rest of the SPARQL query, or of
     * inserting the result set into a temporary table.
     * 
     * @param tempTable
     *            If not null, results are inserted into this temporary table
     * @return the SQL string for this query
     * @throws NoSolutionsException
     */
    protected String getSQL(String tempTable) throws NoSolutionsException, AnzoException {
        boolean insert = tempTable != null;

        if ((insert && triples.size() == 0)) {
            return null; // no rows to populate in this case
        }

        // only assign the temporary table if we're going forward to insert rows into it
        this.temporaryTable = tempTable;

        //////////////////////////////////////////////////////////////
        // SELECT clause
        //
        // 1) take all of the variables and bnodes that we've matched with
        // columns
        // 2) Omit any which do not occur outside of this BGP
        //    (where "outside of this BGP" includes the projected variables, constructed
        //     variables, described variables, etc.)
        // 3) for each, alias the first column as appropriate for the select
        // list
        // 4) If using a graph variable, include that in the list of selected
        // variables
        // 5) We always select for SQLQueryConstants.glitterUnitTableColumnFull in order that
        // we never have an empty SELECT list (unless we're inserting for later)
        // 6) If we're not already selecting it, select for any LIKE variables
        ArrayList<String> selectedColumns = new ArrayList<String>();
        populateSelectedColumnsFromMap(selectedColumns, variableColumns, new Predicate<Bindable>() {
            public boolean satisfies(Bindable b) {
                return isNeededOutsideOfSQL(b) || needsUnusedVariables();
            }
        });

        if (namedGraphVariable != null && !this.selectedBindables.contains(namedGraphVariable)
                && (isNeededOutsideOfSQL(namedGraphVariable) || needsUnusedVariables())) {
            //Check to see the dataset table is in use, if not, use the first triple pattern's namedgraphID as the source of the namedgraphID
            if (!bypassAcls() || (!useDefaultDataset() && getNamedGraphsType() == GraphSetType.LISTED)) {
                selectedColumns.add(datasetTableAlias + ".ID AS \"" + bindable2alias(namedGraphVariable) + "\""); // 4)
            } else {
                TripleInstance ti = triples.get(0);
                selectedColumns.add(
                        ti.tripleTableAlias + ".NAMEDGRAPHID AS \"" + bindable2alias(namedGraphVariable) + "\""); // 4)
            }
            this.selectedBindables.add(namedGraphVariable);
        }
        ArrayList<String> tables = new ArrayList<String>();

        if (!insert) {
            if (populateSelectedColumnsWithUnit(selectedColumns)) {
                tables.add(SQLQueryConstants.glitterUnitTable); // 3)
            }
        }

        HashMap<Bindable, Integer> likeVariables = new HashMap<Bindable, Integer>(likeMatches.size());
        int likeMatchCount = 0;
        for (Bindable likeVar : likeMatches.keySet()) {
            if (!variableColumns.containsKey(likeVar) && isNeededOutsideOfSQL(likeVar)) {
                selectedColumns.add("LITERALS" + likeMatchCount + ".ID AS \"" + bindable2alias(likeVar) + "\"");
                likeVariables.put(likeVar, Integer.valueOf(likeMatchCount));
                likeMatchCount++;
            }
        }
        //////////////////////////////////////////////////////////////
        // FROM clause
        //
        // 1) if a named graph dataset, contains one instance of the named graph
        //    temp dataset table (if the default part of the dataset, the WHERE
        //    constraints will match each triple instance to an ID from the default graphs)
        // 2) contains one instance of the StatementHistory table for each
        //    triple pattern
        // 3) We always include SQLQueryConstants.glitterUnitTable which has one row and
        //    and one column and cross-joins with the rest of the result set
        //    such that it doesn't add any rows, but in the absence of any other
        //    tables we'll still get one solution. (Don't include it if we're inserting for
        //    a future query.)
        // 4) If there is a textlikematch predicate, add the anzo_l table once for each
        //Don't add the dataset table if it isn't going to be used
        if (!useDefaultDataset() && (!bypassAcls() || getNamedGraphsType() == GraphSetType.LISTED)) {
            tables.add(getNamedGraphsTable() + " " + datasetTableAlias); // 1)
        }
        for (TripleInstance ti : triples)
            tables.add(ti.getFrom()); // 2)
        //////////////////////////////////////////////////////////////
        // WHERE clause
        //
        // 1) Add all constant restrictions (IRIs or literals)
        // 2) If using a named graph, restrict each instance of the
        // triple table to the same instance of the NG table (datasetTableAlias)
        // 3) Restrict HEND as appropriate for temporal queries (or specify that it must be current)
        // 4) If using a fixed named graph, add that restriction
        // 5) Add bindable coreference restrictions
        // 6) If querying the default graph, constrain each triple instance against any one of the graphs
        // that (logically) make up the default graph
        // 7) Constrain LIKE matches appropriately (i.e. using the SQL LIKE predicate, and, if necessary,
        // matching with the canonical column for the bindable)
        ArrayList<String> clauses = new ArrayList<String>();
        List<String> columnNames = populateConstraintsFromTriples(clauses, triples, null);
        if (namedGraphVariable != null && variableColumns.containsKey(namedGraphVariable)) {
            if (!useDefaultDataset() && (!bypassAcls() || getNamedGraphsType() == GraphSetType.LISTED)) {
                variableColumns.get(namedGraphVariable).add(datasetTableAlias + ".ID");
            } else if (!useDefaultDataset() && (bypassAcls() || getNamedGraphsType() != GraphSetType.LISTED)
                    && (!columnNames.isEmpty())) {
                variableColumns.put(namedGraphVariable, columnNames);
            }
        } else if (!columnNames.isEmpty() && !useDefaultDataset()) {
            variableColumns.put(namedGraphVariable, columnNames);
        }
        populateConstraintsFromMap(clauses, variableColumns);
        populateFilterConstraintsFromMap(clauses, variableColumns);
        if (!populateRequiredBindings(clauses, variableColumns)) {
            //A required value is not in the db, so can never be true
            return null;
        }
        if (likeMatches.size() > 0) { // 7)
            for (Map.Entry<Bindable, String> match : likeMatches.entrySet()) {
                List<String> vals = variableColumns.get(match.getKey());
                String value = match.getValue();
                if (!value.endsWith("%")) {
                    value += NodeLiteralLayout.PAD;
                }
                value = value.replace("'", "''");
                if (vals != null) {
                    String canonicalColumn = vals.get(0);
                    String literalTable = "LITERALS" + likeMatchCount;
                    clauses.add(literalTable + ".VALUE LIKE '" + value + "' AND " + literalTable + ".ID="
                            + canonicalColumn);
                    likeMatchCount++;
                } else if (likeVariables.containsKey(match.getKey())) {
                    Integer likeId = likeVariables.get(match.getKey());
                    clauses.add("LITERALS" + likeId + ".VALUE LIKE '" + value + "'");
                }
            }
        }
        for (int i = 0; i < likeMatchCount; i++) {
            tables.add(getLiteralTable() + " LITERALS" + i); // 4)
        }
        // put it all together
        String prefix = "";
        if (insert) {
            StringBuilder insertSQL = new StringBuilder("INSERT INTO " + tempTable + "(");
            for (int i = 0; i < selectedColumns.size(); i++) {
                insertSQL.append("C" + i);
                if (i < selectedColumns.size() - 1) {
                    insertSQL.append(",");
                }
            }
            insertSQL.append(") ");
            prefix = insertSQL.toString();
        }
        return prefix + getSQLString(selectDistinct(), selectedColumns, tables, clauses, "");
    }

    /**
     * Get the SQL string for this query for the extra triple patterns. This joins the results from the getSQL() statement, if any, and the results from the
     * extra triple patterns. If there are any optional query patterns, outer joins are added to merge those results in as well.
     * 
     * @return the SQL string for this query
     * @throws NoSolutionsException
     * @throws AnzoException
     */
    public String getExtraSQL() throws NoSolutionsException, AnzoException {
        int namedGraphVariableTemporaryColumn = this.selectedBindables.indexOf(namedGraphVariable);

        // what concrete columns (w/ aliases) will go in our SELECT clause?
        ArrayList<String> selectedColumns = new ArrayList<String>();
        // what bindables did we already select for into our temp table?
        ArrayList<Bindable> tempTableBindables = new ArrayList<Bindable>(this.selectedBindables);

        // Copy columns from the temp table to our result columns list, and stick the proper
        // column name for them in selectedColumns - take any columns that we don't need after
        // this query and skip them
        int c = 0;
        for (Bindable b : tempTableBindables) {
            if (isUsedOutsideOfQuery(b) || isOptionalBindable(b) || needsUnusedVariables()) {
                selectedColumns.add(this.temporaryTable + ".C" + c + " AS \"" + bindable2alias(b) + "\"");
            } else {
                this.selectedBindables.remove(b);
            }
            c++;
        }
        populateSelectedColumnsFromMap(selectedColumns, variableColumnsExtra, new Predicate<Bindable>() {
            public boolean satisfies(Bindable b) {
                return isUsedOutsideOfQuery(b) || needsUnusedVariables();
            }
        });
        ArrayList<String> tables = new ArrayList<String>();

        if (populateSelectedColumnsWithUnit(selectedColumns)) {
            tables.add(SQLQueryConstants.glitterUnitTable);
        }
        String graphsTableName = null;
        if (extraTriples.size() > 0 && namedGraphVariable != null
                && !this.selectedBindables.contains(namedGraphVariable) && (isUsedOutsideOfQuery(namedGraphVariable)
                        || isOptionalBindable(namedGraphVariable) || needsUnusedVariables())) {
            //Again do the check to determine if a dataset graph table is being used, or to use first triple patterns namedgraphid
            if (!useDefaultDataset() && (!bypassAcls() && getNamedGraphsType() == GraphSetType.LISTED)) {
                graphsTableName = datasetTableAlias + ".ID";

            } else {
                TripleInstance ti = extraTriples.get(0);
                graphsTableName = ti.tripleTableAlias + ".NAMEDGRAPHID";
            }
            selectedColumns.add(graphsTableName + " AS \"" + bindable2alias(namedGraphVariable) + "\""); // 4)
        }
        //////////////////////////////////////////////////////////////
        // FROM clause
        //
        if (this.temporaryTable != null)
            tables.add(this.temporaryTable);
        // we need the dataset table if we didn't already select for the named graph variable in our original
        // query
        if (extraTriples.size() > 0 && !useDefaultDataset() && namedGraphVariableTemporaryColumn == -1) {
            //Don't add the dataset table to tables if it isn't beind used
            if (!bypassAcls() || getNamedGraphsType() == GraphSetType.LISTED) {
                tables.add(getNamedGraphsTable() + " " + datasetTableAlias);
            }
        }
        for (TripleInstance ti : extraTriples)
            tables.add(ti.getFrom());

        //////////////////////////////////////////////////////////////
        // WHERE clause
        //
        ArrayList<String> clauses = new ArrayList<String>();
        // We need to constrain triples either by the namedgraph table or by the already bound
        // named graphs coming from the temporary table
        populateConstraintsFromTriples(clauses, extraTriples,
                namedGraphVariableTemporaryColumn > -1
                        ? this.temporaryTable + ".C" + namedGraphVariableTemporaryColumn
                        : null);
        if (extraTriples.size() > 0 && namedGraphVariableTemporaryColumn == -1
                && (!useDefaultDataset() && (!bypassAcls() && getNamedGraphsType() == GraphSetType.LISTED))
                && namedGraphVariable != null && variableColumnsExtra.containsKey(namedGraphVariable))
            variableColumnsExtra.get(namedGraphVariable).add(datasetTableAlias + ".ID");
        populateConstraintsFromMap(clauses, variableColumnsExtra);
        populateFilterConstraintsFromMap(clauses, variableColumnsExtra);
        if (!populateRequiredBindings(clauses, variableColumnsExtra)) {
            //A required value is not in the db, so can never be true
            return null;
        }
        // make sure that any variabels in both the extra triples and regular triples are constrained to match
        for (Entry<Bindable, List<String>> e : variableColumnsExtra.entrySet()) {
            int col = tempTableBindables.indexOf(e.getKey());
            if (col > -1) {
                clauses.add(this.temporaryTable + ".C" + col + "=" + e.getValue().get(0));
            }
        }
        // put it all together

        String sql = getSQLString(selectDistinct(), selectedColumns, tables, clauses,
                getQueryHint(this.temporaryTable));
        if (variableColumnsOptional.size() > 0) {

            for (int depth = 0; depth < this.optionalTriples.size(); depth++) {
                try {
                    String oldSql = sql;
                    sql = getOuterJoinSQL(sql, depth);
                    if (sql == null) {
                        sql = oldSql;
                    }
                } catch (NoSolutionsException nse) {
                    log.trace(LogUtils.DATASOURCE_MARKER,
                            "No solutions found for right-hand side of OPTIONAL. Nothing to see here, move along.");
                }
            }
            return sql;
        } else {
            return sql;
        }
    }

    /**
     * This returns a new sql string, which does an outer join between a provided sql select statement, and a set of optional triple patterns
     * 
     * @param leftSideSql
     *            sql select statement text which makes up left side of join,
     * @param depth
     *            how many nested outerJoins are we currently at
     * @return new SQL string containing the join
     * @throws NoSolutionsException
     */
    private String getOuterJoinSQL(String leftSideSql, int depth) throws NoSolutionsException, AnzoException {
        // what bindables are we now selecting out to the top level (that will need to be added
        // to this.selectedBindables)?
        ArrayList<Bindable> newResultBindables = new ArrayList<Bindable>();
        // what bindables are we selecting on the RHS of out LEFT JOIN?
        ArrayList<Bindable> rightHandBindables = new ArrayList<Bindable>();
        // what concrete columns (w/ aliases) are we selecting in the RHS of the left join?
        ArrayList<String> columnsOptional = new ArrayList<String>();
        // what concrete columns (w/ aliases) are we selecting from the left joined result set?
        ArrayList<String> outerColumnsOptional = new ArrayList<String>();

        Map<Bindable, List<String>> map = variableColumnsOptional.get(depth);
        List<TripleInstance> rightSideTriples = this.optionalTriples.get(depth);
        /////////////////////////////////////
        // SELECT
        //
        // first, build the right-hand side SQL (the SQL for the optional triples)
        if (map != null) {

            for (Entry<Bindable, List<String>> e : map.entrySet()) { // 1)
                Bindable bindable = e.getKey();
                // (N.B. we don't need to check variableExtraColumns here since earlier we made sure that
                // extra triples don't include variables mentioned in OPTIONALs)
                if (variableColumns.containsKey(bindable) || isUsedOutsideOfQuery(bindable)
                        || needsUnusedVariables()) {
                    rightHandBindables.add(bindable);
                    columnsOptional.add(e.getValue().get(0) + " AS \"" + bindable2alias(bindable) + "\""); // 2)
                    // if this isn't a variable we're already selecting from the LHS and we need it beyond this
                    // BGP+ query, then record the R## column alias to be selected out at top level
                    if (!this.selectedBindables.contains(bindable)
                            && (isUsedOutsideOfQuery(bindable) || needsUnusedVariables())) {
                        outerColumnsOptional.add("R" + depth + ".\"" + bindable2alias(bindable) + "\""); // + "\""
                        newResultBindables.add(bindable);
                    }
                }
            }
        }
        if (namedGraphVariable != null && !rightHandBindables.contains(namedGraphVariable)) {
            rightHandBindables.add(namedGraphVariable);
            if (!useDefaultDataset() && (!bypassAcls() || getNamedGraphsType() == GraphSetType.LISTED)) {
                columnsOptional.add(datasetTableAlias + ".ID AS \"" + bindable2alias(namedGraphVariable) + "\""); // 3)
            } else if (rightSideTriples.size() > 0) {
                columnsOptional.add(rightSideTriples.get(0).tripleTableAlias + ".NAMEDGRAPHID AS \""
                        + bindable2alias(namedGraphVariable) + "\"");
            }
            if (!this.selectedBindables.contains(namedGraphVariable)
                    && (isUsedOutsideOfQuery(namedGraphVariable) || needsUnusedVariables())) {
                outerColumnsOptional.add("R" + depth + ".\"" + bindable2alias(namedGraphVariable) + "\""); // + "\""
                newResultBindables.add(namedGraphVariable);
            }
        }
        /////////////////////////////////////
        // FROM
        //
        ArrayList<String> tablesOptional = new ArrayList<String>();
        if (!useDefaultDataset() && (!bypassAcls() || getNamedGraphsType() == GraphSetType.LISTED)) {
            tablesOptional.add(getNamedGraphsTable() + " " + datasetTableAlias); // 2a)
        }
        for (TripleInstance ti : rightSideTriples) {
            tablesOptional.add(ti.getFrom()); // 1)
        }
        // ////////////////////////////////////////////////////////////
        // WHERE clause
        //
        ArrayList<String> clausesOptional = new ArrayList<String>();
        ArrayList<String> clausesOptionalOuter = new ArrayList<String>();
        populateConstraintsFromTriples(clausesOptional, rightSideTriples, null);
        if (map != null) {
            if (namedGraphVariable != null && map.containsKey(namedGraphVariable)
                    && (!bypassAcls() || getNamedGraphsType() == GraphSetType.LISTED)) {
                map.get(namedGraphVariable).add(datasetTableAlias + ".ID");
            }
            populateConstraintsFromMap(clausesOptional, map);
            if (!populateRequiredBindings(clausesOptional, map)) {
                //A required value is not in the db, so can never be true
                return null;
            }
            for (Entry<Bindable, List<String>> e : map.entrySet()) {
                // if something occurs on both the LHS and the RHS, add a top-level constraint that
                // their values be equal
                if (this.selectedBindables.contains(e.getKey())) {
                    clausesOptionalOuter.add("L" + depth + ".\"" + bindable2alias(e.getKey()) + "\"=" + "R" + depth
                            + ".\"" + bindable2alias(e.getKey()) + "\"");
                }
            }
        }
        if (namedGraphVariable != null) {
            String condition = "L" + depth + ".\"" + bindable2alias(namedGraphVariable) + "\"=" + "R" + depth
                    + ".\"" + bindable2alias(namedGraphVariable) + "\"";
            if (!clausesOptionalOuter.contains(condition)) {
                clausesOptionalOuter.add(condition);
            }
        }
        String sqlOptional = getSQLString(selectDistinct(), columnsOptional, tablesOptional, clausesOptional, "");

        ArrayList<String> optionalSelectedColumns = new ArrayList<String>();
        Bindable fake = MemVariable.createVariable(SQLQueryConstants.glitterIgnoredVariable);
        for (Bindable b : this.selectedBindables) {
            boolean usedInUnprocessedOptional = false;
            for (int i = depth + 1; i < variableColumnsOptional.size(); i++) {
                if (variableColumnsOptional.get(i).containsKey(b)) {
                    usedInUnprocessedOptional = true;
                    break;
                }
            }
            if (fake.equals(b) || usedInUnprocessedOptional || isUsedOutsideOfQuery(b) || needsUnusedVariables())
                optionalSelectedColumns.add("L" + depth + ".\"" + bindable2alias(b) + "\"");//
        }
        String selectOptional = "SELECT " + StringUtils.join(optionalSelectedColumns.iterator(), ", ");
        if (outerColumnsOptional.size() > 0) {
            if (optionalSelectedColumns.size() > 0)
                selectOptional += ", ";
            selectOptional += StringUtils.join(outerColumnsOptional.iterator(), ", ");
        }
        String joinCondition = clausesOptionalOuter.size() > 0
                ? StringUtils.join(clausesOptionalOuter.iterator(), " AND ")
                : "1=1";
        selectOptional += " FROM (" + leftSideSql + ") L" + depth + " LEFT OUTER JOIN (" + sqlOptional + ") R"
                + depth + " ON " + joinCondition;

        this.selectedBindables.addAll(newResultBindables);
        return selectOptional;
    }

    /**
     * Records a mapping from a variable to a particular concrete column ("alias.col") for a SQL query. There is one list for each variable for regular and
     * extra triple patterns. Every set of optional patterns, however, has its own such list (as each set of optional patterns can fail on its own)
     * 
     * @param x
     *            A piece of the triple pattern. <tt>catalog</tt> only acts on it if it's a {@link Bindable} (Variable or BlankNode)
     * @param column
     *            The identifier for the concrete column that is being bound to this bindable or must match this term
     * @param type
     *            if 0, this is from a normal triple pattern; if 1, this is from an "extra" triple pattern (see addExtraTriplePattern); if 2, this is from an
     *            optional triple pattern
     */
    private void catalog(TriplePatternComponent x, String column, int type) {
        if (x instanceof Bindable) {
            List<String> columns = null;
            switch (type) {
            case 0:
                columns = variableColumns.get(x);
                break;
            case 1:
                columns = variableColumnsExtra.get(x);
                break;
            case 2:
                // catalog for optional triples is called immediately after a map was created for
                // the current set of optional triples, so we retrieve that at index .size() - 1
                allOptionalBindables.add((Bindable) x);
                Map<Bindable, List<String>> map = variableColumnsOptional.get(variableColumnsOptional.size() - 1);
                if (map == null) {
                    map = new HashMap<Bindable, List<String>>();
                    variableColumnsOptional.add(map);
                }
                columns = map.get(x);
                break;
            }
            // create a list of columns aliased to this binding if we didn't already have one
            if (columns == null) {
                columns = new ArrayList<String>();
                switch (type) {
                case 0:
                    variableColumns.put((Bindable) x, columns);
                    break;
                case 1:
                    variableColumnsExtra.put((Bindable) x, columns);
                    break;
                case 2:
                    variableColumnsOptional.get(variableColumnsOptional.size() - 1).put((Bindable) x, columns);
                    break;
                }
            }
            columns.add(column);
        }
    }

    protected void catalogTriples() {
        // catalog all extra (property-accessing) triples, but note that an extra triple
        // that is used in a LIKE or also appears in an optional triple pattern should be
        // reclassified as a normal triple
        for (Iterator<TripleInstance> tis = this.extraTriples.iterator(); tis.hasNext();) {
            TripleInstance ti = tis.next();
            if ((ti.s instanceof Bindable && (likeMatches.containsKey(ti.s) || allOptionalBindables.contains(ti.s)
                    || equalityBindables.contains(ti.s)))
                    || (ti.p instanceof Bindable && (likeMatches.containsKey(ti.p)
                            || allOptionalBindables.contains(ti.p) || equalityBindables.contains(ti.p)))
                    || (ti.o instanceof Bindable && (likeMatches.containsKey(ti.o)
                            || allOptionalBindables.contains(ti.o) || equalityBindables.contains(ti.o)))) {
                tis.remove();
                this.triples.add(ti); // reclassify
            } else {
                // 1: extra triple
                catalog(ti.s, ti.tripleTableAlias + ".SUBJECT", 1);
                catalog(ti.p, ti.tripleTableAlias + ".PREDICATE", 1);
                catalog(ti.o, ti.tripleTableAlias + ".OBJECT", 1);
            }
        }
        for (TripleInstance ti : this.triples) {
            // 0: normal triple
            catalog(ti.s, ti.tripleTableAlias + ".SUBJECT", 0);
            catalog(ti.p, ti.tripleTableAlias + ".PREDICATE", 0);
            catalog(ti.o, ti.tripleTableAlias + ".OBJECT", 0);
        }
    }

    protected boolean useDefaultDataset() {
        return namedGraphVariable == null && namedGraphIRI == null;
    }

    protected Long getId(TriplePatternComponent tpc) throws NoSolutionsException {
        try {
            Long l = getNodeLayout().fetchId((Value) tpc, getConnection());
            if (l == null)
                throw new NoSolutionsException();
            return l;
        } catch (AnzoException rdbe) {
            throw new NoSolutionsException(rdbe);
        }
    }

    // Right now, this alias system is only used for table aliases, so we
    // don't need to maintain any state
    private int nextAlias = 1;

    private final HashMap<Object, String> aliases = new HashMap<Object, String>();

    protected String getAliasFor(Object o, String prefix) {
        String alias = aliases.get(o);
        if (alias == null) {
            alias = prefix + nextAlias++;
            aliases.put(o, alias);
        }
        return alias;
    }

    private String bindable2alias(Bindable x) {
        String alias;
        if (x instanceof Variable)
            alias = ((Variable) x).getName();
        else
            alias = x.toString();
        try {
            if (alias != null && alias.getBytes(Constants.byteEncoding).length >= 30) {
                alias = getAliasFor(alias, "bind");
            }
        } catch (UnsupportedEncodingException uee) {
            log.error(LogUtils.RDB_MARKER, "Byte encoding error", uee);
            throw new RuntimeException(uee);
        }
        columnsToBindables.put(alias, x);
        return alias;
    }

    private boolean isOptionalBindable(Bindable b) {
        // check optional list
        for (Map<Bindable, List<String>> m : variableColumnsOptional) {
            if (m.containsKey(b))
                return true;
        }
        return false;
    }

    /**
     * If we're querying for distinct solutions only because the default graph (may contain/contains multiple actual graphs, then we need to select variables
     * that are otherwise unused in order to get the proper cardinalities.
     * 
     * This could be replaced with SELECT DISTINCT x, y, z, count(*) ... GROUP BY x, y, z but that requires further infrastructure updates.
     * 
     * @return
     */
    protected boolean needsUnusedVariables() {
        return selectDistinct() && !this.distinctSolutions;
    }

    private boolean selectDistinct() {
        // we select distinct if
        //  (1) the SPARQL query is distinct
        //  (2) the query is against the default dataset which has (or might have) more than 1 actual graph
        //  (eliminate false duplicates arising from querying multiple actual graphs that comprise a single logical graph)
        return this.distinctSolutions || (useDefaultDataset() && this.defaultGraphs != 1);
    }

    // @@ check for counting and/or counting distinct - need enough information preserved to get the proper
    // counts (consider GROUP BY as well)

    /**
     * Returns whether or not the given bindable is needed outside the query. We need to select this variable if it appears as an extra or optional triple
     * pattern or if it is used in a filter or anywhere outside of the node we're dealing with now (including in the query's projection)
     * 
     * @param b
     * @return
     */
    protected boolean isNeededOutsideOfSQL(Bindable b) {
        return isOptionalBindable(b) || variableColumnsExtra.containsKey(b) || isUsedOutsideOfQuery(b);
    }

    protected boolean isUsedOutsideOfQuery(Bindable b) {
        return filterReferencesBindable(b) || Glitter.isNeededOutsideOfNode(b, this.thisNode, this.query, true);
    }

    protected boolean filterReferencesBindable(Bindable b) {
        Set<Expression> exps = this.thisNode.getFilters();
        if (exps != null) {
            for (Expression e : exps) {
                if (e.getReferencedVariables().contains(b))
                    return true;
            }
        }
        return false;
    }

    private String getSQLString(boolean distinct, List<String> columns, List<String> tables,
            List<String> constraints, String queryHints) {
        return (distinct ? "SELECT " + queryHints + " DISTINCT " : "SELECT ")
                + StringUtils.join(columns.iterator(), ", ") + " FROM " + StringUtils.join(tables.iterator(), ", ")
                + (constraints.size() > 0 ? " WHERE " + StringUtils.join(constraints.iterator(), " AND ") : "");
    }

    /**
     * Add equals bindable
     * 
     * @param b1
     *            bindable 1
     * @param b2
     *            bindable 2
     */
    public void addEqualVariables(Bindable b1, Bindable b2) {
        equalsBindables.add(new Pair<Bindable, Bindable>(b1, b2));
        equalityBindables.add(b1);
        equalityBindables.add(b2);
    }

    /**
     * Add equals bindable
     * 
     * @param b1
     *            bindable 1
     * @param b2
     *            bindable 2
     */
    public void addNotEqualVariables(Bindable b1, Bindable b2) {
        notEqualsBindables.add(new Pair<Bindable, Bindable>(b1, b2));
        equalityBindables.add(b1);
        equalityBindables.add(b2);
    }

    /**
     * Add equals bindable
     * 
     * @param b1
     *            bindable 1
     * @param not
     */
    public void addIsIRI(Bindable b1, boolean not) {
        isIRIBindables.add(new Pair<Bindable, Boolean>(b1, not));
    }

    /**
     * Add equals bindable
     * 
     * @param b1
     *            bindable 1
     * @param not
     */
    public void addIsLiteral(Bindable b1, boolean not) {
        isLiteralBindables.add(new Pair<Bindable, Boolean>(b1, not));
    }

    /**
     * Add equals bindable
     * 
     * @param b1
     *            bindable 1
     * @param not
     */
    public void addIsBlank(Bindable b1, boolean not) {
        isBlankBindables.add(new Pair<Bindable, Boolean>(b1, not));
    }

    /**
     * A TripleInstance represents one triple pattern that is going into an SQL query. It receives its own alias of the STATEMENTS table and can produce WHERE
     * clause restrictions for non-bindable parts of the triple pattern.
     * 
     * @author lee <lee@cambridgesemantics.com>
     * 
     */
    protected class TripleInstance {
        protected final String tripleTableAlias;

        protected final TriplePatternComponent s, p, o;

        protected TripleInstance(TriplePatternComponent s, TriplePatternComponent p, TriplePatternComponent o) {
            this.s = s;
            this.p = p;
            this.o = o;
            this.tripleTableAlias = getAliasFor(this, "tp");
        }

        public String getFrom() {
            return getStatementTable() + " " + this.tripleTableAlias;
        }

        private void addRestrictionIfConstant(TriplePatternComponent tpc, String column, ArrayList<String> clauses)
                throws NoSolutionsException {
            if (!(tpc instanceof Bindable))
                clauses.add(column + "=" + getId(tpc));
        }

        public ArrayList<String> getConstantRestrictions() throws NoSolutionsException {
            ArrayList<String> clauses = new ArrayList<String>();
            addRestrictionIfConstant(this.s, this.tripleTableAlias + ".SUBJECT", clauses);
            addRestrictionIfConstant(this.p, this.tripleTableAlias + ".PREDICATE", clauses);
            addRestrictionIfConstant(this.o, this.tripleTableAlias + ".OBJECT", clauses);
            return clauses;
        }

        public String getTripleTableAlias() {
            return this.tripleTableAlias;
        }
    }
}

/**
 * if (!bypassAcls() || ) { !bypassAcls() = (!useDefaultDataset() && getNamedGraphsType() == GraphSetType.LISTED) = (Is in graph clause) AND (Going against a
 * list of named graphs)
 */