| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.jena.query; |
| |
| import java.io.OutputStream ; |
| import java.util.* ; |
| |
| import org.apache.jena.atlas.io.IndentedLineBuffer ; |
| import org.apache.jena.atlas.io.IndentedWriter ; |
| import org.apache.jena.atlas.io.Printable ; |
| import org.apache.jena.atlas.logging.Log ; |
| import org.apache.jena.graph.Node ; |
| import org.apache.jena.sparql.ARQConstants ; |
| import org.apache.jena.sparql.algebra.table.TableData ; |
| import org.apache.jena.sparql.core.* ; |
| import org.apache.jena.sparql.engine.binding.Binding ; |
| import org.apache.jena.sparql.expr.Expr ; |
| import org.apache.jena.sparql.expr.ExprAggregator ; |
| import org.apache.jena.sparql.expr.ExprTransform ; |
| import org.apache.jena.sparql.expr.ExprVar ; |
| import org.apache.jena.sparql.expr.aggregate.Aggregator ; |
| import org.apache.jena.sparql.serializer.QuerySerializerFactory ; |
| import org.apache.jena.sparql.serializer.SerializerRegistry ; |
| import org.apache.jena.sparql.syntax.Element ; |
| import org.apache.jena.sparql.syntax.PatternVars ; |
| import org.apache.jena.sparql.syntax.Template ; |
| import org.apache.jena.sparql.syntax.syntaxtransform.* ; |
| import org.apache.jena.sparql.util.FmtUtils ; |
| import org.apache.jena.sys.JenaSystem ; |
| |
| /** The data structure for a query as presented externally. |
| * There are two ways of creating a query - use the parser to turn |
| * a string description of the query into the executable form, and |
| * the programmatic way (the parser is calling the programmatic |
| * operations driven by the query string). The declarative approach |
| * of passing in a string is preferred. |
| * |
| * Once a query is built, it can be passed to the QueryFactory to produce a query execution engine. |
| * @see QueryExecutionFactory |
| * @see ResultSet |
| */ |
| |
| public class Query extends Prologue implements Cloneable, Printable |
| { |
| static { JenaSystem.init() ; /* Ensure everything has started properly */ } |
| |
| // Old constants. Retain for compatibility. |
| public static final int QueryTypeUnknown = -123 ; |
| public static final int QueryTypeSelect = 111 ; |
| public static final int QueryTypeConstruct = 222 ; |
| public static final int QueryTypeDescribe = 333 ; |
| public static final int QueryTypeAsk = 444 ; |
| public static final int QueryTypeJson = 555 ; |
| |
| private QueryType queryType = QueryType.UNKNOWN; |
| |
| // If no model is provided explicitly, the query engine will load |
| // a model from the URL. Never a list of zero items. |
| |
| private List<String> graphURIs = new ArrayList<>() ; |
| private List<String> namedGraphURIs = new ArrayList<>() ; |
| |
| // The WHERE clause |
| private Element queryPattern = null ; |
| |
| // Query syntax |
| private Syntax syntax = Syntax.syntaxSPARQL ; // Default |
| |
| // LIMIT/OFFSET |
| public static final long NOLIMIT = Long.MIN_VALUE ; |
| private long resultLimit = NOLIMIT ; |
| private long resultOffset = NOLIMIT ; |
| |
| // ORDER BY |
| private List<SortCondition> orderBy = null ; |
| public static final int ORDER_ASCENDING = 1 ; |
| public static final int ORDER_DESCENDING = -1 ; |
| public static final int ORDER_DEFAULT = -2 ; // Not explicitly given. |
| public static final int ORDER_UNKNOW = -3 ; |
| |
| // VALUES trailing clause |
| protected TableData valuesDataBlock = null ; |
| |
| protected boolean strictQuery = true ; |
| |
| // SELECT * seen |
| protected boolean queryResultStar = false ; |
| |
| protected boolean distinct = false ; |
| protected boolean reduced = false ; |
| |
| // CONSTRUCT |
| protected Template constructTemplate = null ; |
| |
| // DESCRIBE |
| // Any URIs/QNames in the DESCRIBE clause |
| // Also uses resultVars |
| protected List<Node> resultNodes = new ArrayList<>() ; // Type in list: Node |
| |
| /** |
| * Creates a new empty query |
| */ |
| public Query() |
| { |
| syntax = Syntax.syntaxSPARQL ; |
| } |
| |
| /** |
| * Creates a new empty query with the given prologue |
| */ |
| public Query(Prologue prologue) |
| { |
| this() ; |
| usePrologueFrom(prologue) ; |
| } |
| |
| // Allocate variables that are unique to this query. |
| private VarAlloc varAlloc = new VarAlloc(ARQConstants.allocQueryVariables) ; |
| private Var allocInternVar() { return varAlloc.allocVar() ; } |
| |
| public void setQuerySelectType() { queryType = QueryType.SELECT ; } |
| public void setQueryConstructType() { queryType = QueryType.CONSTRUCT ; queryResultStar = true ; } |
| public void setQueryDescribeType() { queryType = QueryType.DESCRIBE; } |
| public void setQueryAskType() { queryType = QueryType.ASK; } |
| public void setQueryJsonType() { queryType = QueryType.CONSTRUCT_JSON; } |
| |
| /** Return the {@link QueryType} */ |
| public QueryType queryType() { return queryType ; } |
| |
| /** @deprecated Use {@link #queryType()} which returns an {@link QueryType} */ |
| @Deprecated |
| public int getQueryType() { |
| // Old constants. |
| switch(queryType) { |
| case SELECT : return QueryTypeSelect; |
| case ASK : return QueryTypeAsk; |
| case CONSTRUCT : return QueryTypeConstruct; |
| case CONSTRUCT_JSON : return QueryTypeJson; |
| case CONSTRUCT_QUADS : return QueryTypeConstruct; |
| case DESCRIBE : return QueryTypeDescribe; |
| default : return QueryTypeUnknown; |
| } |
| } |
| |
| public boolean isSelectType() { return queryType == QueryType.SELECT; } |
| |
| public boolean isConstructType() { return queryType == QueryType.CONSTRUCT ; } |
| |
| public boolean isDescribeType() { return queryType == QueryType.DESCRIBE ; } |
| |
| public boolean isAskType() { return queryType == QueryType.ASK ; } |
| |
| public boolean isJsonType() { return queryType == QueryType.CONSTRUCT_JSON ; } |
| |
| public boolean isUnknownType() { return queryType == QueryType.UNKNOWN ; } |
| |
| public boolean isConstructQuad() { |
| return (isConstructType() && constructTemplate.containsRealQuad()) |
| || queryType == QueryType.CONSTRUCT_QUADS; |
| } |
| |
| // It was a mistake to extend Prologue ... but what is done is done. |
| public Prologue getPrologue() { return this ; } |
| |
| public void setStrict(boolean isStrict) |
| { |
| strictQuery = isStrict ; |
| |
| if ( strictQuery ) |
| initStrict() ; |
| else |
| initLax() ; |
| } |
| |
| public boolean isStrict() { return strictQuery ; } |
| |
| private void initStrict() |
| { |
| // if ( prefixMap.getGlobalPrefixMapping() == globalPrefixMap ) |
| // prefixMap.setGlobalPrefixMapping(null) ; |
| } |
| |
| private void initLax() |
| { |
| // if ( prefixMap.getGlobalPrefixMapping() == null ) |
| // prefixMap.setGlobalPrefixMapping(globalPrefixMap) ; |
| } |
| |
| public void setDistinct(boolean b) { distinct = b ; } |
| public boolean isDistinct() { return distinct ; } |
| |
| public void setReduced(boolean b) { reduced = b ; } |
| public boolean isReduced() { return reduced ; } |
| |
| /** @return Returns the syntax. */ |
| public Syntax getSyntax() { return syntax ; } |
| |
| /** @param syntax The syntax to set. */ |
| public void setSyntax(Syntax syntax) |
| { |
| this.syntax = syntax ; |
| if ( syntax != Syntax.syntaxSPARQL ) |
| strictQuery = false ; |
| } |
| |
| // ---- Limit/offset |
| |
| public long getLimit() { return resultLimit ; } |
| public void setLimit(long limit) { resultLimit = limit ; } |
| public boolean hasLimit() { return resultLimit != NOLIMIT ; } |
| |
| public long getOffset() { return resultOffset ; } |
| public void setOffset(long offset) { resultOffset = offset ; } |
| public boolean hasOffset() { return resultOffset != NOLIMIT ; } |
| |
| // ---- Order By |
| |
| public boolean hasOrderBy() { return orderBy != null && orderBy.size() > 0 ; } |
| |
| public boolean isOrdered() { return hasOrderBy() ; } |
| |
| public void addOrderBy(SortCondition condition) |
| { |
| if ( orderBy == null ) |
| orderBy = new ArrayList<>() ; |
| |
| orderBy.add(condition) ; |
| } |
| public void addOrderBy(Expr expr, int direction) |
| { |
| SortCondition sc = new SortCondition(expr, direction) ; |
| addOrderBy(sc) ; |
| } |
| |
| public void addOrderBy(Node var, int direction) |
| { |
| if ( ! var.isVariable() ) |
| throw new QueryException("Not a variable: "+var) ; |
| SortCondition sc = new SortCondition(var, direction) ; |
| addOrderBy(sc) ; |
| } |
| |
| public void addOrderBy(String varName, int direction) |
| { |
| varName = Var.canonical(varName) ; |
| SortCondition sc = new SortCondition(new ExprVar(varName), direction) ; |
| addOrderBy(sc) ; |
| } |
| |
| public List<SortCondition> getOrderBy() { return orderBy ; } |
| |
| // ---- |
| |
| /** Answer whether the query had SELECT/DESCRIBE/CONSTRUCT * |
| * @return boolean as to whether a * result form was seen |
| */ |
| public boolean isQueryResultStar() { return queryResultStar ; } |
| |
| /** Set whether the query had SELECT/DESCRIBE * |
| * |
| * @param isQueryStar |
| */ |
| public void setQueryResultStar(boolean isQueryStar) |
| { |
| queryResultStar = isQueryStar ; |
| if ( isQueryStar ) |
| resultVarsSet = false ; |
| } |
| |
| public void setQueryPattern(Element elt) |
| { |
| queryPattern = elt ; |
| } |
| |
| public Element getQueryPattern() { return queryPattern ; } |
| |
| /** Location of the source for the data. If the model is not set, |
| * then the QueryEngine will attempt to load the data from these URIs |
| * into the default (unamed) graph. |
| */ |
| public void addGraphURI(String s) |
| { |
| if ( graphURIs == null ) |
| graphURIs = new ArrayList<>() ; |
| graphURIs.add(s) ; |
| } |
| |
| /** Location of the source for the data. If the model is not set, |
| * then the QueryEngine will attempt to load the data from these URIs |
| * as named graphs in the dataset. |
| */ |
| public void addNamedGraphURI(String uri) |
| { |
| if ( namedGraphURIs == null ) |
| namedGraphURIs = new ArrayList<>() ; |
| if ( namedGraphURIs.contains(uri) ) |
| throw new QueryException("URI already in named graph set: "+uri) ; |
| else |
| namedGraphURIs.add(uri) ; |
| } |
| |
| /** Return the list of URIs (strings) for the unnamed graph |
| * |
| * @return List of strings |
| */ |
| |
| public List<String> getGraphURIs() { return graphURIs ; } |
| |
| /** Test whether the query mentions a URI in forming the default graph (FROM clause) |
| * |
| * @param uri |
| * @return boolean True if the URI used in a FROM clause |
| */ |
| public boolean usesGraphURI(String uri) { return graphURIs.contains(uri) ; } |
| |
| /** Return the list of URIs (strings) for the named graphs (FROM NAMED clause) |
| * |
| * @return List of strings |
| */ |
| |
| public List<String> getNamedGraphURIs() { return namedGraphURIs ; } |
| |
| /** Test whether the query mentions a URI for a named graph. |
| * |
| * @param uri |
| * @return True if the URI used in a FROM NAMED clause |
| */ |
| public boolean usesNamedGraphURI(String uri) { return namedGraphURIs.contains(uri) ; } |
| |
| /** Return true if the query has either some graph |
| * URIs or some named graph URIs in its description. |
| * This does not mean these URIs will be used - just that |
| * they are noted as part of the query. |
| */ |
| |
| public boolean hasDatasetDescription() |
| { |
| if ( getGraphURIs() != null && getGraphURIs().size() > 0 ) |
| return true ; |
| if ( getNamedGraphURIs() != null && getNamedGraphURIs().size() > 0 ) |
| return true ; |
| return false ; |
| } |
| |
| /** Return a dataset description (FROM/FROM NAMED clauses) for the query. */ |
| public DatasetDescription getDatasetDescription() |
| { |
| if ( ! hasDatasetDescription() ) |
| return null; |
| |
| DatasetDescription description = new DatasetDescription() ; |
| |
| description.addAllDefaultGraphURIs(getGraphURIs()) ; |
| description.addAllNamedGraphURIs(getNamedGraphURIs()) ; |
| return description ; |
| } |
| |
| // ---- SELECT |
| |
| protected VarExprList projectVars = new VarExprList() ; |
| |
| /** Return a list of the variables requested (SELECT) */ |
| public List<String> getResultVars() |
| { |
| // Ensure "SELECT *" processed |
| setResultVars() ; |
| return Var.varNames(projectVars.getVars()) ; |
| } |
| |
| /** Return a list of the variables requested (SELECT) */ |
| public List<Var> getProjectVars() |
| { |
| // Ensure "SELECT *" processed |
| setResultVars() ; |
| return projectVars.getVars() ; |
| } |
| |
| public VarExprList getProject() |
| { |
| return projectVars ; |
| } |
| |
| /** Add a collection of projection variables to a SELECT query */ |
| public void addProjectVars(Collection<?> vars) |
| { |
| for ( Object obj : vars ) |
| { |
| if ( obj instanceof String ) |
| { |
| this.addResultVar( (String) obj ); |
| continue; |
| } |
| if ( obj instanceof Var ) |
| { |
| this.addResultVar( (Var) obj ); |
| continue; |
| } |
| throw new QueryException( "Not a variable or variable name: " + obj ); |
| } |
| resultVarsSet = true ; |
| } |
| |
| |
| /** Add a projection variable to a SELECT query */ |
| public void addResultVar(String varName) |
| { |
| varName = Var.canonical(varName) ; |
| _addResultVar(varName) ; |
| } |
| |
| public void addResultVar(Node v) |
| { |
| if ( !v.isVariable() ) |
| throw new QueryException("Not a variable: "+v) ; |
| _addResultVar(v.getName()) ; |
| } |
| |
| public void addResultVar(Node v, Expr expr) |
| { |
| Var var = null ; |
| if ( v == null ) |
| var = allocInternVar() ; |
| else |
| { |
| if ( !v.isVariable() ) |
| throw new QueryException("Not a variable: "+v) ; |
| var = Var.alloc(v) ; |
| } |
| _addVarExpr(projectVars, var, expr) ; |
| } |
| |
| /** Add an to a SELECT query (a name will be created for it) */ |
| public void addResultVar(Expr expr) |
| { |
| _addVarExpr(projectVars, allocInternVar(), expr) ; |
| } |
| |
| /** Add a named expression to a SELECT query */ |
| public void addResultVar(String varName, Expr expr) |
| { |
| Var var = null ; |
| if ( varName == null ) |
| var = allocInternVar() ; |
| else |
| { |
| varName = Var.canonical(varName) ; |
| var = Var.alloc(varName) ; |
| } |
| _addVarExpr(projectVars, var, expr) ; |
| } |
| |
| // Add raw name. |
| private void _addResultVar(String varName) |
| { |
| Var v = Var.alloc(varName) ; |
| _addVar(projectVars, v) ; |
| resultVarsSet = true ; |
| } |
| |
| private static void _addVar(VarExprList varExprList, Var v) |
| { |
| if ( varExprList.contains(v) ) |
| { |
| Expr expr = varExprList.getExpr(v) ; |
| if ( expr != null ) |
| // SELECT (?a+?b AS ?x) ?x |
| throw new QueryBuildException("Duplicate variable (had an expression) in result projection '"+v+"'") ; |
| // SELECT ?x ?x |
| if ( ! ARQ.allowDuplicateSelectColumns ) |
| return ; |
| // else drop through and have two variables of the same name. |
| } |
| varExprList.add(v) ; |
| } |
| |
| private static void _addVarExpr(VarExprList varExprList, Var v, Expr expr) |
| { |
| if ( varExprList.contains(v) ) |
| // SELECT ?x (?a+?b AS ?x) |
| // SELECT (2*?a AS ?x) (?a+?b AS ?x) |
| throw new QueryBuildException("Duplicate variable in result projection '"+v+"'") ; |
| varExprList.add(v, expr) ; |
| } |
| |
| protected VarExprList groupVars = new VarExprList() ; |
| protected List<Expr> havingExprs = new ArrayList<>() ; // Expressions : Make an ExprList? |
| |
| public boolean hasGroupBy() { return ! groupVars.isEmpty() || getAggregators().size() > 0 ; } |
| public boolean hasHaving() { return havingExprs != null && havingExprs.size() > 0 ; } |
| |
| public VarExprList getGroupBy() { return groupVars ; } |
| |
| public List<Expr> getHavingExprs() { return havingExprs ; } |
| |
| public void addGroupBy(String varName) |
| { |
| varName = Var.canonical(varName) ; |
| addGroupBy(Var.alloc(varName)) ; |
| } |
| |
| public void addGroupBy(Node v) |
| { |
| _addVar(groupVars, Var.alloc(v)) ; |
| } |
| |
| public void addGroupBy(Expr expr) { addGroupBy(null, expr) ; } |
| |
| public void addGroupBy(Var v, Expr expr) |
| { |
| if ( v == null ) |
| v = allocInternVar() ; |
| |
| if ( expr.isVariable() && v.isAllocVar() ) |
| { |
| // It was (?x) with no AS - keep the name by adding by variable. |
| addGroupBy(expr.asVar()) ; |
| return ; |
| } |
| |
| groupVars.add(v, expr) ; |
| } |
| |
| public void addHavingCondition(Expr expr) |
| { |
| havingExprs.add(expr) ; |
| } |
| |
| // SELECT JSON |
| |
| private Map<String, Node> jsonMapping = new LinkedHashMap<>(); |
| |
| public void addJsonMapping(String key, Node value) { |
| jsonMapping.put(key, value); |
| } |
| |
| public Map<String, Node> getJsonMapping() { |
| return Collections.unmodifiableMap(jsonMapping); |
| } |
| |
| // ---- Aggregates |
| |
| // Record allocated aggregations. |
| // Later: The same aggregation expression used in a query |
| // will always lead to the same aggregator. |
| // For now, allocate a fresh one each time (cause the calculation |
| // to be done multiple times but (1) it's unusual to have repeated |
| // aggregators normally and (2) the actual calculation is cheap. |
| |
| // Unlike SELECT expressions, here the expression itself (E_Aggregator) knows its variable |
| // Commonality? |
| |
| private List<ExprAggregator> aggregators = new ArrayList<>() ; |
| private Map<Var, ExprAggregator> aggregatorsMap = new HashMap<>() ; |
| |
| // Note any E_Aggregator created for reuse. |
| private Map<String, Var> aggregatorsAllocated = new HashMap<>() ; |
| |
| public boolean hasAggregators() { return aggregators.size() != 0 ; } |
| public List<ExprAggregator> getAggregators() { return aggregators ; } |
| |
| public Expr allocAggregate(Aggregator agg) |
| { |
| // We need to track the aggregators in case one aggregator is used twice, e.g. in HAVING and in SELECT expression |
| // (is that much harm to do twice? Yes, if distinct.) |
| String key = agg.key() ; |
| |
| Var v = aggregatorsAllocated.get(key); |
| if ( v != null ) |
| { |
| ExprAggregator eAgg = aggregatorsMap.get(v) ; |
| if ( ! agg.equals(eAgg.getAggregator()) ) |
| Log.warn(Query.class, "Internal inconsistency: Aggregator: "+agg) ; |
| return eAgg ; |
| } |
| // Allocate. |
| v = allocInternVar() ; |
| ExprAggregator aggExpr = new ExprAggregator(v, agg) ; |
| aggregatorsAllocated.put(key, v) ; |
| aggregatorsMap.put(v, aggExpr) ; |
| aggregators.add(aggExpr) ; |
| return aggExpr ; |
| } |
| |
| // ---- VALUES |
| |
| /** Does the query have a VALUES trailing block? */ |
| public boolean hasValues() { return valuesDataBlock != null ; } |
| |
| /** Variables from a VALUES trailing block */ |
| public List<Var> getValuesVariables() { return valuesDataBlock==null ? null : valuesDataBlock.getVars() ; } |
| |
| /** Data from a VALUES trailing block. null for a Node means undef */ |
| public List<Binding> getValuesData() { return valuesDataBlock==null ? null : valuesDataBlock.getRows() ; } |
| |
| public void setValuesDataBlock(List<Var> variables, List<Binding> values) |
| { |
| checkDataBlock(variables, values) ; |
| valuesDataBlock = new TableData(variables, values) ; |
| } |
| |
| private static void checkDataBlock(List<Var> variables, List<Binding> values) |
| { |
| // Check. |
| int N = variables.size() ; |
| for ( Binding valueRow : values ) |
| { |
| Iterator<Var> iter= valueRow.vars() ; |
| for ( ; iter.hasNext() ; ) |
| { |
| Var v = iter.next() ; |
| if ( ! variables.contains(v) ) |
| throw new QueryBuildException("Variable "+v+" not found in "+variables) ; |
| } |
| } |
| } |
| |
| // ---- CONSTRUCT |
| |
| /** Get the template pattern for a construct query */ |
| public Template getConstructTemplate() |
| { |
| return constructTemplate ; |
| } |
| |
| /** Set triple patterns for a construct query */ |
| public void setConstructTemplate(Template templ) { constructTemplate = templ ; } |
| |
| // ---- DESCRIBE |
| |
| public void addDescribeNode(Node node) |
| { |
| if ( node.isVariable() ) { addResultVar(node) ; return ; } |
| if ( node.isURI() || node.isBlank() ) |
| { |
| if ( !resultNodes.contains(node) ) |
| resultNodes.add(node); |
| return ; |
| } |
| if ( node.isLiteral() ) |
| throw new QueryException("Result node is a literal: "+FmtUtils.stringForNode(node)) ; |
| throw new QueryException("Result node not recognized: "+node) ; |
| } |
| |
| |
| /** Get the result list (things wanted - not the results themselves) |
| * of a DESCRIBE query. */ |
| public List<Node> getResultURIs() { return resultNodes ; } |
| |
| private boolean resultVarsSet = false ; |
| /** |
| * Set the results variables if necessary, when the query has "*" ({@code SELECT *} |
| * or {@code DESCRIBE *}) and for a construct query. This operation is idempotent and can |
| * be called to ensure the results variables have been set. |
| */ |
| public void setResultVars() |
| { |
| if ( resultVarsSet ) |
| return ; |
| synchronized(this) { |
| if ( resultVarsSet ) |
| return; |
| // Synchronized in case this query is used in a multithreaded |
| // situation calling setResultVars(). JENA-1861. |
| resetResultVars(); |
| resultVarsSet = true ; |
| } |
| } |
| |
| /** |
| * If modifying a query, it may be necessary to reset the calculate of the result |
| * variables of the query for {@code SELECT *} and {@code DESCRIBE *} and {@code CONSTRUCT}. |
| */ |
| public void resetResultVars() { |
| if ( isQueryResultStar() ) |
| projectVars.clear(); |
| |
| if ( getQueryPattern() == null ) |
| { |
| if ( ! this.isDescribeType() ) |
| Log.warn(this, "setResultVars(): no query pattern") ; |
| return ; |
| } |
| |
| if ( isSelectType() ) |
| { |
| if ( isQueryResultStar() ) |
| findAndAddNamedVars() ; |
| return ; |
| } |
| |
| if ( isConstructType() ) |
| { |
| // All named variables are in-scope |
| findAndAddNamedVars() ; |
| return ; |
| } |
| |
| if ( isDescribeType() ) |
| { |
| if ( isQueryResultStar() ) |
| findAndAddNamedVars() ; |
| return ; |
| } |
| // if ( isAskType() ) |
| // {} |
| } |
| |
| private void findAndAddNamedVars() |
| { |
| Iterator<Var> varIter = null ; |
| if ( hasGroupBy() ) |
| varIter = groupVars.getVars().iterator() ; |
| else |
| { |
| // Binding variables -- in patterns, not in filters and not in EXISTS |
| LinkedHashSet<Var> queryVars = new LinkedHashSet<>() ; |
| PatternVars.vars(queryVars, this.getQueryPattern()) ; |
| if ( this.hasValues() ) |
| queryVars.addAll(getValuesVariables()) ; |
| // if ( this.hasValues() ) |
| // queryVars.addAll(getValuesVariables()) ; |
| varIter = queryVars.iterator() ; |
| } |
| |
| // All query variables, including ones from bNodes in the query. |
| |
| for ( ; varIter.hasNext() ; ) |
| { |
| Var var = varIter.next() ; |
| if ( var.isNamedVar() ) |
| addResultVar(var) ; |
| } |
| } |
| |
| public void visit(QueryVisitor visitor) |
| { |
| visitor.startVisit(this) ; |
| visitor.visitResultForm(this) ; |
| visitor.visitPrologue(this) ; |
| if ( this.isSelectType() ) |
| visitor.visitSelectResultForm(this) ; |
| if ( this.isConstructType() ) |
| visitor.visitConstructResultForm(this) ; |
| if ( this.isDescribeType() ) |
| visitor.visitDescribeResultForm(this) ; |
| if ( this.isAskType() ) |
| visitor.visitAskResultForm(this) ; |
| if ( this.isJsonType() ) |
| visitor.visitJsonResultForm(this) ; |
| visitor.visitDatasetDecl(this) ; |
| visitor.visitQueryPattern(this) ; |
| visitor.visitGroupBy(this) ; |
| visitor.visitHaving(this) ; |
| visitor.visitOrderBy(this) ; |
| visitor.visitOffset(this) ; |
| visitor.visitLimit(this) ; |
| visitor.visitValues(this) ; |
| visitor.finishVisit(this) ; |
| } |
| |
| @Override |
| public Object clone() { return cloneQuery() ; } |
| |
| /** |
| * Makes a copy of this query using the syntax transform machinery. |
| * @return Copy of this query |
| */ |
| public Query cloneQuery() { |
| ElementTransform eltTransform = new ElementTransformCopyBase(true); |
| ExprTransform exprTransform = new ExprTransformApplyElementTransform(eltTransform, true); |
| |
| Query result = QueryTransformOps.transform(this, eltTransform, exprTransform); |
| return result; |
| } |
| |
| // ---- Query canonical syntax |
| |
| // Reverse of parsing : should produce a string that parses to an equivalent query |
| // "Equivalent" => gives the same results on any model |
| @Override |
| public String toString() |
| { return serialize() ; } |
| |
| public String toString(Syntax syntax) |
| { return serialize(syntax) ; } |
| |
| |
| /** Must align with .equals */ |
| private int hashcode = -1 ; |
| |
| @Override |
| public int hashCode() |
| { |
| if ( hashcode == -1 ) |
| { |
| hashcode = QueryHashCode.calc(this) ; |
| if ( hashcode == -1 ) |
| hashcode = Integer.MIN_VALUE/2 ; |
| } |
| return hashcode ; |
| } |
| |
| /** Are two queries equals - tests shape and details. |
| * Equality means that the queries do the same thing, including |
| * same variables, in the same places. Being unequals does |
| * <b>not</b> mean the queries do different things. |
| * |
| * For example, reordering a group or union |
| * means that a query is different. |
| * |
| * Two instances of a query parsed from the same string are equal. |
| */ |
| |
| @Override |
| public boolean equals(Object other) |
| { |
| if ( ! ( other instanceof Query ) ) |
| return false ; |
| if ( this == other ) return true ; |
| return QueryCompare.equals(this, (Query)other) ; |
| } |
| |
| // public static boolean sameAs(Query query1, Query query2) |
| // { return query1.sameAs(query2) ; } |
| |
| @Override |
| public void output(IndentedWriter out) |
| { |
| serialize(out) ; |
| } |
| |
| /** Convert the query to a string */ |
| |
| public String serialize() |
| { |
| IndentedLineBuffer buff = new IndentedLineBuffer() ; |
| serialize(buff) ; |
| return buff.toString(); |
| } |
| |
| /** Convert the query to a string in the given syntax |
| * @param syntax |
| */ |
| |
| public String serialize(Syntax syntax) |
| { |
| IndentedLineBuffer buff = new IndentedLineBuffer() ; |
| serialize(buff, syntax) ; |
| return buff.toString(); |
| } |
| |
| /** Output the query |
| * @param out OutputStream |
| */ |
| public void serialize(OutputStream out) { serialize(out, syntax); } |
| |
| /** Output the query |
| * |
| * @param out OutputStream |
| * @param syntax Syntax URI |
| */ |
| |
| public void serialize(OutputStream out, Syntax syntax) { |
| IndentedWriter writer = new IndentedWriter(out) ; |
| serialize(writer, syntax) ; |
| writer.flush() ; |
| try { out.flush() ; } catch (Exception ex) { } |
| } |
| |
| /** Format the query into the buffer |
| * |
| * @param buff IndentedLineBuffer |
| */ |
| |
| public void serialize(IndentedLineBuffer buff) { |
| serialize(buff, syntax); |
| } |
| |
| /** Format the query |
| * |
| * @param buff IndentedLineBuffer in which to place the unparsed query |
| * @param outSyntax Syntax URI |
| */ |
| |
| public void serialize(IndentedLineBuffer buff, Syntax outSyntax) { |
| serialize((IndentedWriter)buff, outSyntax); |
| } |
| |
| /** Format the query |
| * |
| * @param writer IndentedWriter |
| */ |
| |
| public void serialize(IndentedWriter writer) { |
| serialize(writer, syntax); |
| } |
| |
| /** Format the query |
| * |
| * @param writer IndentedWriter |
| * @param outSyntax Syntax URI |
| */ |
| |
| public void serialize(IndentedWriter writer, Syntax outSyntax) |
| { |
| // Try to use a serializer factory if available |
| QuerySerializerFactory factory = SerializerRegistry.get().getQuerySerializerFactory(outSyntax); |
| QueryVisitor serializer = factory.create(outSyntax, this, writer); |
| this.visit(serializer); |
| } |
| } |