/** 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 June 10, 2015 */ package com.bigdata.rdf.sparql.ast.optimizers; import java.util.ArrayList; import java.util.List; import org.openrdf.query.algebra.StatementPattern.Scope; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.bindingSet.ListBindingSet; import com.bigdata.rdf.internal.IV; import com.bigdata.rdf.model.BigdataURI; import com.bigdata.rdf.model.BigdataValue; import com.bigdata.rdf.model.BigdataValueFactory; import com.bigdata.rdf.sparql.ast.ASTContainer; import com.bigdata.rdf.sparql.ast.AbstractASTEvaluationTestCase; 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.FunctionRegistry; import com.bigdata.rdf.sparql.ast.IQueryNode; import com.bigdata.rdf.sparql.ast.IValueExpressionNode; import com.bigdata.rdf.sparql.ast.JoinGroupNode; import com.bigdata.rdf.sparql.ast.ProjectionNode; import com.bigdata.rdf.sparql.ast.QueryHints; 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.StaticAnalysis; import com.bigdata.rdf.sparql.ast.ValueExpressionNode; import com.bigdata.rdf.sparql.ast.VarNode; import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext; /** * Test suite for the {@link ASTFilterNormalizationOptimizer} class and associated * utility methods in {@link StaticAnalysis}. * * @author <a href="mailto:ms@metaphacts.com">Michael Schmidt</a> */ @SuppressWarnings({ "rawtypes" }) public class TestASTFilterNormalizationOptimizer extends AbstractASTEvaluationTestCase { public TestASTFilterNormalizationOptimizer() { } public TestASTFilterNormalizationOptimizer(String name) { super(name); } /** * Test the {@link ASTFilterNormalizationOptimizer#extractToplevelConjuncts( * com.bigdata.rdf.sparql.ast.IValueExpressionNode, List)} method. */ public void testExtractTopLevelConjunctsMethod() { // conjunct 1 final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); // conjunct 2 final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode not1 = FunctionNode.NOT(bound2); // conjunct 3 final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode or1 = FunctionNode.OR(FunctionNode.AND(bound3,bound4), bound5); // conjunct 4 final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); final FunctionNode toCheck = FunctionNode.AND(bound1, FunctionNode.AND( not1, FunctionNode.AND(or1, bound6))); final List<IValueExpressionNode> actual = StaticAnalysis.extractToplevelConjuncts( toCheck, new ArrayList<IValueExpressionNode>()); assertFalse(StaticAnalysis.isCNF(toCheck)); assertEquals(actual.size(), 4); assertEquals(actual.get(0), bound1); assertEquals(actual.get(1), not1); assertEquals(actual.get(2), or1); assertEquals(actual.get(3), bound6); } /** * Test the {@link ASTFilterNormalizationOptimizer#constructFiltersForValueExpressionNode( * IValueExpressionNode, List)} method. */ public void testConstructFiltersForValueExpressionNodeMethod() { // conjunct 1 final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode or1 = FunctionNode.OR(FunctionNode.AND(bound3,bound4), bound5); // conjunct 2 final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode not1 = FunctionNode.NOT(bound2); // conjunct 3 final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); // conjunct 4 final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode base = FunctionNode.AND( FunctionNode.AND( or1, FunctionNode.AND(not1, bound6)),bound1); final ASTFilterNormalizationOptimizer filterOptimizer = new ASTFilterNormalizationOptimizer(); final List<FilterNode> filters = filterOptimizer.constructFiltersForValueExpressionNode( base, new ArrayList<FilterNode>()); assertFalse(StaticAnalysis.isCNF(base)); assertEquals(filters.size(), 4); assertEquals(filters.get(0), new FilterNode(or1)); assertEquals(filters.get(1), new FilterNode(not1)); assertEquals(filters.get(2), new FilterNode(bound6)); assertEquals(filters.get(3), new FilterNode(bound1)); } /** * Test the {@link ASTFilterNormalizationOptimizer#toConjunctiveValueExpression(List)} * method. */ public void testToConjunctiveValueExpressionMethod() { // conjunct 1 final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); // conjunct 2 final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode not1 = FunctionNode.NOT(bound2); // conjunct 3 final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode or1 = FunctionNode.OR(FunctionNode.AND(bound3,bound4), bound5); // conjunct 4 final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); final List<IValueExpressionNode> baseConjuncts = new ArrayList<IValueExpressionNode>(); baseConjuncts.add(bound1); baseConjuncts.add(not1); baseConjuncts.add(or1); baseConjuncts.add(bound6); final IValueExpressionNode expected = FunctionNode.AND( FunctionNode.AND( FunctionNode.AND(bound1, not1), or1), bound6); final IValueExpressionNode actual = StaticAnalysis.toConjunctiveValueExpression(baseConjuncts); assertFalse(StaticAnalysis.isCNF(actual)); assertEquals(expected, actual); } /** * The FILTER * * <pre> * SELECT ?s where { ?s ?p ?o . FILTER(?s=?o) } * </pre> * * is not being modified. */ public void testFilterDecompositionNoOp() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); given.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o"), null/* c */, Scope.DEFAULT_CONTEXTS)); whereClause.addChild( new FilterNode( FunctionNode.EQ(new VarNode("s"), new VarNode("o")))); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); expected.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o"), null/* c */, Scope.DEFAULT_CONTEXTS)); final FilterNode filterNode = new FilterNode( FunctionNode.EQ(new VarNode("s"), new VarNode("o"))); assertTrue(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * The FILTER * * <pre> * SELECT ?s where { ?s ?p ?o . FILTER(?s=?o && ?s!=<http://www.test.com>) } * </pre> * * is rewritten as * * <pre> * SELECT ?s where { ?s ?p ?o . FILTER(?s=?o) . FILTER(?s!=<http://www.test.com>) } * </pre> * */ public void testSimpleConjunctiveFilter() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final BigdataValueFactory f = store.getValueFactory(); final BigdataURI testUri = f.createURI("http://www.test.com"); final IV test = makeIV(testUri); final BigdataValue[] values = new BigdataValue[] { testUri }; store.getLexiconRelation() .addTerms(values, values.length, false/* readOnly */); final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o"), null/* c */, Scope.DEFAULT_CONTEXTS)); final FilterNode filterNode = new FilterNode( FunctionNode.AND( FunctionNode.EQ(new VarNode("s"), new VarNode("o")), FunctionNode.NE(new VarNode("s"), new ConstantNode(test)))); assertTrue(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o"), null/* c */, Scope.DEFAULT_CONTEXTS)); whereClause.addChild( new FilterNode( FunctionNode.EQ(new VarNode("s"), new VarNode("o")))); whereClause.addChild( new FilterNode( FunctionNode.NE(new VarNode("s"), new ConstantNode(test)))); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * The FILTER * * <pre> * SELECT ?s where { FILTER(NOT(?s<?o || BOUND(?o))) . OPTIONAL { ?s ?p ?o } } * </pre> * * is rewritten as * * <pre> * SELECT ?s where { OPTIONAL { ?s ?p ?o } . FILTER(?s>=?o) . FILTER(!BOUND(?s) } * </pre> * */ public void testSimpleDisjunctiveFilter() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final FilterNode filterNode = new FilterNode( FunctionNode.NOT( FunctionNode.OR( FunctionNode.LT(new VarNode("s"), new VarNode("o")), new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("o") })))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); spn.setOptional(true); whereClause.addChild(spn); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); spn.setOptional(true); whereClause.addChild(spn); whereClause.addChild( new FilterNode( FunctionNode.GE(new VarNode("s"), new VarNode("o")))); whereClause.addChild( new FilterNode( FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("o") })))); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test rewriting of negated leaves, such as !(?x=?y) -> ?x!=?y, * !(?a<?b) -> ?a>=?b, etc. in */ public void testNegationLeafRewriting01() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final FunctionNode filterEq = FunctionNode.EQ(new VarNode("s"), new VarNode("o")); final FunctionNode filterNeq = FunctionNode.NE(new VarNode("s"), new VarNode("o")); final FunctionNode filterLe = FunctionNode.LE(new VarNode("s"), new VarNode("o")); final FunctionNode filterLt = FunctionNode.LT(new VarNode("s"), new VarNode("o")); final FunctionNode filterGe = FunctionNode.GE(new VarNode("s"), new VarNode("o")); final FunctionNode filterGt = FunctionNode.GT(new VarNode("s"), new VarNode("o")); final FunctionNode comb1 = FunctionNode.AND(filterEq, filterNeq); final FunctionNode comb2 = FunctionNode.AND(filterLe, filterLt); final FunctionNode comb3 = FunctionNode.AND(filterGt, filterGe); final FilterNode filterNode = new FilterNode( FunctionNode.NOT( FunctionNode.AND(FunctionNode.AND(comb1, comb2),comb3))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final FunctionNode filterEqInv = FunctionNode.NE(new VarNode("s"), new VarNode("o")); final FunctionNode filterNeqInv = FunctionNode.EQ(new VarNode("s"), new VarNode("o")); final FunctionNode filterLeInv = FunctionNode.GT(new VarNode("s"), new VarNode("o")); final FunctionNode filterLtInv = FunctionNode.GE(new VarNode("s"), new VarNode("o")); final FunctionNode filterGeInv = FunctionNode.LT(new VarNode("s"), new VarNode("o")); final FunctionNode filterGtInv = FunctionNode.LE(new VarNode("s"), new VarNode("o")); final FunctionNode comb1 = FunctionNode.OR(filterEqInv, filterNeqInv); final FunctionNode comb2 = FunctionNode.OR(filterLeInv, filterLtInv); final FunctionNode comb3 = FunctionNode.OR(filterGtInv, filterGeInv); final FilterNode filterNode = new FilterNode( FunctionNode.OR(FunctionNode.OR(comb1, comb2),comb3)); assertTrue(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test rewriting of negated leaves, such as !(?x=?y) -> ?x!=?y, * !(?a<?b) -> ?a>=?b, etc. (differs from v01 in tree shape). */ public void testNegationLeafRewriting02() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final FunctionNode filterEq = FunctionNode.EQ(new VarNode("s"), new VarNode("o")); final FunctionNode filterNeq = FunctionNode.NE(new VarNode("s"), new VarNode("o")); final FunctionNode filterLe = FunctionNode.LE(new VarNode("s"), new VarNode("o")); final FunctionNode filterLt = FunctionNode.LT(new VarNode("s"), new VarNode("o")); final FunctionNode filterGe = FunctionNode.GE(new VarNode("s"), new VarNode("o")); final FunctionNode filterGt = FunctionNode.GT(new VarNode("s"), new VarNode("o")); final FunctionNode comb1 = FunctionNode.AND(filterEq, filterNeq); final FunctionNode comb2 = FunctionNode.AND(filterLe, filterLt); final FunctionNode comb3 = FunctionNode.AND(filterGt, filterGe); final FilterNode filterNode = new FilterNode( FunctionNode.NOT( FunctionNode.AND(comb1, FunctionNode.AND(comb2,comb3)))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final FunctionNode filterEqInv = FunctionNode.NE(new VarNode("s"), new VarNode("o")); final FunctionNode filterNeqInv = FunctionNode.EQ(new VarNode("s"), new VarNode("o")); final FunctionNode filterLeInv = FunctionNode.GT(new VarNode("s"), new VarNode("o")); final FunctionNode filterLtInv = FunctionNode.GE(new VarNode("s"), new VarNode("o")); final FunctionNode filterGeInv = FunctionNode.LT(new VarNode("s"), new VarNode("o")); final FunctionNode filterGtInv = FunctionNode.LE(new VarNode("s"), new VarNode("o")); final FunctionNode comb1 = FunctionNode.OR(filterEqInv, filterNeqInv); final FunctionNode comb2 = FunctionNode.OR(filterLeInv, filterLtInv); final FunctionNode comb3 = FunctionNode.OR(filterGtInv, filterGeInv); final FilterNode filterNode = new FilterNode( FunctionNode.OR(comb1, FunctionNode.OR(comb2,comb3))); assertTrue(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test level three pushing of negation. */ public void testNestedNegationRewriting() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final FunctionNode filterANot1 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("o") })); final FunctionNode filterANot2 = FunctionNode.NOT(filterANot1); final FunctionNode filterANot3 = FunctionNode.NOT(filterANot2); final FunctionNode filterBNot1 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.EQ, null, new ValueExpressionNode[] { new VarNode("s"), new VarNode("o") })); final FunctionNode filterBNot2 = FunctionNode.NOT(filterBNot1); final FunctionNode filterBNot3 = FunctionNode.NOT(filterBNot2); final FunctionNode filterBNot4 = FunctionNode.NOT(filterBNot3); final FilterNode filterNode = new FilterNode( FunctionNode.NOT( FunctionNode.AND(filterANot3, filterBNot4))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final FunctionNode bound = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("o") }); final FunctionNode neq = new FunctionNode(FunctionRegistry.NE, null, new ValueExpressionNode[] { new VarNode("s"), new VarNode("o") }); FilterNode filterNode = new FilterNode(FunctionNode.OR(bound, neq)); assertTrue(StaticAnalysis.isCNF(filterNode)); // all NOT nodes should be resolved whereClause.addChild(filterNode); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test level three pushing of negation. */ public void testNestedNegationRewritingAndSplit() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final FunctionNode filterANot1 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("o") })); final FunctionNode filterANot2 = FunctionNode.NOT(filterANot1); final FunctionNode filterANot3 = FunctionNode.NOT(filterANot2); final FunctionNode filterBNot1 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.EQ, null, new ValueExpressionNode[] { new VarNode("s"), new VarNode("o") })); final FunctionNode filterBNot2 = FunctionNode.NOT(filterBNot1); final FunctionNode filterBNot3 = FunctionNode.NOT(filterBNot2); final FunctionNode filterBNot4 = FunctionNode.NOT(filterBNot3); final FilterNode filterNode = new FilterNode( FunctionNode.NOT( FunctionNode.OR(filterANot3, filterBNot4))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final FunctionNode bound = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("o") }); final FunctionNode neq = new FunctionNode(FunctionRegistry.NE, null, new ValueExpressionNode[] { new VarNode("s"), new VarNode("o") }); whereClause.addChild(new FilterNode(bound)); whereClause.addChild(new FilterNode(neq)); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test switch of OR over AND expression expression. */ public void testSimpleOrAndSwitch() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FilterNode filterNode = new FilterNode( FunctionNode.OR( FunctionNode.AND(bound1, bound2), FunctionNode.AND(bound3, FunctionNode.AND(bound4, bound5)))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode and1 = FunctionNode.OR(bound1, bound3); final FunctionNode and2 = FunctionNode.OR(bound1, bound4); final FunctionNode and3 = FunctionNode.OR(bound1, bound5); final FunctionNode and4 = FunctionNode.OR(bound2, bound3); final FunctionNode and5 = FunctionNode.OR(bound2, bound4); final FunctionNode and6 = FunctionNode.OR(bound2, bound5); // after splitting, we should get the following conjuncts whereClause.addChild(new FilterNode(and1)); whereClause.addChild(new FilterNode(and2)); whereClause.addChild(new FilterNode(and3)); whereClause.addChild(new FilterNode(and4)); whereClause.addChild(new FilterNode(and5)); whereClause.addChild(new FilterNode(and6)); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test switch of OR over AND expression with top-level negation expression. */ public void testOrAndSwitchWithNegation() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setQueryHint(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode notBound1 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") })); final FunctionNode notBound2 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") })); final FunctionNode notBound3 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") })); final FunctionNode notBound4 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") })); final FunctionNode notBound5 = FunctionNode.NOT( new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") })); final FilterNode filterNode = new FilterNode( FunctionNode.NOT( FunctionNode.AND( FunctionNode.OR(notBound1, notBound2), FunctionNode.OR(notBound3, FunctionNode.OR(notBound4, notBound5))))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setQueryHint(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode or1 = FunctionNode.OR(bound1, bound3); final FunctionNode or2 = FunctionNode.OR(bound1, bound4); final FunctionNode or3 = FunctionNode.OR(bound1, bound5); final FunctionNode or4 = FunctionNode.OR(bound2, bound3); final FunctionNode or5 = FunctionNode.OR(bound2, bound4); final FunctionNode or6 = FunctionNode.OR(bound2, bound5); // after splitting, we should get the following conjuncts whereClause.addChild(new FilterNode(or1)); whereClause.addChild(new FilterNode(or2)); whereClause.addChild(new FilterNode(or3)); whereClause.addChild(new FilterNode(or4)); whereClause.addChild(new FilterNode(or5)); whereClause.addChild(new FilterNode(or6)); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test recursive optimization of OR - AND - OR - AND pattern. */ public void testOrAndSwitchRecursive() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); final FunctionNode bound7 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s7") }); final FunctionNode bound8 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s8") }); final FunctionNode bound9 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s9") }); final FunctionNode bound10 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s10") }); final FunctionNode bound11 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s11") }); final FunctionNode bound12 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s12") }); final FunctionNode bound13 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s13") }); final FunctionNode bound14 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s14") }); final FunctionNode bound15 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s15") }); final FunctionNode bound16 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s16") }); final FilterNode filterNode = new FilterNode( FunctionNode.OR( FunctionNode.AND( FunctionNode.OR( FunctionNode.AND(bound1, bound2), FunctionNode.AND(bound3, bound4) ), FunctionNode.OR( FunctionNode.AND(bound5, bound6), FunctionNode.AND(bound7, bound8) ) ), FunctionNode.AND( FunctionNode.OR( FunctionNode.AND(bound9, bound10), FunctionNode.AND(bound11, bound12) ), FunctionNode.OR( FunctionNode.AND(bound13, bound14), FunctionNode.AND(bound15, bound16) )))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); final FunctionNode bound7 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s7") }); final FunctionNode bound8 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s8") }); final FunctionNode bound9 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s9") }); final FunctionNode bound10 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s10") }); final FunctionNode bound11 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s11") }); final FunctionNode bound12 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s12") }); final FunctionNode bound13 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s13") }); final FunctionNode bound14 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s14") }); final FunctionNode bound15 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s15") }); final FunctionNode bound16 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s16") }); /** * Sketch of intended rewriting process (bottom-up) * * ### STEP 1: for the four leaf nodes we get the following. * * FunctionNode.OR( * FunctionNode.AND( * FunctionNode.AND( * FunctionNode.OR(bound1,bound3) * FunctionNode.OR(bound1,bound4) * FunctionNode.OR(bound2,bound3) * FunctionNode.OR(bound2,bound4) * ), * FunctionNode.AND( * FunctionNode.OR(bound5,bound7) * FunctionNode.OR(bound5,bound8) * FunctionNode.OR(bound6,bound7) * FunctionNode.OR(bound6,bound8) * ) * ), * FunctionNode.AND( * FunctionNode.AND( * FunctionNode.OR(bound9,bound11) * FunctionNode.OR(bound9,bound12) * FunctionNode.OR(bound10,bound11) * FunctionNode.OR(bound10,bound12) * ), * FunctionNode.AND( * FunctionNode.OR(bound13,bound15) * FunctionNode.OR(bound13,bound16) * FunctionNode.OR(bound14,bound15) * FunctionNode.OR(bound14,bound16) * ) * ) * ) * * ### STEP 2: pushing down the top-level OR, we compute the cross * product of the left and right disjuncts (we flatten * out the and in the representation below): * * FunctionNode.AND( * FunctionNode.AND( * FunctionNode.OR( * FunctionNode.OR(bound1,bound3), * FunctionNode.OR(bound9,bound11) * ), * FunctionNode.OR( * FunctionNode.OR(bound1,bound3), * FunctionNode.OR(bound9,bound12) * ), * FunctionNode.OR( * FunctionNode.OR(bound1,bound3), * FunctionNode.OR(bound10,bound11) * ), * FunctionNode.OR( * FunctionNode.OR(bound1,bound3), * FunctionNode.OR(bound10,bound12) * ), * * FunctionNode.OR( * FunctionNode.OR(bound1,bound4), * FunctionNode.OR(bound9,bound11) * ), * FunctionNode.OR( * FunctionNode.OR(bound1,bound4), * FunctionNode.OR(bound9,bound12) * ), * FunctionNode.OR( * FunctionNode.OR(bound1,bound4), * FunctionNode.OR(bound10,bound11) * ), * FunctionNode.OR( * FunctionNode.OR(bound1,bound4), * FunctionNode.OR(bound10,bound12) * ), * * ... * * Each of those topmost OR expression gives us one FILTER expression * in the end, resulting in 8x8 = 64 FILTERs. We construct them * schematically below. */ final List<FunctionNode> lefts = new ArrayList<FunctionNode>(); lefts.add(FunctionNode.OR(bound1,bound3)); lefts.add(FunctionNode.OR(bound1,bound4)); lefts.add(FunctionNode.OR(bound2,bound3)); lefts.add(FunctionNode.OR(bound2,bound4)); lefts.add(FunctionNode.OR(bound5,bound7)); lefts.add(FunctionNode.OR(bound5,bound8)); lefts.add(FunctionNode.OR(bound6,bound7)); lefts.add(FunctionNode.OR(bound6,bound8)); final List<FunctionNode> rights = new ArrayList<FunctionNode>(); rights.add(FunctionNode.OR(bound9,bound11)); rights.add(FunctionNode.OR(bound9,bound12)); rights.add(FunctionNode.OR(bound10,bound11)); rights.add(FunctionNode.OR(bound10,bound12)); rights.add(FunctionNode.OR(bound13,bound15)); rights.add(FunctionNode.OR(bound13,bound16)); rights.add(FunctionNode.OR(bound14,bound15)); rights.add(FunctionNode.OR(bound14,bound16)); for (final FunctionNode left : lefts) { for (final FunctionNode right : rights) { whereClause.addChild( new FilterNode(FunctionNode.OR(left, right))); } } } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test recursive optimization of OR - OR - AND pattern. */ public void testOrOrAndSwitch() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); final FunctionNode bound7 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s7") }); final FunctionNode bound8 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s8") }); final FilterNode filterNode = new FilterNode( FunctionNode.OR( FunctionNode.OR( FunctionNode.AND(bound1, bound2), FunctionNode.AND(bound3, bound4) ), FunctionNode.OR( FunctionNode.AND(bound5, bound6), FunctionNode.AND(bound7, bound8) ))); assertFalse(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final StatementPatternNode spn = new StatementPatternNode( new VarNode("s"), new VarNode("p"), new VarNode("o"), null, Scope.DEFAULT_CONTEXTS); whereClause.addChild(spn); final FunctionNode bound1 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }); final FunctionNode bound2 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s2") }); final FunctionNode bound3 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s3") }); final FunctionNode bound4 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s4") }); final FunctionNode bound5 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s5") }); final FunctionNode bound6 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s6") }); final FunctionNode bound7 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s7") }); final FunctionNode bound8 = new FunctionNode(FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s8") }); /** * * ### STEP 1: generates to OR connected leafs in CNF * * FunctionNode.OR( * FunctionNode.AND( * FunctionNode.AND( * FunctionNode.OR(bound1, bound3), * FunctionNode.OR(bound1, bound4) * ), * FunctionNode.AND( * FunctionNode.OR(bound2, bound3), * FunctionNode.OR(bound2, bound4) * ) * ), * FunctionNode.AND( * FunctionNode.AND( * FunctionNode.OR(bound5, bound7), * FunctionNode.OR(bound5, bound8) * ), * FunctionNode.AND( * FunctionNode.OR(bound6, bound7), * FunctionNode.OR(bound6, bound8) * ) * ) * ) * * ### STEP 2: pushes down the uppermost OR expression * * Considers all OR-leafs in the left top-level AND expression * and joins them with OR-leafs in the right top-level AND expression. * After decomposing, this actually gives us 4x4 = 16 FILTERs. * */ final List<FunctionNode> lefts = new ArrayList<FunctionNode>(); lefts.add(FunctionNode.OR(bound1,bound3)); lefts.add(FunctionNode.OR(bound1,bound4)); lefts.add(FunctionNode.OR(bound2,bound3)); lefts.add(FunctionNode.OR(bound2,bound4)); final List<FunctionNode> rights = new ArrayList<FunctionNode>(); rights.add(FunctionNode.OR(bound5,bound7)); rights.add(FunctionNode.OR(bound5,bound8)); rights.add(FunctionNode.OR(bound6,bound7)); rights.add(FunctionNode.OR(bound6,bound8)); for (final FunctionNode left : lefts) { for (final FunctionNode right : rights) { whereClause.addChild( new FilterNode(FunctionNode.OR(left, right))); } } } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test removal of duplicate filter. */ public void testRemoveDuplicateFilter() { final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s1"), new VarNode("p1"), new VarNode("o1"), null/* c */, Scope.DEFAULT_CONTEXTS)); // just noise // two times exactly the same pattern final FunctionNode simpleFunctionNode1 = FunctionNode.NE(new VarNode("s1"), new VarNode("s2")); final FunctionNode simpleFunctionNode2 = FunctionNode.NE(new VarNode("s1"), new VarNode("s2")); // three times the same pattern final FunctionNode complexFunctionNode1 = FunctionNode.OR( FunctionNode.NE(new VarNode("s1"), new VarNode("s2")), FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }))); final FunctionNode complexFunctionNode2 = FunctionNode.OR( FunctionNode.NE(new VarNode("s1"), new VarNode("s2")), FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }))); final FunctionNode complexFunctionNode3 = FunctionNode.OR( FunctionNode.NE(new VarNode("s1"), new VarNode("s2")), FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }))); whereClause.addChild(new FilterNode(simpleFunctionNode1)); whereClause.addChild(new FilterNode(simpleFunctionNode2)); whereClause.addChild(new FilterNode(complexFunctionNode1)); whereClause.addChild(new FilterNode(complexFunctionNode2)); whereClause.addChild(new StatementPatternNode(new VarNode("s2"), new VarNode("p2"), new VarNode("o2"), null/* c */, Scope.DEFAULT_CONTEXTS)); // just noise whereClause.addChild(new FilterNode(complexFunctionNode3)); assertTrue(StaticAnalysis.isCNF(simpleFunctionNode1)); assertTrue(StaticAnalysis.isCNF(simpleFunctionNode2)); assertTrue(StaticAnalysis.isCNF(complexFunctionNode1)); assertTrue(StaticAnalysis.isCNF(complexFunctionNode2)); assertTrue(StaticAnalysis.isCNF(complexFunctionNode3)); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s1"), new VarNode("p1"), new VarNode("o1"), null/* c */, Scope.DEFAULT_CONTEXTS)); // just noise // the simple function node final FunctionNode simpleFunctionNode = FunctionNode.NE(new VarNode("s1"), new VarNode("s2")); // the complex function node final FunctionNode complexFunctionNode = FunctionNode.OR( FunctionNode.NE(new VarNode("s1"), new VarNode("s2")), FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null, new ValueExpressionNode[] { new VarNode("s1") }))); whereClause.addChild(new FilterNode(simpleFunctionNode)); whereClause.addChild(new StatementPatternNode(new VarNode("s2"), new VarNode("p2"), new VarNode("o2"), null/* c */, Scope.DEFAULT_CONTEXTS)); // just noise whereClause.addChild(new FilterNode(complexFunctionNode)); assertTrue(StaticAnalysis.isCNF(simpleFunctionNode)); assertTrue(StaticAnalysis.isCNF(complexFunctionNode)); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test removal of duplicate filter, where the duplicate is introduced * through the CNF based decomposition process. This is a variant of test * {@link TestASTFilterNormalizationOptimizer#testSimpleConjunctiveFilter()}, * where we just add a duplicate. */ public void testRemoveDuplicateGeneratedFilter() { /* * Note: DO NOT share structures in this test!!!! */ final BigdataValueFactory f = store.getValueFactory(); final BigdataURI testUri = f.createURI("http://www.test.com"); final IV test = makeIV(testUri); final BigdataValue[] values = new BigdataValue[] { testUri }; store.getLexiconRelation() .addTerms(values, values.length, false/* readOnly */); final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o"), null/* c */, Scope.DEFAULT_CONTEXTS)); final FilterNode filterNode = new FilterNode( FunctionNode.AND( FunctionNode.EQ(new VarNode("s"), new VarNode("o")), FunctionNode.NE(new VarNode("s"), new ConstantNode(test)))); // difference towards base test: this is the duplicate to be dropped whereClause.addChild( new FilterNode( FunctionNode.EQ(new VarNode("s"), new VarNode("o")))); assertTrue(StaticAnalysis.isCNF(filterNode)); whereClause.addChild(filterNode); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); whereClause.addChild(new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o"), null/* c */, Scope.DEFAULT_CONTEXTS)); whereClause.addChild( new FilterNode( FunctionNode.EQ(new VarNode("s"), new VarNode("o")))); whereClause.addChild( new FilterNode( FunctionNode.NE(new VarNode("s"), new ConstantNode(test)))); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } /** * Test removal of unsatisfiable filters. More precisely, the query * * SELECT ?s WHERE { * ?s ?p ?o1 . * { ?s ?p ?o2 } * OPTIONAL { ?s ?p ?o3 } * * FILTER(bound(?o1)) * FILTER(bound(?o2)) * FILTER(bound(?o3)) * * FILTER(!bound(?o1)) * FILTER(!bound(?o2)) * FILTER(!bound(?o3)) * FILTER(!bound(?o4)) * * // some duplicates (which should be dropped) * FILTER(!bound(?o2)) * FILTER(!bound(?o3)) * } * * will be rewritten to * * SELECT ?s WHERE { * ?s ?p ?o1 . * { ?s ?p ?o2 } * OPTIONAL { ?s ?p ?o3 } * * // ?o1 and ?o2 are definitely bound, so we can't optimize away * FILTER(bound(?o3)) * * // ?o4 is the only variable that is definitely not bound * FILTER(!bound(?o1)) * FILTER(!bound(?o2)) * FILTER(!bound(?o3)) * } * */ public void testRemoveUnsatisfiableFilters() { /* * Note: DO NOT share structures in this test!!!! */ final IBindingSet[] bsets = new IBindingSet[] { new ListBindingSet() }; // The source AST. final QueryRoot given = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); given.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); given.setWhereClause(whereClause); final StatementPatternNode spo1 = new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o1"), null/* c */, Scope.DEFAULT_CONTEXTS); final StatementPatternNode spo2 = new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o2"), null/* c */, Scope.DEFAULT_CONTEXTS); final JoinGroupNode jgn = new JoinGroupNode(spo2); final StatementPatternNode spo3 = new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o3"), null/* c */, Scope.DEFAULT_CONTEXTS); spo3.setOptional(true); whereClause.addChild(spo1); whereClause.addChild(jgn); whereClause.addChild(spo3); final FunctionNode filterBound1 = new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o1")}); final FunctionNode filterBound2 = new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o2")}); final FunctionNode filterBound3 = new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o3")}); final FunctionNode filterNotBound1 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o1")})); final FunctionNode filterNotBound2 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o2")})); final FunctionNode filterNotBound3 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o3")})); final FunctionNode filterNotBound4 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o4")})); whereClause.addChild(new FilterNode(filterBound1)); whereClause.addChild(new FilterNode(filterBound2)); whereClause.addChild(new FilterNode(filterBound3)); whereClause.addChild(new FilterNode(filterNotBound1)); whereClause.addChild(new FilterNode(filterNotBound2)); whereClause.addChild(new FilterNode(filterNotBound3)); whereClause.addChild(new FilterNode(filterNotBound4)); // add some duplicates (they should be removed) whereClause.addChild(new FilterNode(filterNotBound2)); whereClause.addChild(new FilterNode(filterNotBound3)); } // The expected AST after the rewrite. final QueryRoot expected = new QueryRoot(QueryType.SELECT); { final ProjectionNode projection = new ProjectionNode(); expected.setProjection(projection); projection.addProjectionVar(new VarNode("s")); final JoinGroupNode whereClause = new JoinGroupNode(); whereClause.setProperty(QueryHints.NORMALIZE_FILTER_EXPRESSIONS, "true"); expected.setWhereClause(whereClause); final StatementPatternNode spo1 = new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o1"), null/* c */, Scope.DEFAULT_CONTEXTS); final StatementPatternNode spo2 = new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o2"), null/* c */, Scope.DEFAULT_CONTEXTS); final JoinGroupNode jgn = new JoinGroupNode(spo2); final StatementPatternNode spo3 = new StatementPatternNode(new VarNode("s"), new VarNode("p"), new VarNode("o3"), null/* c */, Scope.DEFAULT_CONTEXTS); spo3.setOptional(true); whereClause.addChild(spo1); whereClause.addChild(jgn); whereClause.addChild(spo3); final FunctionNode filterBound3 = new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o3")}); final FunctionNode filterNotBound1 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o1")})); final FunctionNode filterNotBound2 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o2")})); final FunctionNode filterNotBound3 = FunctionNode.NOT( new FunctionNode( FunctionRegistry.BOUND, null/* scalarValues */, new ValueExpressionNode[] { new VarNode("o3")})); whereClause.addChild(new FilterNode(filterBound3)); whereClause.addChild(new FilterNode(filterNotBound1)); whereClause.addChild(new FilterNode(filterNotBound2)); whereClause.addChild(new FilterNode(filterNotBound3)); } final AST2BOpContext context = new AST2BOpContext(new ASTContainer(given), store); final ASTFilterNormalizationOptimizer rewriter = new ASTFilterNormalizationOptimizer(); final IQueryNode actual = rewriter.optimize(context, new QueryNodeWithBindingSet(given, bsets)). getQueryNode(); assertSameAST(expected, actual); } }