/******************************************************************************* * 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) */