org.apache.jena.query.Query Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jena-arq Show documentation
Show all versions of jena-arq Show documentation
ARQ is a SPARQL 1.1 query engine for Apache Jena
/*
* 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.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.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 */ }
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 ;
int queryType = QueryTypeUnknown ;
// If no model is provided explicitly, the query engine will load
// a model from the URL. Never a list of zero items.
private List graphURIs = new ArrayList<>() ;
private List 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 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 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.allocVarMarker) ;
private Var allocInternVar() { return varAlloc.allocVar() ; }
//private VarAlloc varAnonAlloc = new VarAlloc(ARQConstants.allocVarAnonMarker) ;
//public Var allocVarAnon() { return varAnonAlloc.allocVar() ; }
public void setQuerySelectType() { queryType = QueryTypeSelect ; }
public void setQueryConstructType() { queryType = QueryTypeConstruct ; queryResultStar = true ; }
public void setQueryDescribeType() { queryType = QueryTypeDescribe ; }
public void setQueryAskType() { queryType = QueryTypeAsk ; }
public void setQueryJsonType() { queryType = QueryTypeJson ; }
public int getQueryType() { return queryType ; }
public boolean isSelectType() { return queryType == QueryTypeSelect ; }
public boolean isConstructType() { return queryType == QueryTypeConstruct ; }
public boolean isDescribeType() { return queryType == QueryTypeDescribe ; }
public boolean isAskType() { return queryType == QueryTypeAsk ; }
public boolean isJsonType() { return queryType == QueryTypeJson ; }
public boolean isUnknownType() { return queryType == QueryTypeUnknown ; }
public boolean isConstructQuad() { return isConstructType() && constructTemplate.containsRealQuad() ; }
// 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 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 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 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 getResultVars()
{
// Ensure "SELECT *" processed
setResultVars() ;
return Var.varNames(projectVars.getVars()) ;
}
/** Return a list of the variables requested (SELECT) */
public List 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 thorugh 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 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 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 jsonMapping = new LinkedHashMap<>();
public void addJsonMapping(String key, Node value) {
jsonMapping.put(key, value);
}
public Map 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 aggregators = new ArrayList<>() ;
private Map aggregatorsMap = new HashMap<>() ;
// Note any E_Aggregator created for reuse.
private Map aggregatorsAllocated = new HashMap<>() ;
public boolean hasAggregators() { return aggregators.size() != 0 ; }
public List 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 getValuesVariables() { return valuesDataBlock==null ? null : valuesDataBlock.getVars() ; }
/** Data from a VALUES trailing block. null for a Node means undef */
public List getValuesData() { return valuesDataBlock==null ? null : valuesDataBlock.getRows() ; }
public void setValuesDataBlock(List variables, List values)
{
checkDataBlock(variables, values) ;
valuesDataBlock = new TableData(variables, values) ;
}
private static void checkDataBlock(List variables, List values)
{
// Check.
int N = variables.size() ;
for ( Binding valueRow : values )
{
Iterator 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 getResultURIs() { return resultNodes ; }
private boolean resultVarsSet = false ;
/** Fix up when the query has "*" (when SELECT * or DESCRIBE *)
* and for a construct query. This operation is idempotent.
*/
public void setResultVars()
{
if ( resultVarsSet )
return ;
resultVarsSet = true ;
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 varIter = null ;
if ( hasGroupBy() )
varIter = groupVars.getVars().iterator() ;
else
{
// Binding variables -- in patterns, not in filters and not in EXISTS
LinkedHashSet 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() ; )
{
Object obj = varIter.next() ;
//Var var = (Var)iter.next() ;
Var var = (Var)obj ;
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. Copies by parsing a query from the serialized form of this query
* @return Copy of this query
*/
public Query cloneQuery() {
// A little crude.
// Must use toString() rather than serialize() because we may not know how to serialize extended syntaxes
String qs = this.toString();
return QueryFactory.create(qs, getSyntax()) ;
}
// ---- 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
* not 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);
}
}