/* 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 Jan 27, 2007 */ package com.bigdata.rdf.sail; import java.util.Iterator; import java.util.Properties; import org.openrdf.model.vocabulary.RDF; import org.openrdf.sail.SailException; import com.bigdata.btree.IIndex; import com.bigdata.btree.ITuple; import com.bigdata.rdf.axioms.NoAxioms; import com.bigdata.rdf.changesets.ChangeAction; import com.bigdata.rdf.model.BigdataURI; import com.bigdata.rdf.model.BigdataValueFactory; import com.bigdata.rdf.model.StatementEnum; import com.bigdata.rdf.sail.BigdataSail.BigdataSailConnection; import com.bigdata.rdf.sparql.ast.service.history.HistoryChangeRecord; import com.bigdata.rdf.sparql.ast.service.history.HistoryServiceFactory; import com.bigdata.rdf.spo.ISPO; import com.bigdata.rdf.spo.SPO; import com.bigdata.rdf.spo.SPORelation; import com.bigdata.rdf.store.AbstractTripleStore; import com.bigdata.relation.AbstractRelation; import cutthecrap.utils.striterators.Resolver; import cutthecrap.utils.striterators.Striterator; /** * Test the index supporting the {@link HistoryServiceFactory}. * * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/607"> History * Service</a> * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ * * TODO The unit tests should cover both the tx and non-tx modes. * <p> * For read/write tx, we can not use an unisolated history index since * changes could become committed before a given tx commits due to a * concurrent tx commit. That would break the semantics of the history * index if the tx then fails rather than committing since some of its * changes would have become visible and durable anyway. * <p> * We might need a write-write conflict resolver for the history index * when read-write tx are used. Otherwise a conflict on the history * index could cause a tx to fail. I suspect that we can reconcile * exactly the same write-write conflicts on the history index that we * can reconcile on the statement indices, but this needs to be worked * through in detail. */ public class TestHistoryIndex extends ProxyBigdataSailTestCase { // private static final Logger log = Logger.getLogger(TestHistoryIndex.class); /** * */ public TestHistoryIndex() { } /** * @param name */ public TestHistoryIndex(String name) { super(name); } /** * Return the pre-existing history index. * * @param tripleStore * The KB. * @return The history index -or- <code>null</code> if it was not * configured. */ private IIndex getHistoryIndex(final AbstractTripleStore tripleStore) { final SPORelation spoRelation = tripleStore.getSPORelation(); final String fqn = AbstractRelation.getFQN(spoRelation, SPORelation.NAME_HISTORY); final IIndex ndx = spoRelation.getIndex(fqn); return ndx; } /** * Unit test verifies that the history index is not created if the option is * not enabled. */ public void test_historyIndexDisabled() throws SailException { final Properties properties = getProperties(); // disable the history service. properties .setProperty( com.bigdata.rdf.store.AbstractTripleStore.Options.HISTORY_SERVICE, "false"); final BigdataSail sail = getSail(properties); try { sail.initialize(); final BigdataSailConnection conn = sail.getConnection(); try { // Resolve the index that the connection will write on. final IIndex ndx = getHistoryIndex(conn.getTripleStore()); // The index should not exist. assertNull(ndx); conn.rollback(); } finally { conn.close(); } } finally { sail.__tearDownUnitTest(); } } /** * Unit test works its way through two commit points, verifying the state * changes in the history index in depth. In the first commit point, two * statements are added and both should appear in the history index. In the * second commit point, one of the statements is removed and an entry for * that removal is also added to the history index. */ public void test_historyIndex01() throws SailException { final Properties properties = getProperties(); // enable the history service. properties .setProperty( com.bigdata.rdf.store.AbstractTripleStore.Options.HISTORY_SERVICE, "true"); // disable inference. properties.setProperty( com.bigdata.rdf.store.AbstractTripleStore.Options.AXIOMS_CLASS, NoAxioms.class.getName()); final BigdataSail sail = getSail(properties); try { sail.initialize(); /* * Verify that we can add some statements and they will appear in * the history index. */ final long revisionTime0; final ISPO stmt0, stmt1; { final BigdataSailConnection conn = sail.getConnection(); try { // Expected revision time for the history index entries. revisionTime0 = conn.getTripleStore().getIndexManager() .getLastCommitTime() + 1; // Resolve the index that the connection will write on. final IIndex ndx = getHistoryIndex(conn.getTripleStore()); // The index should exist. assertNotNull(ndx); // The index should be empty. assertEquals(0L, ndx.rangeCount()); final BigdataValueFactory f = (BigdataValueFactory) sail .getValueFactory(); final BigdataURI A = f .createURI("http://www.bigdata.com/A"); final BigdataURI B = f .createURI("http://www.bigdata.com/B"); final BigdataURI C = f .createURI("http://www.bigdata.com/C"); final BigdataURI rdfType = f.asValue(RDF.TYPE); conn.addStatement(A, rdfType, B); conn.addStatement(A, rdfType, C); conn.commit(); // Should be 2 entries (more if inference is enabled). assertEquals(2L, ndx.rangeCount()); stmt0 = new SPO(A.getIV(), rdfType.getIV(), B.getIV(), StatementEnum.Explicit); stmt1 = new SPO(A.getIV(), rdfType.getIV(), C.getIV(), StatementEnum.Explicit); @SuppressWarnings("unchecked") final Iterator<HistoryChangeRecord> itr = new Striterator( ndx.rangeIterator()).addFilter(new Resolver() { private static final long serialVersionUID = 1L; @Override protected Object resolve(final Object obj) { return ((ITuple<HistoryChangeRecord>) obj) .getObject(); } }); final HistoryChangeRecord[] a = new HistoryChangeRecord[] {// new HistoryChangeRecord(stmt0, ChangeAction.INSERTED, revisionTime0),// new HistoryChangeRecord(stmt1, ChangeAction.INSERTED, revisionTime0),// }; // for (HistoryChangeRecord r : a) { // System.err.println("Expected: " + r); // System.out.println("Actual : " + itr.next()); // } // Verify the records in the index. assertSameIteratorAnyOrder(a, itr); } finally { conn.close(); } } /* * Verify that we can remove a statements and it will appear in the * history index as a new entry. The statement that we do not remove * will have only one entry. */ final long revisionTime1; { final BigdataSailConnection conn = sail.getConnection(); try { // Expected revision time for the history index entries. revisionTime1 = conn.getTripleStore().getIndexManager() .getLastCommitTime() + 1; // Resolve the index that the connection will write on. final IIndex ndx = getHistoryIndex(conn.getTripleStore()); // The index should exist. assertNotNull(ndx); // The index should not be empty. assertEquals(2L, ndx.rangeCount()); final BigdataValueFactory f = (BigdataValueFactory) sail .getValueFactory(); final BigdataURI A = f .createURI("http://www.bigdata.com/A"); final BigdataURI B = f .createURI("http://www.bigdata.com/B"); // final BigdataURI C = f // .createURI("http://www.bigdata.com/C"); final BigdataURI rdfType = f.asValue(RDF.TYPE); conn.removeStatements(A, rdfType, B); conn.commit(); // Should be 3 entries (more if inference is enabled). assertEquals(3L, ndx.rangeCount()); @SuppressWarnings("unchecked") final Iterator<HistoryChangeRecord> itr = new Striterator( ndx.rangeIterator()).addFilter(new Resolver() { private static final long serialVersionUID = 1L; @Override protected Object resolve(final Object obj) { return ((ITuple<HistoryChangeRecord>) obj) .getObject(); } }); final HistoryChangeRecord[] a = new HistoryChangeRecord[] {// new HistoryChangeRecord(stmt0, ChangeAction.INSERTED, revisionTime0),// new HistoryChangeRecord(stmt1, ChangeAction.INSERTED, revisionTime0),// new HistoryChangeRecord(stmt0, ChangeAction.REMOVED, revisionTime1),// }; // for (HistoryChangeRecord r : a) { // System.err.println("Expected: " + r); // System.out.println("Actual : " + itr.next()); // } // Verify the records in the index. assertSameIteratorAnyOrder(a, itr); } finally { conn.close(); } } } finally { sail.__tearDownUnitTest(); } } /** * Unit test works its way through two commit points when the index pruning * is set to 1 millisecond, verifying the state changes in the history index * in depth. In the first commit point, two statements are added and both * should appear in the history index. In the second commit point, one of * the statements is removed and an entry for that removal is also added to * the history index. However, both of the original entries in the history * index have been aged out so they no longer appear. */ public void test_historyIndexWithPruning01() throws SailException { final Properties properties = getProperties(); // enable the history service. properties .setProperty( com.bigdata.rdf.store.AbstractTripleStore.Options.HISTORY_SERVICE, "true"); // prune history after 1 millisecond. properties .setProperty( com.bigdata.rdf.store.AbstractTripleStore.Options.HISTORY_SERVICE_MIN_RELEASE_AGE, "1"); // disable inference. properties.setProperty( com.bigdata.rdf.store.AbstractTripleStore.Options.AXIOMS_CLASS, NoAxioms.class.getName()); final BigdataSail sail = getSail(properties); try { sail.initialize(); /* * Verify that we can add some statements and they will appear in * the history index. */ final long revisionTime0; final ISPO stmt0, stmt1; { final BigdataSailConnection conn = sail.getConnection(); try { // Expected revision time for the history index entries. revisionTime0 = conn.getTripleStore().getIndexManager() .getLastCommitTime() + 1; // Resolve the index that the connection will write on. final IIndex ndx = getHistoryIndex(conn.getTripleStore()); // The index should exist. assertNotNull(ndx); // The index should be empty. assertEquals(0L, ndx.rangeCount()); final BigdataValueFactory f = (BigdataValueFactory) sail .getValueFactory(); final BigdataURI A = f .createURI("http://www.bigdata.com/A"); final BigdataURI B = f .createURI("http://www.bigdata.com/B"); final BigdataURI C = f .createURI("http://www.bigdata.com/C"); final BigdataURI rdfType = f.asValue(RDF.TYPE); conn.addStatement(A, rdfType, B); conn.addStatement(A, rdfType, C); conn.commit(); // Should be 2 entries (more if inference is enabled). assertEquals(2L, ndx.rangeCount()); stmt0 = new SPO(A.getIV(), rdfType.getIV(), B.getIV(), StatementEnum.Explicit); stmt1 = new SPO(A.getIV(), rdfType.getIV(), C.getIV(), StatementEnum.Explicit); @SuppressWarnings("unchecked") final Iterator<HistoryChangeRecord> itr = new Striterator( ndx.rangeIterator()).addFilter(new Resolver() { private static final long serialVersionUID = 1L; @Override protected Object resolve(final Object obj) { return ((ITuple<HistoryChangeRecord>) obj) .getObject(); } }); final HistoryChangeRecord[] a = new HistoryChangeRecord[] {// new HistoryChangeRecord(stmt0, ChangeAction.INSERTED, revisionTime0),// new HistoryChangeRecord(stmt1, ChangeAction.INSERTED, revisionTime0),// }; // for (HistoryChangeRecord r : a) { // System.err.println("Expected: " + r); // System.out.println("Actual : " + itr.next()); // } // Verify the records in the index. assertSameIteratorAnyOrder(a, itr); } finally { conn.close(); } } /* * Verify that we can remove a statements and it will appear in the * history index as a new entry. The statement that we do not remove * will have only one entry. */ final long revisionTime1; { final BigdataSailConnection conn = sail.getConnection(); try { // Expected revision time for the history index entries. revisionTime1 = conn.getTripleStore().getIndexManager() .getLastCommitTime() + 1; // Resolve the index that the connection will write on. final IIndex ndx = getHistoryIndex(conn.getTripleStore()); // The index should exist. assertNotNull(ndx); // The index should not be empty. assertEquals(2L, ndx.rangeCount()); final BigdataValueFactory f = (BigdataValueFactory) sail .getValueFactory(); final BigdataURI A = f .createURI("http://www.bigdata.com/A"); final BigdataURI B = f .createURI("http://www.bigdata.com/B"); // final BigdataURI C = f // .createURI("http://www.bigdata.com/C"); final BigdataURI rdfType = f.asValue(RDF.TYPE); conn.removeStatements(A, rdfType, B); conn.commit(); // Should be 1 entry since others were pruned out. assertEquals(1L, ndx.rangeCount()); @SuppressWarnings("unchecked") final Iterator<HistoryChangeRecord> itr = new Striterator( ndx.rangeIterator()).addFilter(new Resolver() { private static final long serialVersionUID = 1L; @Override protected Object resolve(final Object obj) { return ((ITuple<HistoryChangeRecord>) obj) .getObject(); } }); /* * The older entries should have been pruned out. */ final HistoryChangeRecord[] a = new HistoryChangeRecord[] {// // new HistoryChangeRecord(stmt0, // ChangeAction.INSERTED, revisionTime0),// // new HistoryChangeRecord(stmt1, // ChangeAction.INSERTED, revisionTime0),// new HistoryChangeRecord(stmt0, ChangeAction.REMOVED, revisionTime1),// }; // for (HistoryChangeRecord r : a) { // System.err.println("Expected: " + r); // System.out.println("Actual : " + itr.next()); // } // Verify the records in the index. assertSameIteratorAnyOrder(a, itr); } finally { conn.close(); } } } finally { sail.__tearDownUnitTest(); } } }