/**
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 Oct 27, 2011
*/
package com.bigdata.bop.join;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import junit.framework.TestCase2;
import com.bigdata.bop.BOp;
import com.bigdata.bop.BOpContext;
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.Predicate;
import com.bigdata.bop.bindingSet.ListBindingSet;
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.BlockingBufferWithStats;
import com.bigdata.bop.engine.MockRunningQuery;
import com.bigdata.bop.solutions.MockQueryContext;
import com.bigdata.journal.BufferMode;
import com.bigdata.journal.ITx;
import com.bigdata.journal.Journal;
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.model.StatementEnum;
import com.bigdata.rdf.spo.SPO;
import com.bigdata.rdf.store.AbstractTripleStore;
import com.bigdata.rdf.store.LocalTripleStore;
import com.bigdata.rdf.vocab.decls.FOAFVocabularyDecl;
import com.bigdata.relation.accesspath.IAsynchronousIterator;
import com.bigdata.relation.accesspath.IBlockingBuffer;
import com.bigdata.relation.accesspath.ThickAsynchronousIterator;
/**
* Common base class for hash join with access path unit tests.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id: AbstractHashJoinOpTestCase.java 5499 2011-11-03 19:49:10Z
* thompsonbry $
*/
@SuppressWarnings("rawtypes")
abstract public class AbstractHashJoinOpTestCase extends TestCase2 {
/**
*
*/
public AbstractHashJoinOpTestCase() {
}
/**
* @param name
*/
public AbstractHashJoinOpTestCase(String name) {
super(name);
}
/**
* Setup for a problem used by many of the join test suites.
*/
static public class JoinSetup {
protected final String spoNamespace;
protected final IV<?, ?> knows, brad, john, fred, mary, paul, leon, luke;
private Journal jnl;
public JoinSetup(final String kbNamespace) {
if (kbNamespace == null)
throw new IllegalArgumentException();
final Properties properties = new Properties();
properties.setProperty(Journal.Options.BUFFER_MODE,
BufferMode.Transient.toString());
jnl = new Journal(properties);
// create the kb.
final AbstractTripleStore kb = new LocalTripleStore(jnl,
kbNamespace, ITx.UNISOLATED, properties);
kb.create();
this.spoNamespace = kb.getSPORelation().getNamespace();
// Setup the vocabulary.
{
final BigdataValueFactory vf = kb.getValueFactory();
final String uriString = "http://bigdata.com/";
final BigdataURI _knows = vf.asValue(FOAFVocabularyDecl.knows);
final BigdataURI _brad = vf.createURI(uriString+"brad");
final BigdataURI _john = vf.createURI(uriString+"john");
final BigdataURI _fred = vf.createURI(uriString+"fred");
final BigdataURI _mary = vf.createURI(uriString+"mary");
final BigdataURI _paul = vf.createURI(uriString+"paul");
final BigdataURI _leon = vf.createURI(uriString+"leon");
final BigdataURI _luke = vf.createURI(uriString+"luke");
final BigdataValue[] a = new BigdataValue[] {
_knows,//
_brad,
_john,
_fred,
_mary,
_paul,
_leon,
_luke
};
kb.getLexiconRelation()
.addTerms(a, a.length, false/* readOnly */);
knows = _knows.getIV();
brad = _brad.getIV();
john = _john.getIV();
fred = _fred.getIV();
mary = _mary.getIV();
paul = _paul.getIV();
leon = _leon.getIV();
luke = _luke.getIV();
}
// data to insert (in key order for convenience).
final SPO[] a = {//
new SPO(paul, knows, mary, StatementEnum.Explicit),// [0]
new SPO(paul, knows, brad, StatementEnum.Explicit),// [1]
new SPO(john, knows, mary, StatementEnum.Explicit),// [2]
new SPO(john, knows, brad, StatementEnum.Explicit),// [3]
new SPO(mary, knows, brad, StatementEnum.Explicit),// [4]
new SPO(brad, knows, fred, StatementEnum.Explicit),// [5]
new SPO(brad, knows, leon, StatementEnum.Explicit),// [6]
};
// insert data (the records are not pre-sorted).
kb.addStatements(a, a.length);
// Do commit since not scale-out.
jnl.commit();
}
protected void destroy() {
if (jnl != null) {
jnl.destroy();
jnl = null;
}
}
}
protected JoinSetup setup = null;
public void setUp() throws Exception {
setup = new JoinSetup(getName());
}
public void tearDown() throws Exception {
if (setup != null) {
setup.destroy();
setup = null;
}
}
/**
* Return an {@link IAsynchronousIterator} that will read a single
* {@link IBindingSet}.
*
* @param bindingSet
* the binding set.
*/
protected ThickAsynchronousIterator<IBindingSet[]> newBindingSetIterator(
final IBindingSet bindingSet) {
return new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { new IBindingSet[] { bindingSet } });
}
/**
* Return a new join operator instance for the test.
*
* @param args
* @param joinId
* @param joinVars
* @param predOp
* @param queryId
* @param annotations
* @return
*/
abstract protected PipelineOp newJoin(final BOp[] args, final int joinId,
final IVariable<IV>[] joinVars,
final Predicate<IV> predOp,
final UUID queryId,
final NV... annotations);
/*
* Tests
*/
/**
* Unit test for a simple join. There are two source solutions. Each binds
* the join variable (there is only one join variable, which is [x]). The
* access path is run once and visits two elements, yielding as-bound
* solutions. The hash map containing the buffered source solutions is
* probed and the as-bound solutions which join are written out.
*/
public void test_join_simple()
throws InterruptedException, ExecutionException {
final int joinId = 2;
final int predId = 3;
@SuppressWarnings("unchecked")
final IVariable<IV> x = Var.var("x");
@SuppressWarnings("unchecked")
final IVariable<IV> y = Var.var("y");
@SuppressWarnings("unchecked")
final IVariable<IV>[] joinVars = new IVariable[] { x };
final UUID queryId = UUID.randomUUID();
final Predicate<IV> predOp = new Predicate<IV>(
new IVariableOrConstant[] { new Constant<IV>(setup.john),
new Constant<IV>(setup.knows), x }, NV.asMap(new NV[] {//
new NV(Predicate.Annotations.RELATION_NAME,
new String[] { setup.spoNamespace }),//
new NV(Predicate.Annotations.BOP_ID, predId),//
new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),//
}));
final PipelineOp query = newJoin(new BOp[] {}, joinId, joinVars,
predOp, queryId);
// the expected solutions.
final IBindingSet[] expected = new IBindingSet[] {//
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.mary) }//
),//
new ListBindingSet(//
new IVariable[] { x, y },//
new IConstant[] { new Constant<IV>(setup.brad),
new Constant<IV>(setup.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<IV>(setup.brad));
tmp.set(y, new Constant<IV>(setup.fred));
list.add(tmp);
tmp = new ListBindingSet();
tmp.set(x, new Constant<IV>(setup.mary));
list.add(tmp);
initialBindingSets = list.toArray(new IBindingSet[0]);
}
final MockQueryContext queryContext = new MockQueryContext(queryId);
try {
final BaseJoinStats stats = (BaseJoinStats) query.newStats();
final IAsynchronousIterator<IBindingSet[]> source = new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { initialBindingSets });
final IBlockingBuffer<IBindingSet[]> sink = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
/*
* Note: Since the operator relies on the isLastInvocation() test to
* run the hash join, this is signal is required in order to have
* the operator produce output. Otherwise it will just buffer the
* source solutions until it exceeds its memory budget.
*/
final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>(
new MockRunningQuery(null/* fed */,
setup.jnl/* indexManager */, queryContext),
-1/* partitionId */, stats, query/* op */,
true/* lastInvocation */, source, sink, null/* sink2 */);
// context.setLastInvocation();
// get task.
final FutureTask<Void> ft = query.eval(context);
// execute task.
setup.jnl.getExecutorService().execute(ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,
sink.iterator(), ft);
// join task
assertEquals(1L, stats.chunksIn.get());
assertEquals(2L, stats.unitsIn.get());
assertEquals(2L, stats.unitsOut.get());
assertEquals(1L, 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());
} finally {
queryContext.close();
}
}
/**
* Unit test for a simple join. There are two source solutions. Each binds
* the join variable (there is only one join variable, which is [x]). The
* access path is run once and visits two elements, yielding as-bound
* solutions. The hash map containing the buffered source solutions is
* probed and the as-bound solutions which join are written out.
* <p>
* For this variant, there are no join variables. We should get exactly the
* same solutions but the join will do more work.
*/
public void test_join_simple_noJoinVars()
throws InterruptedException, ExecutionException {
final int joinId = 2;
final int predId = 3;
@SuppressWarnings("unchecked")
final IVariable<IV> x = Var.var("x");
@SuppressWarnings("unchecked")
final IVariable<IV> y = Var.var("y");
@SuppressWarnings("unchecked")
final IVariable<IV>[] joinVars = new IVariable[] { /* x */};
final UUID queryId = UUID.randomUUID();
final Predicate<IV> predOp = new Predicate<IV>(
new IVariableOrConstant[] { new Constant<IV>(setup.john),
new Constant<IV>(setup.knows), x }, NV.asMap(new NV[] {//
new NV(Predicate.Annotations.RELATION_NAME,
new String[] { setup.spoNamespace }),//
new NV(Predicate.Annotations.BOP_ID, predId),//
new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),//
}));
final PipelineOp query = newJoin(new BOp[] {}, joinId, joinVars,
predOp, queryId);
// the expected solutions.
final IBindingSet[] expected = new IBindingSet[] {//
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.mary) }//
),//
new ListBindingSet(//
new IVariable[] { x, y },//
new IConstant[] { new Constant<IV>(setup.brad),
new Constant<IV>(setup.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<IV>(setup.brad));
tmp.set(y, new Constant<IV>(setup.fred));
list.add(tmp);
tmp = new ListBindingSet();
tmp.set(x, new Constant<IV>(setup.mary));
list.add(tmp);
initialBindingSets = list.toArray(new IBindingSet[0]);
}
final MockQueryContext queryContext = new MockQueryContext(queryId);
try {
final BaseJoinStats stats = (BaseJoinStats) query.newStats();
final IAsynchronousIterator<IBindingSet[]> source = new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { initialBindingSets });
final IBlockingBuffer<IBindingSet[]> sink = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
/*
* Note: Since the operator relies on the isLastInvocation() test to
* run the hash join, this is signal is required in order to have
* the operator produce output. Otherwise it will just buffer the
* source solutions until it exceeds its memory budget.
*/
final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>(
new MockRunningQuery(null/* fed */,
setup.jnl/* indexManager */, queryContext),
-1/* partitionId */, stats, query/* op */,
true/* lastInvocation */, source, sink, null/* sink2 */);
// context.setLastInvocation();
// get task.
final FutureTask<Void> ft = query.eval(context);
// execute task.
setup.jnl.getExecutorService().execute(ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,
sink.iterator(), ft);
// join task
assertEquals(1L, stats.chunksIn.get());
assertEquals(2L, stats.unitsIn.get());
assertEquals(2L, stats.unitsOut.get());
assertEquals(1L, 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());
} finally {
queryContext.close();
}
}
public void test_join_simple_withConstraint()
throws InterruptedException, ExecutionException {
final int joinId = 2;
final int predId = 3;
@SuppressWarnings("unchecked")
final IVariable<IV> x = Var.var("x");
@SuppressWarnings("unchecked")
final IVariable<IV> y = Var.var("y");
@SuppressWarnings("unchecked")
final IVariable<IV>[] joinVars = new IVariable[] { x };
final UUID queryId = UUID.randomUUID();
final Predicate<IV> predOp = new Predicate<IV>(
new IVariableOrConstant[] { new Constant<IV>(setup.john),
new Constant<IV>(setup.knows), x }, NV.asMap(new NV[] {//
new NV(Predicate.Annotations.RELATION_NAME,
new String[] { setup.spoNamespace }),//
new NV(Predicate.Annotations.BOP_ID, predId),//
new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),//
}));
final PipelineOp query = newJoin(new BOp[] {}, joinId, joinVars, predOp,
queryId,//
new NV(JoinAnnotations.CONSTRAINTS,
new IConstraint[] { Constraint
.wrap(new EQConstant(x, new Constant<IV>(setup.brad))),//
}));
// 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<IV>(setup.brad),
new Constant<IV>(setup.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<IV>(setup.brad));
tmp.set(y, new Constant<IV>(setup.fred));
list.add(tmp);
tmp = new ListBindingSet();
tmp.set(x, new Constant<IV>(setup.mary));
list.add(tmp);
initialBindingSets = list.toArray(new IBindingSet[0]);
}
final MockQueryContext queryContext = new MockQueryContext(queryId);
try {
final BaseJoinStats stats = (BaseJoinStats) query.newStats();
final IAsynchronousIterator<IBindingSet[]> source = new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { initialBindingSets });
final IBlockingBuffer<IBindingSet[]> sink = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
/*
* Note: Since the operator relies on the isLastInvocation() test to
* run the hash join, this is signal is required in order to have
* the operator produce output. Otherwise it will just buffer the
* source solutions until it exceeds its memory budget.
*/
final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>(
new MockRunningQuery(null/* fed */,
setup.jnl/* indexManager */, queryContext),
-1/* partitionId */, stats, query/* op */,
true/* lastInvocation */, source, sink, null/* sink2 */);
// context.setLastInvocation();
// get task.
final FutureTask<Void> ft = query.eval(context);
// execute task.
setup.jnl.getExecutorService().execute(ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,
sink.iterator(), ft);
// join task
assertEquals(1L, stats.chunksIn.get());
assertEquals(2L, stats.unitsIn.get());
assertEquals(1L, stats.unitsOut.get());
assertEquals(1L, 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());
} finally {
queryContext.close();
}
}
public void test_join_simple_selectOnly_x()
throws InterruptedException, ExecutionException {
final int joinId = 2;
final int predId = 3;
@SuppressWarnings("unchecked")
final IVariable<IV> x = Var.var("x");
@SuppressWarnings("unchecked")
final IVariable<IV> y = Var.var("y");
@SuppressWarnings("unchecked")
final IVariable<IV>[] joinVars = new IVariable[] { x };
final UUID queryId = UUID.randomUUID();
final Predicate<IV> predOp = new Predicate<IV>(
new IVariableOrConstant[] { new Constant<IV>(setup.john),
new Constant<IV>(setup.knows), x }, NV.asMap(new NV[] {//
new NV(Predicate.Annotations.RELATION_NAME,
new String[] { setup.spoNamespace }),//
new NV(Predicate.Annotations.BOP_ID, predId),//
new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),//
}));
final PipelineOp query = newJoin(new BOp[] {}, joinId, joinVars,
predOp, queryId, //
new NV(JoinAnnotations.SELECT,
new IVariable[] { x })//
);
// the expected solutions.
final IBindingSet[] expected = new IBindingSet[] {//
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.mary) }//
),//
new ListBindingSet(//
new IVariable[] { x/*, y*/ },//
new IConstant[] { new Constant<IV>(setup.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<IV>(setup.brad));
tmp.set(y, new Constant<IV>(setup.fred));
list.add(tmp);
tmp = new ListBindingSet();
tmp.set(x, new Constant<IV>(setup.mary));
list.add(tmp);
initialBindingSets = list.toArray(new IBindingSet[0]);
}
final MockQueryContext queryContext = new MockQueryContext(queryId);
try {
final BaseJoinStats stats = (BaseJoinStats) query.newStats();
final IAsynchronousIterator<IBindingSet[]> source = new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { initialBindingSets });
final IBlockingBuffer<IBindingSet[]> sink = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
/*
* Note: Since the operator relies on the isLastInvocation() test to
* run the hash join, this is signal is required in order to have
* the operator produce output. Otherwise it will just buffer the
* source solutions until it exceeds its memory budget.
*/
final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>(
new MockRunningQuery(null/* fed */,
setup.jnl/* indexManager */, queryContext),
-1/* partitionId */, stats, query/* op */,
true/* lastInvocation */, source, sink, null/* sink2 */);
// context.setLastInvocation();
// get task.
final FutureTask<Void> ft = query.eval(context);
// execute task.
setup.jnl.getExecutorService().execute(ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,
sink.iterator(), ft);
// join task
assertEquals(1L, stats.chunksIn.get());
assertEquals(2L, stats.unitsIn.get());
assertEquals(2L, stats.unitsOut.get());
assertEquals(1L, 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());
} finally {
queryContext.close();
}
}
/**
* Unit tests for optional joins, including a constraint on solutions which
* join.
*
* @throws ExecutionException
* @throws InterruptedException
*/
public void test_optionalJoin_and_constraint() throws InterruptedException, ExecutionException {
final int joinId = 2;
final int predId = 3;
@SuppressWarnings("unchecked")
final Var<IV> x = Var.var("x");
@SuppressWarnings("unchecked")
final IVariable<IV>[] joinVars = new IVariable[]{x};
final UUID queryId = UUID.randomUUID();
// AP("Paul" ?x)
final Predicate<IV> pred = new Predicate<IV>(
new IVariableOrConstant[] { new Constant<IV>(setup.paul),
new Constant<IV>(setup.knows), x },
NV.asMap(new NV[] {//
new NV(Predicate.Annotations.RELATION_NAME,
new String[] { setup.spoNamespace }),//
new NV(Predicate.Annotations.BOP_ID, predId),//
new NV(Predicate.Annotations.OPTIONAL, Boolean.TRUE),//
// constraint x != Luke
new NV(PipelineJoin.Annotations.CONSTRAINTS,
new IConstraint[] { Constraint.wrap(new NEConstant(x,
new Constant<IV>(setup.luke))) }),
new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),//
}));
final PipelineOp query = newJoin(
new BOp[] { }, // args
joinId,
joinVars,
pred,
queryId
);
/**
* Setup the source.
*
* bset1: This has nothing bound and can not join since the join
* variable is not bound. However, it is passed along any as an
* "optional" solution.
*
* bset2: This has x:=Luke, which does not join. However, this is an
* optional join so x:=Luke should be output anyway. There is a
* constraint that x!= Luke, but that constraint does not fail the
* solution because it is an optional solution.
*
* bset3: This has x:=Mary, which joins and is output as a solution.
*/
final IAsynchronousIterator<IBindingSet[]> source;
{
final IBindingSet bset1 = new ListBindingSet();
final IBindingSet bset2 = new ListBindingSet();
{
bset2.set(x, new Constant<IV>(setup.luke));
}
final IBindingSet bset3 = new ListBindingSet();
{
bset3.set(x, new Constant<IV>(setup.mary));
}
source = new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { new IBindingSet[] { bset1, bset2,
bset3 } });
}
// the expected solutions.
final IBindingSet[] expected = new IBindingSet[] {//
// bset1: optional solution.
new ListBindingSet(//
new IVariable[] { },//
new IConstant[] {}//
),//
// bset2: optional solution.
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.luke) }//
),//
// bset3: joins.
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.mary) }//
),//
};
final MockQueryContext queryContext = new MockQueryContext(queryId);
try {
final BaseJoinStats stats = (BaseJoinStats) query.newStats();
final IBlockingBuffer<IBindingSet[]> sink = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
/*
* Note: Since the operator relies on the isLastInvocation() test to
* run the hash join, this is signal is required in order to have
* the operator produce output. Otherwise it will just buffer the
* source solutions until it exceeds its memory budget.
*/
final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>(
new MockRunningQuery(null/* fed */,
setup.jnl/* indexManager */, queryContext),
-1/* partitionId */, stats, query/* op */,
true/* lastInvocation */, source, sink, null/* sink2 */);
// context.setLastInvocation();
// get task.
final FutureTask<Void> ft = query.eval(context);
// execute task.
setup.jnl.getExecutorService().execute(ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,
sink.iterator(), ft);
// join task
assertEquals(1L, stats.chunksIn.get());
assertEquals(3L, stats.unitsIn.get());
assertEquals(3L, stats.unitsOut.get());
assertEquals(1L, 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());
} finally {
queryContext.close();
}
}
/**
* Unit test for an optional {@link PipelineJoin} when the
* {@link BOpContext#getSink2() alternative sink} is specified (simple
* variant of the unit test above).
*
* @throws InterruptedException
* @throws ExecutionException
*/
public void test_optionalJoin_withAltSink() throws InterruptedException,
ExecutionException {
final int joinId = 2;
final int predId = 3;
@SuppressWarnings("unchecked")
final Var<IV> x = Var.var("x");
@SuppressWarnings("unchecked")
final IVariable<IV>[] joinVars = new IVariable[]{x};
final UUID queryId = UUID.randomUUID();
final Predicate<IV> pred = new Predicate<IV>(
new IVariableOrConstant[] { new Constant<IV>(setup.paul),
new Constant<IV>(setup.knows), x },
NV.asMap(new NV[] {//
new NV(Predicate.Annotations.RELATION_NAME,
new String[] { setup.spoNamespace }),//
new NV(Predicate.Annotations.BOP_ID, predId),//
new NV(Predicate.Annotations.OPTIONAL, Boolean.TRUE),//
// constraint x != Luke
new NV(PipelineJoin.Annotations.CONSTRAINTS,
new IConstraint[] { Constraint.wrap(new NEConstant(x,
new Constant<IV>(setup.luke))) }),
new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),//
}));
final PipelineOp query = newJoin(
new BOp[] { }, // args
joinId,
joinVars,
pred,
queryId
);
/**
* Setup the source.
*
* bset1: This has nothing bound and can not join since the join
* variable is not bound. However, it is passed along any as an
* "optional" solution.
*
* bset2: This has x:=Luke, which does not join. However, this is an
* optional join so x:=Luke should be output anyway. There is a
* constraint that x!= Luke, but that constraint does not fail the
* solution because it is an optional solution.
*
* bset3: This has x:=Mary, which joins and is output as a solution.
*
* <pre>
* 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]
* </pre>
*/
final IAsynchronousIterator<IBindingSet[]> source;
{
final IBindingSet bset1 = new ListBindingSet();
final IBindingSet bset2 = new ListBindingSet();
{
bset2.set(x, new Constant<IV>(setup.luke));
}
final IBindingSet bset3 = new ListBindingSet();
{
bset3.set(x, new Constant<IV>(setup.mary));
}
source = new ThickAsynchronousIterator<IBindingSet[]>(
new IBindingSet[][] { new IBindingSet[] { bset1, bset2,
bset3 } });
}
// the expected solutions.
final IBindingSet[] expected = new IBindingSet[] {//
// // bset1: optional solution.
// new ListBindingSet(//
// new IVariable[] { },//
// new IConstant[] {}//
// ),//
// // bset2: optional solution.
// new ListBindingSet(//
// new IVariable[] { x },//
// new IConstant[] { new Constant<String>("Luke") }//
// ),//
// bset3: joins.
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.mary) }//
),//
};
// the expected solutions for the alternative sink (the optional solutions).
final IBindingSet[] expected2 = new IBindingSet[] {//
// bset1: optional solution.
new ListBindingSet(//
new IVariable[] { },//
new IConstant[] {}//
),//
// bset2: optional solution.
new ListBindingSet(//
new IVariable[] { x },//
new IConstant[] { new Constant<IV>(setup.luke) }//
),//
};
final MockQueryContext queryContext = new MockQueryContext(queryId);
try {
final BaseJoinStats stats = (BaseJoinStats) query.newStats();
final IBlockingBuffer<IBindingSet[]> sink = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
final IBlockingBuffer<IBindingSet[]> sink2 = new BlockingBufferWithStats<IBindingSet[]>(
query, stats);
/*
* Note: Since the operator relies on the isLastInvocation() test to
* run the hash join, this is signal is required in order to have
* the operator produce output. Otherwise it will just buffer the
* source solutions until it exceeds its memory budget.
*/
final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>(
new MockRunningQuery(null/* fed */,
setup.jnl/* indexManager */, queryContext),
-1/* partitionId */, stats, query/* op */,
true/* lastInvocation */, source, sink, sink2);
// context.setLastInvocation();
// get task.
final FutureTask<Void> ft = query.eval(context);
// execute task.
setup.jnl.getExecutorService().execute(ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected,
sink.iterator(), ft);
AbstractQueryEngineTestCase.assertSameSolutionsAnyOrder(expected2,
sink2.iterator(), ft);
// join task
assertEquals(1L, stats.chunksIn.get());
assertEquals(3L, stats.unitsIn.get());
assertEquals(3L, 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());
} finally {
queryContext.close();
}
}
}