/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Aug 29, 2011
*/
package com.bigdata.rdf.sparql.ast.optimizers;
import java.util.Collections;
import java.util.List;
import com.bigdata.bop.BOpUtility;
import com.bigdata.bop.IBindingSet;
import com.bigdata.rdf.model.BigdataURI;
import com.bigdata.rdf.model.BigdataValue;
import com.bigdata.rdf.sparql.ast.ASTContainer;
import com.bigdata.rdf.sparql.ast.AbstractASTEvaluationTestCase;
import com.bigdata.rdf.sparql.ast.BSBMQ5Setup;
import com.bigdata.rdf.sparql.ast.ConstantNode;
import com.bigdata.rdf.sparql.ast.FilterNode;
import com.bigdata.rdf.sparql.ast.FunctionNode;
import com.bigdata.rdf.sparql.ast.GraphPatternGroup;
import com.bigdata.rdf.sparql.ast.IGroupMemberNode;
import com.bigdata.rdf.sparql.ast.IQueryNode;
import com.bigdata.rdf.sparql.ast.JoinGroupNode;
import com.bigdata.rdf.sparql.ast.ProjectionNode;
import com.bigdata.rdf.sparql.ast.QueryBase.Annotations;
import com.bigdata.rdf.sparql.ast.QueryNodeWithBindingSet;
import com.bigdata.rdf.sparql.ast.QueryRoot;
import com.bigdata.rdf.sparql.ast.QueryType;
import com.bigdata.rdf.sparql.ast.StatementPatternNode;
import com.bigdata.rdf.sparql.ast.SubqueryRoot;
import com.bigdata.rdf.sparql.ast.VarNode;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
/**
* Test suite for {@link ASTAttachJoinFiltersOptimizer}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestASTAttachJoinFiltersOptimizer extends AbstractASTEvaluationTestCase {
/**
*
*/
public TestASTAttachJoinFiltersOptimizer() {
}
/**
* @param name
*/
public TestASTAttachJoinFiltersOptimizer(String name) {
super(name);
}
/**
* Unit test for the attachment of the join filters to the required joins in
* a {@link JoinGroupNode}.
* <p>
* Note: The core logic for deciding join filter attachment is tested
* elsewhere. This is only testing the attachment once those decisions are
* made.
*/
@SuppressWarnings("unchecked")
public void test_attachFilters() {
/*
* Note: DO NOT share structures in this test!!!!
*/
final IBindingSet[] bsets = new IBindingSet[]{};
// The source AST.
final QueryRoot given;
{
final BSBMQ5Setup s = new BSBMQ5Setup(store);
given = s.queryRoot;
/**
* Put the joins into a known order whose correct filter attachments
* are also known. This order is <code>[5, 3, 1, 0, 2, 4, 6]</code>.
*/
final GraphPatternGroup<IGroupMemberNode> whereClause = new JoinGroupNode();
given.setWhereClause(whereClause);
// the required joins in a known order.
whereClause.addChild(s.p5);
whereClause.addChild(s.p3);
whereClause.addChild(s.p1);
whereClause.addChild(s.p0);
whereClause.addChild(s.p2);
whereClause.addChild(s.p4);
whereClause.addChild(s.p6);
// now add in the filters.
whereClause.addChild(s.c0);
whereClause.addChild(s.c1);
whereClause.addChild(s.c2);
}
// The expected AST after the rewrite.
final QueryRoot expected;
{
final BSBMQ5Setup s = new BSBMQ5Setup(store);
expected = s.queryRoot;
/*
* Build up the join group. The joins will appear in the same order
* but all of the filters will have been attached to joins.
*/
final GraphPatternGroup<IGroupMemberNode> whereClause = new JoinGroupNode();
expected.setWhereClause(whereClause);
// the required joins in the same order.
whereClause.addChild(s.p5);
whereClause.addChild(s.p3);
whereClause.addChild(s.p1);
whereClause.addChild(s.p0);
whereClause.addChild(s.p2);
whereClause.addChild(s.p4);
whereClause.addChild(s.p6);
// Clear the parent references on the filters.
s.c0.setParent(null);
s.c1.setParent(null);
s.c2.setParent(null);
// now attach the filters to the joins.
s.p0.setAttachedJoinFilters(Collections.singletonList(s.c0));
s.p4.setAttachedJoinFilters(Collections.singletonList(s.c1));
s.p6.setAttachedJoinFilters(Collections.singletonList(s.c2));
}
final IASTOptimizer rewriter = new ASTAttachJoinFiltersOptimizer();
final AST2BOpContext context = new AST2BOpContext(new ASTContainer(
given), store);
final IQueryNode actual = rewriter.optimize(context,
new QueryNodeWithBindingSet(given, bsets)).getQueryNode();
assertSameAST(expected, actual);
/*
* Verify no change if we feed the output back into the input. This
* tests the ability to pick up the attached join filters for
* consideration the next time around.
*
* TODO Actually, the best test would be if we changed the join order
* as well.
*/
final IQueryNode actual2 = rewriter.optimize(context,
new QueryNodeWithBindingSet(BOpUtility.deepCopy(expected), bsets))
.getQueryNode();
assertSameAST(expected, actual2);
}
/**
* Assert proper handling of redundant (identical) filter expressions.
*/
@SuppressWarnings("unchecked")
public void test_redundantFilter() {
final String sampleInstance = "http://www.example.com/I";
final BigdataURI someUri = valueFactory.createURI(sampleInstance);
final BigdataValue[] terms = new BigdataValue[] { someUri };
// resolve terms.
store.getLexiconRelation()
.addTerms(terms, terms.length, false/* readOnly */);
for (BigdataValue bv : terms) {
// Cache the Value on the IV.
bv.getIV().setValue(bv);
}
// The source AST:
/*
* QueryType: SELECT
* SELECT VarNode(s)
* JoinGroupNode {
* QueryType: SELECT
* SELECT VarNode(s)
* JoinGroupNode {
* StatementPatternNode(VarNode(s), VarNode(p), VarNode(o)) [scope=DEFAULT_CONTEXTS]
* }
* FILTER( FunctionNode(VarNode(s),VarNode(s))[FunctionNode.scalarVals=null, FunctionNode.functionURI=http://www.w3.org/2005/xpath-functions#not-equal-to, valueExpr=com.bigdata.rdf.internal.constraints.CompareBOp(s,TermId(0U)[eg:b])[ CompareBOp.op=NE]] )
* FILTER( FunctionNode(VarNode(s),VarNode(s))[ FunctionNode.scalarVals=null, FunctionNode.functionURI=http://www.w3.org/2005/xpath-functions#not-equal-to, valueExpr=com.bigdata.rdf.internal.constraints.CompareBOp(s,TermId(0U)[eg:b])[ CompareBOp.op=NE]] )
* }
*/
final QueryRoot qUnoptimized = new QueryRoot(QueryType.SELECT);
{
VarNode varS = new VarNode("s");
VarNode varP = new VarNode("p");
VarNode varO = new VarNode("o");
///////////////////////////////////// STEP 1: build inner subquery
// build innermost join
JoinGroupNode innerJoin = new JoinGroupNode();
innerJoin.addArg(
new StatementPatternNode(varS, varP, varO));
// build inner projection
ProjectionNode innerProjection = new ProjectionNode();
innerProjection.addProjectionVar(new VarNode("s"));
// put it together to inner query
final SubqueryRoot qUnoptimizedInner = new SubqueryRoot(QueryType.SELECT);
qUnoptimizedInner.setProjection(innerProjection);
qUnoptimizedInner.setWhereClause(innerJoin);
///////////////////////////////////// STEP 1: build outer query
// build filter expressions
FilterNode f1 = new FilterNode(FunctionNode.NE(
new ConstantNode(someUri.getIV()), varS));
FilterNode f2 = new FilterNode(FunctionNode.NE(
new ConstantNode(someUri.getIV()), varS));
// build outer join
JoinGroupNode outerJoin = new JoinGroupNode();
outerJoin.addArg(qUnoptimizedInner);
outerJoin.addArg(f1);
outerJoin.addArg(f2);
// build top-level projection
ProjectionNode projection = new ProjectionNode();
projection.addProjectionVar(varS);
// compose final query
qUnoptimized.setWhereClause(outerJoin);
qUnoptimized.setProjection(projection);
}
System.out.println(qUnoptimized);
// we need to apply the value exp rewriter to calculate value exps
final ASTSetValueExpressionsOptimizer valueExpRewriter =
new ASTSetValueExpressionsOptimizer();
final AST2BOpContext contextValueExpRewriter =
new AST2BOpContext(new ASTContainer(qUnoptimized), store);
final IBindingSet[] bsetsValueExpRewriter = new IBindingSet[]{};
final IQueryNode qIntermediate =
valueExpRewriter.optimize(
contextValueExpRewriter,
new QueryNodeWithBindingSet(qUnoptimized, bsetsValueExpRewriter))
.getQueryNode();
// the join rewriter is what we want to test
final IASTOptimizer joinRewriter =
new ASTAttachJoinFiltersOptimizer();
final AST2BOpContext contextJoinRewriter =
new AST2BOpContext(new ASTContainer(
(QueryRoot)qIntermediate), store);
final IBindingSet[] bsetsJoinRewriter = new IBindingSet[]{};
final IQueryNode qOptimized = joinRewriter.optimize(
contextJoinRewriter,
new QueryNodeWithBindingSet(qIntermediate, bsetsJoinRewriter))
.getQueryNode();
/** This is the output we expect
* QueryType: SELECT
* SELECT VarNode(s)
* JoinGroupNode {
* QueryType: SELECT
* SELECT VarNode(s)
* JoinGroupNode {
* StatementPatternNode(VarNode(s), VarNode(p), VarNode(o)) [scope=DEFAULT_CONTEXTS]
* }
* FILTER( FunctionNode(ConstantNode(TermId(1U)[http://www.example.com/I]),VarNode(s))[ FunctionNode.scalarVals=null, FunctionNode.functionURI=http://www.w3.org/2005/xpath-functions#not-equal-to, valueExpr=com.bigdata.rdf.internal.constraints.CompareBOp(TermId(1U)[http://www.example.com/I],s)[ CompareBOp.op=NE]] )
* }
*/
// the optimized query contains no more filter at top-level, but one
// filter nested inside the SELECT clause (duplicate has been removed)
assertEquals(
QueryType.SELECT,
qOptimized.annotations().get(Annotations.QUERY_TYPE));
JoinGroupNode topLevelJoin =
(JoinGroupNode)qOptimized.annotations().get(Annotations.GRAPH_PATTERN);
List<IGroupMemberNode> topLevelJoinChildren = topLevelJoin.getChildren();
// the top level join has only one subquery, namely the select; filters
// have been pushed inside
assertEquals(topLevelJoinChildren.size(),1);
SubqueryRoot innerSelect = (SubqueryRoot)topLevelJoinChildren.get(0);
assertEquals(
QueryType.SELECT,
innerSelect.annotations().get(Annotations.QUERY_TYPE));
JoinGroupNode innerJoin =
(JoinGroupNode)innerSelect.annotations().get(Annotations.GRAPH_PATTERN);
assertNotNull(innerJoin);
List<FilterNode> filters =
(List<FilterNode>)innerSelect.annotations().get("filters");
assertEquals(filters.size(),1);
}
}