/** 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 19, 2010 */ package com.bigdata.bop.ap; import java.util.Map; import java.util.Properties; 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.IPredicate; 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.Annotations; import com.bigdata.bop.ap.filter.BOpResolver; import com.bigdata.bop.ap.filter.BOpTupleFilter; import com.bigdata.bop.ap.filter.DistinctFilter; import com.bigdata.bop.engine.BOpStats; import com.bigdata.bop.engine.MockRunningQuery; import com.bigdata.btree.ITuple; import com.bigdata.io.SerializerUtil; import com.bigdata.journal.BufferMode; import com.bigdata.journal.ITx; import com.bigdata.journal.Journal; import com.bigdata.relation.accesspath.BlockingBuffer; import com.bigdata.relation.accesspath.IAccessPath; import com.bigdata.relation.accesspath.IAsynchronousIterator; import com.bigdata.relation.accesspath.IBlockingBuffer; import com.bigdata.relation.accesspath.ThickAsynchronousIterator; import com.bigdata.striterator.ChunkedArrayIterator; import com.bigdata.striterator.IChunkedOrderedIterator; import cutthecrap.utils.striterators.ICloseableIterator; /** * Unit test for reading on an access path using a {@link Predicate}. This unit * test works through the create and population of a test relation with some * data and verifies the ability to access that data using some different access * paths. This sets the ground for testing the evaluation of {@link Predicate}s * with various constraints, filters, etc. * <p> * Note: Tests of remote access path reads are done in the context of a bigdata * federation since there must be a data service in play for a remote access * path. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id: TestPredicateAccessPath.java 3466 2010-08-27 14:28:04Z * thompsonbry $ * * @todo test read-committed access paths. * @todo test read historical access paths. * @todo test unisolated (writable) access paths. * @todo test fully isolated access paths. */ public class TestPredicateAccessPath extends TestCase2 { /** * */ public TestPredicateAccessPath() { } /** * @param name */ public TestPredicateAccessPath(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"; Journal jnl; R rel; public void setUp() throws Exception { jnl = new Journal(getProperties()); loadData(jnl); } /** * 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. final E[] a = {// new E("John", "Mary"),// new E("Mary", "Paul"),// new E("Paul", "Leon"),// new E("Leon", "Paul"),// new E("Mary", "John"),// }; // 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(); // should exist as of the last commit point. this.rel = (R) jnl.getResourceLocator().locate(namespace, ITx.READ_COMMITTED); assertNotNull(rel); } public void tearDown() throws Exception { if (jnl != null) { jnl.destroy(); jnl = null; } // clear reference. rel = null; } public void test_keyOrderSerializable() { SerializerUtil.serialize(R.primaryKeyOrder); } /** * Using a predicate with nothing bound, verify that we get the * right range count on the relation and that we read the correct elements * from the relation. */ public void test_nothingBound() { // nothing bound. final IAccessPath<E> accessPath = rel.getAccessPath(new Predicate<E>( new BOp[] { Var.var("name"), Var.var("value") }, new NV( IPredicate.Annotations.RELATION_NAME, new String[] { namespace }))); // verify the range count. assertEquals(5, accessPath.rangeCount(true/* exact */)); // visit that access path, verifying the elements and order. if (log.isInfoEnabled()) log.info("accessPath=" + accessPath); final E[] expected = new E[] {// new E("John", "Mary"),// new E("Leon", "Paul"),// new E("Mary", "John"),// new E("Mary", "Paul"),// new E("Paul", "Leon"),// }; final IChunkedOrderedIterator<E> itr = accessPath.iterator(); try { int n = 0; while (itr.hasNext()) { final E e = itr.next(); if (log.isInfoEnabled()) log.info(n + " : " + e); assertEquals(expected[n], e); n++; } assertEquals(expected.length,n); } finally { itr.close(); } } /** * Using a predicate which binds the [name] position, verify that we get the * right range count on the relation and verify the actual element pulled * back from the access path. */ public void test_scan() { final IAccessPath<E> accessPath = rel.getAccessPath(new Predicate<E>( new IVariableOrConstant[] { new Constant<String>("Mary"), Var.var("value") }, new NV( Predicate.Annotations.RELATION_NAME, new String[] { namespace }))); // verify the range count. assertEquals(2, accessPath.rangeCount(true/* exact */)); // visit that access path, verifying the elements and order. if (log.isInfoEnabled()) log.info("accessPath=" + accessPath); final E[] expected = new E[] {// new E("Mary", "John"),// new E("Mary", "Paul"),// }; final IChunkedOrderedIterator<E> itr = accessPath.iterator(); try { int n = 0; while (itr.hasNext()) { final E e = itr.next(); if (log.isInfoEnabled()) log.info(n + " : " + e); assertEquals(expected[n], e); n++; } assertEquals(expected.length,n); } finally { itr.close(); } } /** * Verify lookup and read on an {@link IPredicate}. */ public void test_predicate_eval() { final Predicate<E> pred = new Predicate<E>(new IVariableOrConstant[] { new Constant<String>("Mary"), Var.var("value") }, NV.asMap(new NV[] {// new NV(Annotations.RELATION_NAME,new String[]{namespace}),// new NV(com.bigdata.bop.IPredicate.Annotations.TIMESTAMP, ITx.READ_COMMITTED),// new NV(Annotations.REMOTE_ACCESS_PATH, false),// })); final E[] expected = new E[] {// new E("Mary", "John"),// new E("Mary", "Paul"),// }; final BOpStats statIsIgnored = new BOpStats(); final ICloseableIterator<IBindingSet[]> sourceIsIgnored = newBindingSetIterator( new IBindingSet[0]); final IBlockingBuffer<IBindingSet[]> sinkIsIgnored = new BlockingBuffer<IBindingSet[]>( 1/* capacity */); final PipelineOp mockQuery = new MockPipelineOp(BOp.NOARGS); final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>( new MockRunningQuery(null/* fed */, jnl/* indexManager */ ), -1/* partitionId */, statIsIgnored, mockQuery/* op */, false/* lastInvocation */, sourceIsIgnored, sinkIsIgnored, null/* sink2 */); // lookup relation final R relation = (R) context.getRelation(pred); // obtain access path for that relation. final IAccessPath<E> ap = context.getAccessPath(relation, pred); // obtain range count from the access path. assertEquals(2L, ap.rangeCount(true/* exact */)); // verify the data visited by the access path. final IChunkedOrderedIterator<E> itr = ap.iterator(); try { int n = 0; while (itr.hasNext()) { final E e = itr.next(); if (log.isInfoEnabled()) log.info(n + " : " + e); assertEquals(expected[n], e); n++; } assertEquals(expected.length,n); } finally { itr.close(); } } /** * Unit test for an {@link IPredicate.Annotations#INDEX_LOCAL_FILTER}. * * @todo test with synchronous and asynchronous iterators. * @todo test with exact range count (filter must be applied). */ public void test_indexLocalFilter() { final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); /* * Filter accepts iff name := "Mary". */ final BOpTupleFilter<E> filter = new BOpTupleFilter<E>( new BOp[] {/* filters */}, null/* annotations */) { private static final long serialVersionUID = 1L; @Override protected boolean isValid(ITuple<E> tuple) { return tuple.getObject().name.equals("Mary"); } }; final Predicate<E> pred = new Predicate<E>(new IVariableOrConstant[] { x, y }, NV.asMap(new NV[] {// new NV(Annotations.RELATION_NAME, new String[] { namespace }),// new NV(com.bigdata.bop.IPredicate.Annotations.TIMESTAMP, ITx.READ_COMMITTED),// new NV(Annotations.INDEX_LOCAL_FILTER, filter),// })); final E[] expected = new E[] {// new E("Mary", "John"),// new E("Mary", "Paul"),// }; final BOpStats statIsIgnored = new BOpStats(); final ICloseableIterator<IBindingSet[]> sourceIsIgnored = newBindingSetIterator(new IBindingSet[0]); final IBlockingBuffer<IBindingSet[]> sinkIsIgnored = new BlockingBuffer<IBindingSet[]>( 1/* capacity */); final PipelineOp mockQuery = new MockPipelineOp(BOp.NOARGS); final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>( new MockRunningQuery(null/* fed */, jnl/* indexManager */ ), -1/* partitionId */, statIsIgnored, mockQuery/* op */, false/* lastInvocation */, sourceIsIgnored, sinkIsIgnored, null/* sink2 */); // lookup relation final R relation = (R) context.getRelation(pred); // obtain access path for that relation. final IAccessPath<E> ap = context.getAccessPath(relation, pred); // obtain range count from the access path. assertEquals(2L, ap.rangeCount(true/* exact */)); // verify the data visited by the access path. final IChunkedOrderedIterator<E> itr = ap.iterator(); try { int n = 0; while (itr.hasNext()) { final E e = itr.next(); if (log.isInfoEnabled()) log.info(n + " : " + e); assertEquals(expected[n], e); n++; } assertEquals(expected.length,n); } finally { itr.close(); } } /** * Filter strips off the 'value' column. */ private static class ValueStripper extends BOpResolver { public ValueStripper(ValueStripper op) { super(op); } public ValueStripper(BOp[] args, Map<String, Object> annotations) { super(args, annotations); } private static final long serialVersionUID = 1L; @Override protected Object resolve(Object obj) { return new E(((E) obj).name, ""); } } /** * Unit test for an {@link IPredicate.Annotations#ACCESS_PATH_FILTER}. * * @todo test with synchronous and asynchronous iterators. * @todo test with exact range count (filter must be applied). */ public void test_accessPathFilter() { final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); /* * Filter strips off the 'value' column. */ final BOpResolver stripper = new ValueStripper( new BOp[] {/* filters */}, null/* annotations */); /* * Filter imposes distinct on the visited elements. It chains the filter * to strip off the 'value' column first, then applies itself to filter * for just the distinct elements. */ final DistinctFilter distinctFilter = new DistinctFilter( new BOp[] { stripper /* filters */}, null/* annotations */); final Predicate<E> pred = new Predicate<E>(new IVariableOrConstant[] { x, y }, NV.asMap(new NV[] {// new NV(Annotations.RELATION_NAME, new String[] { namespace }),// new NV(com.bigdata.bop.IPredicate.Annotations.TIMESTAMP, ITx.READ_COMMITTED),// new NV(Annotations.ACCESS_PATH_FILTER, distinctFilter),// })); // the distinct values from the name column in index order. final E[] expected = new E[] {// new E("John", ""),// new E("Leon", ""),// new E("Mary", ""),// new E("Paul", ""),// }; final BOpStats statIsIgnored = new BOpStats(); final ICloseableIterator<IBindingSet[]> sourceIsIgnored = newBindingSetIterator(new IBindingSet[0]); final IBlockingBuffer<IBindingSet[]> sinkIsIgnored = new BlockingBuffer<IBindingSet[]>( 1/* capacity */); final PipelineOp mockQuery = new MockPipelineOp(BOp.NOARGS); final BOpContext<IBindingSet> context = new BOpContext<IBindingSet>( new MockRunningQuery(null/* fed */, jnl/* indexManager */ ), -1/* partitionId */, statIsIgnored, mockQuery/* op */, false/* lastInvocation */, sourceIsIgnored, sinkIsIgnored, null/* sink2 */); // lookup relation final R relation = (R) context.getRelation(pred); // obtain access path for that relation. final IAccessPath<E> ap = context.getAccessPath(relation, pred); // obtain range count from the access path. assertEquals(4L, ap.rangeCount(true/* exact */)); // verify the data visited by the access path. final IChunkedOrderedIterator<E> itr = ap.iterator(); try { int n = 0; while (itr.hasNext()) { final E e = itr.next(); if (log.isInfoEnabled()) log.info(n + " : " + e); assertEquals(expected[n], e); n++; } assertEquals(expected.length,n); } finally { itr.close(); } } /** * Return an {@link IAsynchronousIterator} that will read the source * {@link IBindingSet}s. * * @param bsets * The source binding sets. */ private static ThickAsynchronousIterator<IBindingSet[]> newBindingSetIterator( final IBindingSet[] bsets) { return new ThickAsynchronousIterator<IBindingSet[]>( new IBindingSet[][] { bsets }); } protected class MockPipelineOp extends PipelineOp { public MockPipelineOp(final BOp[] args, final NV... anns) { super(args, NV.asMap(anns)); }; private static final long serialVersionUID = 1L; @Override public FutureTask<Void> eval(BOpContext<IBindingSet> context) { throw new UnsupportedOperationException(); } } }