/** 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 23, 2010 */ package com.bigdata.bop.controller; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; import com.bigdata.bop.BOp; import com.bigdata.bop.BOpEvaluationContext; import com.bigdata.bop.Constant; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IConstant; import com.bigdata.bop.IConstraint; import com.bigdata.bop.IPredicate.Annotations; import com.bigdata.bop.IVariable; import com.bigdata.bop.IVariableOrConstant; import com.bigdata.bop.NV; import com.bigdata.bop.PipelineOp; import com.bigdata.bop.Var; import com.bigdata.bop.ap.E; import com.bigdata.bop.ap.Predicate; import com.bigdata.bop.ap.R; import com.bigdata.bop.bindingSet.ListBindingSet; import com.bigdata.bop.bset.ConditionalRoutingOp; import com.bigdata.bop.bset.StartOp; import com.bigdata.bop.constraint.Constraint; import com.bigdata.bop.constraint.EQConstant; import com.bigdata.bop.constraint.NEConstant; import com.bigdata.bop.engine.AbstractQueryEngineTestCase; import com.bigdata.bop.engine.BOpStats; import com.bigdata.bop.engine.IRunningQuery; import com.bigdata.bop.engine.QueryEngine; import com.bigdata.bop.engine.StandaloneChunkHandler; import com.bigdata.bop.join.JoinTypeEnum; import com.bigdata.bop.join.PipelineJoin; import com.bigdata.bop.solutions.SliceOp; import com.bigdata.journal.BufferMode; import com.bigdata.journal.ITx; import com.bigdata.journal.Journal; import com.bigdata.striterator.ChunkedArrayIterator; /** * Test suite for handling of optional join groups during query evaluation * against a local database instance. * * <pre> * -Dlog4j.configuration=bigdata/src/resources/logging/log4j.properties * </pre> * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ public class TestSubqueryOp extends AbstractSubqueryTestCase { /** * */ public TestSubqueryOp() { } /** * @param name */ public TestSubqueryOp(String name) { super(name); } @Override public Properties getProperties() { final Properties p = new Properties(super.getProperties()); p.setProperty(Journal.Options.BUFFER_MODE, BufferMode.Transient .toString()); return p; } static private final String namespace = "ns"; private Journal jnl; private QueryEngine queryEngine; @Override public void setUp() throws Exception { jnl = new Journal(getProperties()); loadData(jnl); queryEngine = new QueryEngine(jnl); queryEngine.init(); } /** * Create and populate relation in the {@link #namespace}. */ private void loadData(final Journal store) { // create the relation. final R rel = new R(store, namespace, ITx.UNISOLATED, new Properties()); rel.create(); // data to insert (in key order for convenience). final E[] a = {// new E("Paul", "Mary"),// [0] new E("Paul", "Brad"),// [1] new E("John", "Mary"),// [2] new E("John", "Brad"),// [3] new E("Mary", "Brad"),// [4] new E("Brad", "Fred"),// [5] new E("Brad", "Leon"),// [6] }; // insert data (the records are not pre-sorted). rel.insert(new ChunkedArrayIterator<E>(a.length, a, null/* keyOrder */)); // Do commit since not scale-out. store.commit(); } @Override public void tearDown() throws Exception { if (queryEngine != null) { queryEngine.shutdownNow(); queryEngine = null; } if (jnl != null) { jnl.destroy(); jnl = null; } } /** * Unit test for a simple join. */ public void test_join() throws Exception { // final int startId = 1; final int joinId = 2; final int predId = 3; final int subqueryId = 4; final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); final Predicate<E> predOp = new Predicate<E>(// new IVariableOrConstant[] { // new Constant<String>("John"), x }, // NV.asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); // the subquery (basically, an access path read with "x" unbound). final PipelineJoin<E> subquery = new PipelineJoin<E>( new BOp[] { },// new NV(Predicate.Annotations.BOP_ID, joinId),// new NV(PipelineJoin.Annotations.PREDICATE, predOp)); // the hash-join against the subquery. final SubqueryOp subqueryOp = new SubqueryOp( new BOp[] {},// new NV(Predicate.Annotations.BOP_ID, subqueryId),// new NV(SubqueryOp.Annotations.SUBQUERY, subquery),// new NV(SubqueryOp.Annotations.JOIN_TYPE,JoinTypeEnum.Normal),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE)// ); final PipelineOp query = subqueryOp; // the expected solutions. final IBindingSet[] expected = new IBindingSet[] {// new ListBindingSet(// new IVariable[] { x },// new IConstant[] { new Constant<String>("Mary") }// ),// new ListBindingSet(// new IVariable[] { x, y },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Fred"), }// ),// }; /* * Setup the input binding sets. Each input binding set MUST provide * binding for the join variable(s). */ final IBindingSet[] initialBindingSets; { final List<IBindingSet> list = new LinkedList<IBindingSet>(); IBindingSet tmp; tmp = new ListBindingSet(); tmp.set(x, new Constant<String>("Brad")); tmp.set(y, new Constant<String>("Fred")); list.add(tmp); tmp = new ListBindingSet(); tmp.set(x, new Constant<String>("Mary")); list.add(tmp); initialBindingSets = list.toArray(new IBindingSet[0]); } final IRunningQuery runningQuery = queryEngine.eval(query, initialBindingSets); AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected, runningQuery); { final BOpStats stats = runningQuery.getStats().get( subqueryId); assertEquals(2L, stats.chunksIn.get()); assertEquals(2L, stats.unitsIn.get()); assertEquals(2L, stats.unitsOut.get()); assertEquals(2L, stats.chunksOut.get()); } { // // access path // assertEquals(0L, stats.accessPathDups.get()); // assertEquals(1L, stats.accessPathCount.get()); // assertEquals(1L, stats.accessPathChunksIn.get()); // assertEquals(2L, stats.accessPathUnitsIn.get()); } assertTrue(runningQuery.isDone()); assertFalse(runningQuery.isCancelled()); runningQuery.get(); // verify nothing thrown. } /** * Unit test for simple join with a constraint. */ public void test_joinWithConstraint() throws Exception { // final int startId = 1; final int joinId = 2; final int predId = 3; final int subqueryId = 4; final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); // final IConstant<String>[] set = new IConstant[] {// // new Constant<String>("Fred"),// // }; final Predicate<E> predOp = new Predicate<E>(// new IVariableOrConstant[] { // new Constant<String>("John"), x }, // NV.asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); // the subquery (basically, an access path read with "x" unbound). final PipelineJoin<E> subquery = new PipelineJoin<E>( new BOp[] { },// new NV(Predicate.Annotations.BOP_ID, joinId),// new NV(PipelineJoin.Annotations.PREDICATE, predOp)); // the hash-join against the subquery. final SubqueryOp subqueryOp = new SubqueryOp( new BOp[] {},// new NV(Predicate.Annotations.BOP_ID, subqueryId),// new NV(SubqueryOp.Annotations.SUBQUERY, subquery),// new NV(SubqueryOp.Annotations.JOIN_TYPE,JoinTypeEnum.Normal),// new NV(SubqueryOp.Annotations.CONSTRAINTS, new IConstraint[] { Constraint .wrap(new EQConstant(x, new Constant<String>("Brad"))),// }), new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE)// ); final PipelineOp query = subqueryOp; // the expected solutions. final IBindingSet[] expected = new IBindingSet[] {// // new ListBindingSet(// // new IVariable[] { x },// // new IConstant[] { new Constant<String>("Mary") }// // ),// new ListBindingSet(// new IVariable[] { x, y },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Fred"), }// ),// }; /* * Setup the input binding sets. Each input binding set MUST provide * binding for the join variable(s). */ final IBindingSet[] initialBindingSets; { final List<IBindingSet> list = new LinkedList<IBindingSet>(); IBindingSet tmp; tmp = new ListBindingSet(); tmp.set(x, new Constant<String>("Brad")); tmp.set(y, new Constant<String>("Fred")); list.add(tmp); tmp = new ListBindingSet(); tmp.set(x, new Constant<String>("Mary")); list.add(tmp); initialBindingSets = list.toArray(new IBindingSet[0]); } final IRunningQuery runningQuery = queryEngine.eval(query, initialBindingSets); AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected, runningQuery); { final BOpStats stats = runningQuery.getStats().get( subqueryId); assertEquals(2L, stats.chunksIn.get()); assertEquals(2L, stats.unitsIn.get()); assertEquals(1L, stats.unitsOut.get()); assertEquals(2L, stats.chunksOut.get()); } { // // access path // assertEquals(0L, stats.accessPathDups.get()); // assertEquals(1L, stats.accessPathCount.get()); // assertEquals(1L, stats.accessPathChunksIn.get()); // assertEquals(2L, stats.accessPathUnitsIn.get()); } assertTrue(runningQuery.isDone()); assertFalse(runningQuery.isCancelled()); runningQuery.get(); // verify nothing thrown. } /** * Unit test for a simple join in which only <code>x</code> is projected * into the subquery. */ public void test_join_selectOnly_x() throws Exception { // final int startId = 1; final int joinId = 2; final int predId = 3; final int subqueryId = 4; final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); final Predicate<E> predOp = new Predicate<E>(// new IVariableOrConstant[] { // new Constant<String>("John"), x }, // NV.asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); // the subquery (basically, an access path read with "x" unbound). final PipelineJoin<E> subquery = new PipelineJoin<E>( new BOp[] { },// new NV(Predicate.Annotations.BOP_ID, joinId),// new NV(PipelineJoin.Annotations.PREDICATE, predOp),// new NV(SubqueryOp.Annotations.JOIN_TYPE,JoinTypeEnum.Normal)// ); // the hash-join against the subquery. final SubqueryOp subqueryOp = new SubqueryOp( new BOp[] {},// new NV(Predicate.Annotations.BOP_ID, subqueryId),// new NV(SubqueryOp.Annotations.SELECT, new IVariable[]{x}),// new NV(SubqueryOp.Annotations.SUBQUERY, subquery),// new NV(SubqueryOp.Annotations.JOIN_TYPE, JoinTypeEnum.Normal),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE)// ); final PipelineOp query = subqueryOp; // the expected solutions. final IBindingSet[] expected = new IBindingSet[] {// new ListBindingSet(// new IVariable[] { x },// new IConstant[] { new Constant<String>("Mary") }// ),// new ListBindingSet(// new IVariable[] { x, y },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Fred"), }// ),// }; /* * Setup the input binding sets. Each input binding set MUST provide * binding for the join variable(s). */ final IBindingSet[] initialBindingSets; { final List<IBindingSet> list = new LinkedList<IBindingSet>(); IBindingSet tmp; tmp = new ListBindingSet(); tmp.set(x, new Constant<String>("Brad")); tmp.set(y, new Constant<String>("Fred")); list.add(tmp); tmp = new ListBindingSet(); tmp.set(x, new Constant<String>("Mary")); list.add(tmp); initialBindingSets = list.toArray(new IBindingSet[0]); } final IRunningQuery runningQuery = queryEngine.eval(query, initialBindingSets); AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected, runningQuery); { final BOpStats stats = runningQuery.getStats().get( subqueryId); assertEquals(2L, stats.chunksIn.get()); assertEquals(2L, stats.unitsIn.get()); assertEquals(2L, stats.unitsOut.get()); assertEquals(2L, stats.chunksOut.get()); } { // // access path // assertEquals(0L, stats.accessPathDups.get()); // assertEquals(1L, stats.accessPathCount.get()); // assertEquals(1L, stats.accessPathChunksIn.get()); // assertEquals(2L, stats.accessPathUnitsIn.get()); } assertTrue(runningQuery.isDone()); assertFalse(runningQuery.isCancelled()); runningQuery.get(); // verify nothing thrown. } /** * Unit test for optional join group. Three joins are used and target a * {@link SliceOp}. The 2nd and 3rd joins are embedded in an * {@link SubqueryOp}. * <P> * The optional join group takes the form: * * <pre> * (a b) * optional { * (b c) * (c d) * } * </pre> * * The (a b) tail will match everything in the knowledge base. The join * group takes us two hops out from ?b. There should be four solutions that * succeed the optional join group: * * <pre> * (paul mary brad fred) * (paul mary brad leon) * (john mary brad fred) * (john mary brad leon) * </pre> * * and five more that don't succeed the optional join group: * * <pre> * (paul brad) * * (john brad) * * (mary brad) * * (brad fred) * (brad leon) * </pre> * * In this cases marked with a <code>*</code>, ?c will become temporarily * bound to fred and leon (since brad knows fred and leon), but the (c d) * tail will fail since fred and leon don't know anyone else. At this point, * the ?c binding must be removed from the solution. */ public void test_query_join2_optionals() throws Exception { // main query final int startId = 1; // final int joinId1 = 2; // : base join group. final int predId1 = 3; // (a b) final int joinGroup1 = 9; final int sliceId = 8; // // subquery final int joinId2 = 4; // : joinGroup1 final int predId2 = 5; // (b c) final int joinId3 = 6; // : joinGroup1 final int predId3 = 7; // (c d) final IVariable<?> a = Var.var("a"); final IVariable<?> b = Var.var("b"); final IVariable<?> c = Var.var("c"); final IVariable<?> d = Var.var("d"); final PipelineOp startOp = new StartOp(new BOp[] {}, NV.asMap(new NV[] {// new NV(Predicate.Annotations.BOP_ID, startId),// new NV(SliceOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// })); final Predicate<?> pred1Op = new Predicate<E>( new IVariableOrConstant[] { a, b }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId1),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final Predicate<?> pred2Op = new Predicate<E>( new IVariableOrConstant[] { b, c }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId2),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final Predicate<?> pred3Op = new Predicate<E>( new IVariableOrConstant[] { c, d }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId3),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final PipelineOp join1Op = new PipelineJoin<E>(// new BOp[]{startOp},// new NV(Predicate.Annotations.BOP_ID, joinId1),// new NV(PipelineJoin.Annotations.PREDICATE,pred1Op)); final PipelineOp subQuery; { final PipelineOp join2Op = new PipelineJoin<E>(// new BOp[] { /*join1Op*/ },// new NV(Predicate.Annotations.BOP_ID, joinId2),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(PipelineJoin.Annotations.PREDICATE, pred2Op)// // // join is optional. // new NV(PipelineJoin.Annotations.OPTIONAL, true),// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); final PipelineOp join3Op = new PipelineJoin<E>(// new BOp[] { join2Op },// new NV(Predicate.Annotations.BOP_ID, joinId3),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(PipelineJoin.Annotations.PREDICATE, pred3Op),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE)// // // join is optional. // new NV(PipelineJoin.Annotations.OPTIONAL, true),// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); subQuery = join3Op; } final PipelineOp joinGroup1Op = new SubqueryOp(new BOp[]{join1Op}, new NV(Predicate.Annotations.BOP_ID, joinGroup1),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(SubqueryOp.Annotations.SUBQUERY, subQuery),// // , new NV(BOp.Annotations.CONTROLLER,true)// // new NV(BOp.Annotations.EVALUATION_CONTEXT, // BOpEvaluationContext.CONTROLLER)// // join is optional. new NV(SubqueryOp.Annotations.JOIN_TYPE, JoinTypeEnum.Optional)// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); final PipelineOp sliceOp = new SliceOp(// new BOp[]{joinGroup1Op}, NV.asMap(new NV[] {// new NV(BOp.Annotations.BOP_ID, sliceId),// new NV(BOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// new NV(PipelineOp.Annotations.SHARED_STATE,true),// new NV(PipelineOp.Annotations.REORDER_SOLUTIONS,false),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE),// })); final PipelineOp query = sliceOp; // start the query. final UUID queryId = UUID.randomUUID(); // final IChunkMessage<IBindingSet> initialChunkMessage; final IBindingSet initialBindings = new ListBindingSet(); { // final IBindingSet initialBindings = new HashBindingSet(); // initialBindings.set(Var.var("x"), new Constant<String>("Mary")); // initialChunkMessage = new LocalChunkMessage<IBindingSet>(queryEngine, // queryId, startId,// // -1, // partitionId // newBindingSetIterator(initialBindings)); } final IRunningQuery runningQuery = queryEngine.eval(queryId, query, initialBindings); // initialChunkMessage); // verify solutions. { // the expected solutions. final IBindingSet[] expected = new IBindingSet[] {// // four solutions where the optional join succeeds. new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Fred") }// ), new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Leon") }// ), new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Fred") }// ), new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Leon") }// ), // plus anything we read from the first access path which did not // pass the optional join new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Mary"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Fred") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Leon") }// ) }; /* * junit.framework.AssertionFailedError: Iterator will deliver too * many objects: reminder(3)=[{ a=John, b=Brad }, { a=Mary, b=Brad * }, { a=Paul, b=Brad }]. */ AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,runningQuery); } // // Wait until the query is done. // runningQuery.get(); final Map<Integer, BOpStats> statsMap = runningQuery.getStats(); { // validate the stats map. assertNotNull(statsMap); assertEquals(4, statsMap.size()); if (log.isInfoEnabled()) log.info(statsMap.toString()); } } /** * Unit test for optional join group with a filter. Three joins are used and * target a {@link SliceOp}. The 2nd and 3rd joins are embedded in an * optional join group. The optional join group contains a filter. * <p> * The optional join group takes the form: * * <pre> * (a b) * optional { * (b c) * (c d) * filter(d != Leon) * } * </pre> * * The (a b) tail will match everything in the knowledge base. The join * group takes us two hops out from ?b. There should be two solutions that * succeed the optional join group: * * <pre> * (paul mary brad fred) * (john mary brad fred) * </pre> * * and five more that don't succeed the optional join group: * * <pre> * (paul brad) * * (john brad) * * (mary brad) * * (brad fred) * (brad leon) * </pre> * * In the cases marked with a <code>*</code>, ?c will become temporarily * bound to fred and leon (since brad knows fred and leon), but the (c d) * tail will fail since fred and leon don't know anyone else. At this point, * the ?c binding must be removed from the solution. * <p> * The filter (d != Leon) will prune the two solutions: * * <pre> * (paul mary brad leon) * (john mary brad leon) * </pre> * * since ?d is bound to Leon in those cases. */ public void test_query_optionals_filter() throws Exception { // main query final int startId = 1; final int joinId1 = 2; // final int predId1 = 3; // (a,b) final int joinGroup1 = 9; final int sliceId = 8; // subquery final int joinId2 = 4; // : group1 final int predId2 = 5; // (b,c) final int joinId3 = 6; // : group1 final int predId3 = 7; // (c,d) final IVariable<?> a = Var.var("a"); final IVariable<?> b = Var.var("b"); final IVariable<?> c = Var.var("c"); final IVariable<?> d = Var.var("d"); final PipelineOp startOp = new StartOp(new BOp[] {}, NV.asMap(new NV[] {// new NV(Predicate.Annotations.BOP_ID, startId),// new NV(SliceOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// })); final Predicate<?> pred1Op = new Predicate<E>( new IVariableOrConstant[] { a, b }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId1),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final Predicate<?> pred2Op = new Predicate<E>( new IVariableOrConstant[] { b, c }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId2),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final Predicate<?> pred3Op = new Predicate<E>( new IVariableOrConstant[] { c, d }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId3),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final PipelineOp join1Op = new PipelineJoin<E>(// new BOp[]{startOp},// new NV(Predicate.Annotations.BOP_ID, joinId1),// new NV(PipelineJoin.Annotations.PREDICATE,pred1Op)); final PipelineOp subQuery; { final PipelineOp join2Op = new PipelineJoin<E>(// new BOp[] { /*join1Op*/ },// new NV(Predicate.Annotations.BOP_ID, joinId2),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(PipelineJoin.Annotations.PREDICATE, pred2Op)// // // join is optional. // new NV(PipelineJoin.Annotations.OPTIONAL, true),// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); final PipelineOp join3Op = new PipelineJoin<E>(// new BOp[] { join2Op },// new NV(Predicate.Annotations.BOP_ID, joinId3),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(PipelineJoin.Annotations.PREDICATE, pred3Op),// // constraint d != Leon new NV(PipelineJoin.Annotations.CONSTRAINTS, new IConstraint[] { Constraint.wrap(new NEConstant(d, new Constant<String>("Leon"))) }), new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE)// // // join is optional. // new NV(PipelineJoin.Annotations.OPTIONAL, true),// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); subQuery = join3Op; } final PipelineOp joinGroup1Op = new SubqueryOp(new BOp[]{join1Op}, new NV(Predicate.Annotations.BOP_ID, joinGroup1),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(SubqueryOp.Annotations.SUBQUERY, subQuery),// // new NV(BOp.Annotations.CONTROLLER,true)// // new NV(BOp.Annotations.EVALUATION_CONTEXT, // BOpEvaluationContext.CONTROLLER)// // join is optional. new NV(SubqueryOp.Annotations.JOIN_TYPE, JoinTypeEnum.Optional)// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); final PipelineOp sliceOp = new SliceOp(// new BOp[]{joinGroup1Op}, NV.asMap(new NV[] {// new NV(BOp.Annotations.BOP_ID, sliceId),// new NV(BOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// new NV(PipelineOp.Annotations.SHARED_STATE,true),// new NV(PipelineOp.Annotations.REORDER_SOLUTIONS,false),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE),// })); final PipelineOp query = sliceOp; // start the query. final UUID queryId = UUID.randomUUID(); // final IChunkMessage<IBindingSet> initialChunkMessage; final IBindingSet initialBindings = new ListBindingSet(); { // final IBindingSet initialBindings = new HashBindingSet(); // initialBindings.set(Var.var("x"), new Constant<String>("Mary")); // initialChunkMessage = new LocalChunkMessage<IBindingSet>(queryEngine, // queryId, startId,// // -1, // partitionId // newBindingSetIterator(initialBindings)); } final IRunningQuery runningQuery = queryEngine.eval(queryId, query, initialBindings); // initialChunkMessage); // verify solutions. { // the expected solutions. final IBindingSet[] expected = new IBindingSet[] {// // two solutions where the optional join succeeds. new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Fred") }// ), new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Fred") }// ), // plus anything we read from the first access path which did not // pass the optional join new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Mary"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Fred") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Leon") }// ) }; AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected, runningQuery); } // Wait until the query is done. runningQuery.get(); final Map<Integer, BOpStats> statsMap = runningQuery.getStats(); { // validate the stats map. assertNotNull(statsMap); assertEquals(4, statsMap.size()); if (log.isInfoEnabled()) log.info(statsMap.toString()); } } /** * Unit test for optional join group with a filter on a variable outside the * optional join group. Three joins are used and target a {@link SliceOp}. * The 2nd and 3rd joins are in embedded an {@link SubqueryOp}. The * optional join group contains a filter that uses a variable outside the * optional join group. * <P> * The query takes the form: * * <pre> * (a b) * optional { * (b c) * (c d) * filter(a != Paul) * } * </pre> * * The (a b) tail will match everything in the knowledge base. The join * group takes us two hops out from ?b. There should be two solutions that * succeed the optional join group: * * <pre> * (john mary brad fred) * (john mary brad leon) * </pre> * * and six more that don't succeed the optional join group: * * <pre> * (paul mary) * * (paul brad) * * (john brad) * (mary brad) * (brad fred) * (brad leon) * </pre> * * In the cases marked with a <code>*</code>, ?a is bound to Paul even * though there is a filter that specifically prohibits a = Paul. This is * because the filter is inside the optional join group, which means that * solutions can still include a = Paul, but the optional join group should * not run in that case. */ public void test_query_optionals_filter2() throws Exception { // main query final int startId = 1; final int joinId1 = 2; final int predId1 = 3; // (a,b) final int condId = 4; // (a != Paul) final int joinGroup1 = 10; final int sliceId = 9; // subquery (iff condition is satisfied) final int joinId2 = 5; // : group1 final int predId2 = 6; // (b,c) final int joinId3 = 7; // : group1 final int predId3 = 8; // (c,d) final IVariable<?> a = Var.var("a"); final IVariable<?> b = Var.var("b"); final IVariable<?> c = Var.var("c"); final IVariable<?> d = Var.var("d"); // final Integer joinGroup1 = Integer.valueOf(1); /* * Not quite sure how to write this one. I think it probably goes * something like this: * * 1. startOp * 2. join1Op(a b) * 3. conditionalRoutingOp( if a = Paul then goto sliceOp ) * 4. join2Op(b c) * 5. join3Op(c d) * 6. sliceOp */ final PipelineOp startOp = new StartOp(new BOp[] {}, NV.asMap(new NV[] {// new NV(Predicate.Annotations.BOP_ID, startId),// new NV(SliceOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// })); final Predicate<?> pred1Op = new Predicate<E>( new IVariableOrConstant[] { a, b }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId1),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final Predicate<?> pred2Op = new Predicate<E>( new IVariableOrConstant[] { b, c }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId2),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final Predicate<?> pred3Op = new Predicate<E>( new IVariableOrConstant[] { c, d }, NV .asMap(new NV[] {// new NV(Predicate.Annotations.RELATION_NAME, new String[] { namespace }),// new NV(Predicate.Annotations.BOP_ID, predId3),// new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// })); final PipelineOp join1Op = new PipelineJoin<E>(// new BOp[]{startOp},// new NV(Predicate.Annotations.BOP_ID, joinId1),// new NV(PipelineJoin.Annotations.PREDICATE,pred1Op)); final IConstraint condition = Constraint.wrap(new NEConstant(a, new Constant<String>("Paul"))); final ConditionalRoutingOp condOp = new ConditionalRoutingOp(new BOp[]{join1Op}, NV.asMap(new NV[]{// new NV(BOp.Annotations.BOP_ID,condId), new NV(PipelineOp.Annotations.SINK_REF, joinGroup1), // a != Paul new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId), // a == Paul new NV(ConditionalRoutingOp.Annotations.CONDITION, condition), })); final PipelineOp subQuery; { final PipelineOp join2Op = new PipelineJoin<E>(// new BOp[] { /*condOp*/ },// new NV(Predicate.Annotations.BOP_ID, joinId2),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(PipelineJoin.Annotations.PREDICATE, pred2Op)// // // join is optional. // new NV(PipelineJoin.Annotations.OPTIONAL, true),// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); final PipelineOp join3Op = new PipelineJoin<E>(// new BOp[] { join2Op },// new NV(Predicate.Annotations.BOP_ID, joinId3),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(PipelineJoin.Annotations.PREDICATE, pred3Op),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE)// // // join is optional. // new NV(PipelineJoin.Annotations.OPTIONAL, true),// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); subQuery = join3Op; } final PipelineOp joinGroup1Op = new SubqueryOp(new BOp[]{condOp}, new NV(Predicate.Annotations.BOP_ID, joinGroup1),// // new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// new NV(SubqueryOp.Annotations.SUBQUERY, subQuery),// // new NV(BOp.Annotations.CONTROLLER,true)// // new NV(BOp.Annotations.EVALUATION_CONTEXT, // BOpEvaluationContext.CONTROLLER)// // join is optional. new NV(SubqueryOp.Annotations.JOIN_TYPE, JoinTypeEnum.Optional)// // // optional target is the same as the default target. // new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) ); final PipelineOp sliceOp = new SliceOp(// new BOp[]{joinGroup1Op}, NV.asMap(new NV[] {// new NV(BOp.Annotations.BOP_ID, sliceId),// new NV(BOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// new NV(PipelineOp.Annotations.SHARED_STATE,true),// new NV(PipelineOp.Annotations.REORDER_SOLUTIONS,false),// new NV(QueryEngine.Annotations.CHUNK_HANDLER, StandaloneChunkHandler.TEST_INSTANCE),// })); final PipelineOp query = sliceOp; // start the query. final UUID queryId = UUID.randomUUID(); // final IChunkMessage<IBindingSet> initialChunkMessage; final IBindingSet initialBindings = new ListBindingSet(); { // final IBindingSet initialBindings = new HashBindingSet(); // initialBindings.set(Var.var("x"), new Constant<String>("Mary")); // initialChunkMessage = new LocalChunkMessage<IBindingSet>(queryEngine, // queryId, startId,// // -1, // partitionId // newBindingSetIterator(initialBindings)); } final IRunningQuery runningQuery = queryEngine.eval(queryId, query, initialBindings); // initialChunkMessage); // verify solutions. { // the expected solutions. final IBindingSet[] expected = new IBindingSet[] {// // two solutions where the optional join succeeds. new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Leon") }// ), new ListBindingSet(// new IVariable[] { a, b, c, d },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Mary"), new Constant<String>("Brad"), new Constant<String>("Fred") }// ), // plus anything we read from the first access path which did not // pass the optional join new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Mary") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Paul"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("John"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Mary"), new Constant<String>("Brad") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Fred") }// ), new ListBindingSet(// new IVariable[] { a, b },// new IConstant[] { new Constant<String>("Brad"), new Constant<String>("Leon") }// ) }; AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected, runningQuery); } // // Wait until the query is done. // runningQuery.get(); final Map<Integer, BOpStats> statsMap = runningQuery.getStats(); { // validate the stats map. assertNotNull(statsMap); assertEquals(5, statsMap.size()); if (log.isInfoEnabled()) log.info(statsMap.toString()); } } }