@Override public QueryNodeWithBindingSet optimize( final AST2BOpContext context, final QueryNodeWithBindingSet input) { final IQueryNode queryNode = input.getQueryNode(); final IBindingSet[] bindingSets = input.getBindingSets(); final QueryRoot queryRoot = (QueryRoot) queryNode; final StaticAnalysis sa = new StaticAnalysis(queryRoot, context); // First, process any pre-existing named subqueries. { final NamedSubqueriesNode namedSubqueries = queryRoot.getNamedSubqueries(); if (namedSubqueries != null) { // Note: works around concurrent modification error. final List<NamedSubqueryRoot> list = BOpUtility.toList(namedSubqueries, NamedSubqueryRoot.class); for (NamedSubqueryRoot namedSubquery : list) { liftSubqueries(context, sa, namedSubquery.getWhereClause()); } } } // Now process the main where clause. liftSubqueries(context, sa, queryRoot.getWhereClause()); if (false) { /* * Note: This may be enabled to lift all SPARQL 1.1 subqueries into * named subqueries. However, I think that the better way to handle * this is to run the subqueries either as-bound "chunked" or with * ALL solutions from the parent as their inputs (the extreme case * of chunked). This can also be applied to handling OPTIONAL groups. * * @see https://sourceforge.net/apps/trac/bigdata/ticket/377 */ rewriteSparql11Subqueries(context, sa, queryRoot); } return new QueryNodeWithBindingSet(queryRoot, bindingSets); }
private void rewriteSparql11Subqueries( final AST2BOpContext context, final StaticAnalysis sa, final QueryRoot queryRoot) { final Striterator itr2 = new Striterator(BOpUtility.postOrderIterator((BOp) queryRoot.getWhereClause())); itr2.addTypeFilter(SubqueryRoot.class); final List<SubqueryRoot> subqueries = new LinkedList<SubqueryRoot>(); while (itr2.hasNext()) { subqueries.add((SubqueryRoot) itr2.next()); } for (SubqueryRoot subquery : subqueries) { liftSparql11Subquery(context, sa, subquery); } }
public GroupByState( final IValueExpression<?>[] select, final IValueExpression<?>[] groupBy, final IConstraint[] having) { // normalize an empty[] to a null. this.groupBy = groupBy != null && groupBy.length == 0 ? null : groupBy; // must be non-null, non-empty array. this.select = select; if (select == null) throw new IllegalArgumentException(); if (select.length == 0) throw new IllegalArgumentException(); // normalize an empty[] to a null. this.having = having != null && having.length == 0 ? null : having; // true iff any aggregate expression uses DISTINCT. final AtomicBoolean anyDistinct = new AtomicBoolean(false); // true iff any aggregate expression nests another aggregate expression. final AtomicBoolean nestedAggregates = new AtomicBoolean(false); /* * Validate GROUP_BY value expressions. * * Note: The GROUP BY clause may include bare variables such as "?x", * non-aggregate expressions such as "STR(?x)" and declarations of * variables for non-aggregate expressions such as "STR(?x) as strX". * However, only bare variables or variables declared using "AS" may * appear in the SELECT clause. Those variables are collected in * [groupByVars]. * * Note: Aggregate functions MAY NOT appear in the GROUP_BY clause. */ if (groupBy != null) { // Collect top-level variables from GROUP_BY value expressions. for (IValueExpression<?> expr : groupBy) { if (expr instanceof IVariable<?>) { groupByVars.add((IVariable<?>) expr); } else if (expr instanceof IBind<?>) { final IBind<?> bindExpr = (IBind<?>) expr; final IValueExpression<?> e = bindExpr.getExpr(); if (isAggregate( e, false /* isSelectClause */, null /* isSelectDependency */, nestedAggregates, anyDistinct)) { throw new IllegalArgumentException( "Aggregate expression not allowed in GROUP_BY: " + expr); } groupByVars.add(bindExpr.getVar()); } } } /* * Validate SELECT value expressions. * * Note: SELECT value expressions must be either variables appearing in * the top-level of the GROUP BY value expressions -or- a IBind wrapping * an aggregate function. * * Note: Certain optimizations are possible when none of the SELECT * value expressions use DISTINCT. * * Note: Certain optimizations are possible when all of the SELECT value * expressions may be computed based on per-group counters. */ { // true iff any aggregate expression uses a reference to another // aggregate expression in the select clause. final AtomicBoolean selectDependency = new AtomicBoolean(false); for (IValueExpression<?> expr : select) { /* * Each SELECT value expression must be either a top-level * IVariable in the GROUP BY clause or an IBind wrapping a value * expression consisting solely of aggregates (which may of * course wrap bare variables) and constants. */ if (expr instanceof IVariable<?>) { final IVariable<?> var = (IVariable<?>) expr; if (!groupByVars.contains(var)) { throw new IllegalArgumentException( "Bare variable not declared by GROUP_BY clause: " + var); } selectVars.add(var); } else if (expr instanceof IBind<?>) { /* * Child of IBind must be a valid aggregate expression * consisting solely of aggregates (which may wrap bare * variables declared in the GROUP_BY clause) and constants. * * Note: Top-level variables already declared in a GROUP_BY * or SELECT clause MAY appear within other value * expressions in the SELECT clause. * * Note: If any aggregate in the expression uses DISTINCT * then we make a note of that as certain optimizations are * not possible when DISTINCT is used within an aggregate * expression (this is done by isAggregate()). */ final IBind<?> bindExpr = (IBind<?>) expr; final IValueExpression<?> e = bindExpr.getExpr(); if (!isAggregate( e, true /* isSelectClause */, selectDependency, nestedAggregates, anyDistinct)) throw new IllegalArgumentException("Not an aggregate: " + bindExpr); selectVars.add(bindExpr.getVar()); } else { throw new IllegalArgumentException( "Top-level of SELECT expression must be IVariable or IBind: " + expr); } } this.selectDependency = selectDependency.get(); } /* * HAVING clause. * * The having[] may be null or an empty[]. However, any value * expressions used within the IConstraint[] must be aggregates (as * defined for SELECT expressions). */ /* * true iff none of the value expressions in the HAVING clause involve * IAggregate functions. */ boolean simpleHaving = true; if (having != null) { for (IConstraint c : having) { /* * The constraint must be an aggregate expression. * * Note: Top-level variables already declared in a GROUP_BY or * SELECT clause MAY appear within value expressions in the * HAVING clause. * * Note: If any aggregate in the expression uses DISTINCT then * we make a note of that as certain optimizations are not * possible when DISTINCT is used within an aggregate expression * (this is done by isAggregate()). */ if (!isAggregate( c, false /* isSelectClause */, null /* isSelectDependency */, nestedAggregates, anyDistinct)) throw new IllegalArgumentException("Not an aggregate: " + c); if (simpleHaving) { /* * Inspect the value expression for each constraint. * Typically the constraint will be a SPARQLConstraint, * which reports the EBV of a value expression. If that * value expression uses an IAggregate function then we set * [simpleHaving := false]. We are done as soon as we have * falsified the "simpleHaving" hypothesis. */ final IValueExpression<?> expr = ((IValueExpressionConstraint<?>) c).getValueExpression(); final Iterator<BOp> itr = BOpUtility.preOrderIterator(expr); while (itr.hasNext()) { final BOp t = itr.next(); if (t instanceof IAggregate<?>) { simpleHaving = false; break; } } } } } this.simpleHaving = simpleHaving; // true iff any aggregate function nests another aggregate function // within it. this.nestedAggregates = nestedAggregates.get(); // true iff DISTINCT used w/in aggregate function in SELECT or HAVING. this.anyDistinct = anyDistinct.get(); }