/* * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007. * Copyright James Leigh (c) 2006. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.sail.memory; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import info.aduna.concurrent.locks.Lock; import info.aduna.iteration.CloseableIteration; import info.aduna.iteration.CloseableIteratorIteration; import info.aduna.iteration.FilterIteration; import info.aduna.iteration.IteratorIteration; import info.aduna.iteration.LockingIteration; import info.aduna.iteration.UnionIteration; import org.openrdf.model.Namespace; import org.openrdf.model.Resource; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.query.BindingSet; import org.openrdf.query.Dataset; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.algebra.QueryRoot; import org.openrdf.query.algebra.StatementPattern; import org.openrdf.query.algebra.TupleExpr; import org.openrdf.query.algebra.Var; import org.openrdf.query.algebra.evaluation.TripleSource; import org.openrdf.query.algebra.evaluation.impl.BindingAssigner; import org.openrdf.query.algebra.evaluation.impl.CompareOptimizer; import org.openrdf.query.algebra.evaluation.impl.ConjunctiveConstraintSplitter; import org.openrdf.query.algebra.evaluation.impl.ConstantOptimizer; import org.openrdf.query.algebra.evaluation.impl.EvaluationStatistics; import org.openrdf.query.algebra.evaluation.impl.EvaluationStrategyImpl; import org.openrdf.query.algebra.evaluation.impl.FilterOptimizer; import org.openrdf.query.algebra.evaluation.impl.QueryJoinOptimizer; import org.openrdf.query.algebra.evaluation.impl.QueryModelPruner; import org.openrdf.query.algebra.evaluation.impl.SameTermFilterOptimizer; import org.openrdf.query.algebra.evaluation.util.QueryOptimizerList; import org.openrdf.sail.SailException; import org.openrdf.sail.helpers.SailConnectionBase; import org.openrdf.sail.inferencer.InferencerConnection; import org.openrdf.sail.memory.model.MemResource; import org.openrdf.sail.memory.model.MemStatement; import org.openrdf.sail.memory.model.MemStatementIterator; import org.openrdf.sail.memory.model.MemStatementList; import org.openrdf.sail.memory.model.MemURI; import org.openrdf.sail.memory.model.MemValue; import org.openrdf.sail.memory.model.MemValueFactory; import org.openrdf.sail.memory.model.ReadMode; /** * Implementation of a Sail Connection for memory stores. * * @author Arjohn Kampman * @author jeen */ public class MemoryStoreConnection extends SailConnectionBase implements InferencerConnection { /*-----------* * Variables * *-----------*/ protected final MemoryStore store; /** * The exclusive transaction lock held by this connection during * transactions. */ private Lock txnLock; /** * A statement list read lock held by this connection during transactions. * Keeping this lock prevents statements from being removed from the main * statement list during transactions. */ private Lock txnStLock; /*--------------* * Constructors * *--------------*/ protected MemoryStoreConnection(MemoryStore store) { super(store); this.store = store; } /*---------* * Methods * *---------*/ @Override protected CloseableIteration<? extends BindingSet, QueryEvaluationException> evaluateInternal( TupleExpr tupleExpr, Dataset dataset, BindingSet bindings, boolean includeInferred) throws SailException { // Clone the tuple expression to allow for more aggresive optimizations tupleExpr = tupleExpr.clone(); if (!(tupleExpr instanceof QueryRoot)) { // Add a dummy root node to the tuple expressions to allow the // optimizers to modify the actual root node tupleExpr = new QueryRoot(tupleExpr); } Lock stLock = store.getStatementsReadLock(); try { int snapshot = store.getCurrentSnapshot(); ReadMode readMode = ReadMode.COMMITTED; if (transactionActive()) { snapshot++; readMode = ReadMode.TRANSACTION; } TripleSource tripleSource = new MemTripleSource(includeInferred, snapshot, readMode); EvaluationStrategyImpl strategy = new EvaluationStrategyImpl(tripleSource, dataset); QueryOptimizerList optimizerList = new QueryOptimizerList(); optimizerList.add(new BindingAssigner()); optimizerList.add(new ConstantOptimizer(strategy)); optimizerList.add(new CompareOptimizer()); optimizerList.add(new ConjunctiveConstraintSplitter()); optimizerList.add(new SameTermFilterOptimizer()); optimizerList.add(new QueryModelPruner()); optimizerList.add(new QueryJoinOptimizer(new MemEvaluationStatistics())); optimizerList.add(new FilterOptimizer()); optimizerList.optimize(tupleExpr, dataset, bindings); CloseableIteration<BindingSet, QueryEvaluationException> iter; iter = strategy.evaluate(tupleExpr, bindings); return new LockingIteration<BindingSet, QueryEvaluationException>(stLock, iter); } catch (QueryEvaluationException e) { stLock.release(); throw new SailException(e); } catch (RuntimeException e) { stLock.release(); throw e; } } @Override protected void closeInternal() throws SailException { // do nothing } @SuppressWarnings("unchecked") @Override protected CloseableIteration<? extends Resource, SailException> getContextIDsInternal() throws SailException { Lock stLock = store.getStatementsReadLock(); try { // Iterate over all MemURIs and MemBNodes CloseableIteration<MemResource, SailException> iter; iter = new UnionIteration<MemResource, SailException>( new IteratorIteration<MemResource, SailException>( store.getValueFactory().getMemURIs().iterator()), new IteratorIteration<MemResource, SailException>( store.getValueFactory().getMemBNodes().iterator())); final int snapshot = transactionActive() ? store.getCurrentSnapshot() + 1 : store.getCurrentSnapshot(); final ReadMode readMode = transactionActive() ? ReadMode.TRANSACTION : ReadMode.COMMITTED; iter = new FilterIteration<MemResource, SailException>(iter) { @Override protected boolean accept(MemResource memResource) throws SailException { MemStatementList contextStatements = memResource.getContextStatementList(); // Filter resources that are not used as context identifier if (contextStatements.size() == 0) { return false; } // Filter more thoroughly by considering snapshot and read-mode // parameters MemStatementIterator<SailException> iter = new MemStatementIterator<SailException>( contextStatements, null, null, null, false, snapshot, readMode); try { return iter.hasNext(); } finally { iter.close(); } } }; // Release query lock when iterator is closed iter = new LockingIteration<MemResource, SailException>(stLock, iter); return iter; } catch (RuntimeException e) { stLock.release(); throw e; } } @Override protected CloseableIteration<? extends Statement, SailException> getStatementsInternal(Resource subj, URI pred, Value obj, boolean includeInferred, Resource... contexts) throws SailException { Lock stLock = store.getStatementsReadLock(); try { int snapshot = store.getCurrentSnapshot(); ReadMode readMode = ReadMode.COMMITTED; if (transactionActive()) { snapshot++; readMode = ReadMode.TRANSACTION; } return new LockingIteration<MemStatement, SailException>(stLock, store.createStatementIterator( SailException.class, subj, pred, obj, !includeInferred, snapshot, readMode, contexts)); } catch (RuntimeException e) { stLock.release(); throw e; } } @Override protected long sizeInternal(Resource... contexts) throws SailException { Lock stLock = store.getStatementsReadLock(); try { CloseableIteration<? extends Statement, SailException> iter = getStatementsInternal(null, null, null, false, contexts); try { long size = 0L; while (iter.hasNext()) { iter.next(); size++; } return size; } finally { iter.close(); } } finally { stLock.release(); } } @Override protected CloseableIteration<? extends Namespace, SailException> getNamespacesInternal() throws SailException { return new CloseableIteratorIteration<Namespace, SailException>(store.getNamespaceStore().iterator()); } @Override protected String getNamespaceInternal(String prefix) throws SailException { return store.getNamespaceStore().getNamespace(prefix); } @Override protected void startTransactionInternal() throws SailException { if (!store.isWritable()) { throw new SailException("Unable to start transaction: data file is read-only"); } txnStLock = store.getStatementsReadLock(); // Prevent concurrent transactions by acquiring an exclusive txn lock txnLock = store.getTransactionLock(); store.startTransaction(); } @Override protected void commitInternal() throws SailException { store.commit(); txnLock.release(); txnStLock.release(); } @Override protected void rollbackInternal() throws SailException { try { store.rollback(); } finally { txnLock.release(); txnStLock.release(); } } @Override protected void addStatementInternal(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException { addStatementInternal(subj, pred, obj, true, contexts); } public boolean addInferredStatement(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); return addStatementInternal(subj, pred, obj, false, contexts); } finally { txnLock.release(); } } finally { conLock.release(); } } /** * Adds the specified statement to this MemoryStore. * * @throws SailException */ protected boolean addStatementInternal(Resource subj, URI pred, Value obj, boolean explicit, Resource... contexts) throws SailException { Statement st = null; if (contexts.length == 0) { st = store.addStatement(subj, pred, obj, null, explicit); if (st != null) { notifyStatementAdded(st); } } else { for (Resource context : contexts) { st = store.addStatement(subj, pred, obj, context, explicit); if (st != null) { notifyStatementAdded(st); } } } // FIXME: this return type is invalid in case multiple contexts were // specified return st != null; } @Override protected void removeStatementsInternal(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException { removeStatementsInternal(subj, pred, obj, true, contexts); } public boolean removeInferredStatement(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); return removeStatementsInternal(subj, pred, obj, false, contexts); } finally { txnLock.release(); } } finally { conLock.release(); } } @Override protected void clearInternal(Resource... contexts) throws SailException { removeStatementsInternal(null, null, null, true, contexts); } public void clearInferred(Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); removeStatementsInternal(null, null, null, false, contexts); } finally { txnLock.release(); } } finally { conLock.release(); } } public void flushUpdates() { // no-op; changes are reported as soon as they come in } /** * Removes the statements that match the specified pattern of subject, * predicate, object and context. * * @param subj * The subject for the pattern, or <tt>null</tt> for a wildcard. * @param pred * The predicate for the pattern, or <tt>null</tt> for a wildcard. * @param obj * The object for the pattern, or <tt>null</tt> for a wildcard. * @param explicit * Flag indicating whether explicit or inferred statements should be * removed; <tt>true</tt> removes explicit statements that match the * pattern, <tt>false</tt> removes inferred statements that match * the pattern. * @throws SailException */ protected boolean removeStatementsInternal(Resource subj, URI pred, Value obj, boolean explicit, Resource... contexts) throws SailException { CloseableIteration<MemStatement, SailException> stIter = store.createStatementIterator( SailException.class, subj, pred, obj, explicit, store.getCurrentSnapshot() + 1, ReadMode.TRANSACTION, contexts); return removeIteratorStatements(stIter, explicit); } protected boolean removeIteratorStatements(CloseableIteration<MemStatement, SailException> stIter, boolean explicit) throws SailException { boolean statementsRemoved = false; try { while (stIter.hasNext()) { MemStatement st = stIter.next(); if (store.removeStatement(st, explicit)) { statementsRemoved = true; notifyStatementRemoved(st); } } } finally { stIter.close(); } return statementsRemoved; } @Override protected void setNamespaceInternal(String prefix, String name) throws SailException { // FIXME: changes to namespace prefixes not isolated in transactions yet try { store.getNamespaceStore().setNamespace(prefix, name); } catch (IllegalArgumentException e) { throw new SailException(e.getMessage()); } } @Override protected void removeNamespaceInternal(String prefix) throws SailException { // FIXME: changes to namespace prefixes not isolated in transactions yet store.getNamespaceStore().removeNamespace(prefix); } @Override protected void clearNamespacesInternal() throws SailException { // FIXME: changes to namespace prefixes not isolated in transactions yet store.getNamespaceStore().clear(); } /*-----------------------------* * Inner class MemTripleSource * *-----------------------------*/ /** * Implementation of the TripleSource interface from the Sail Query Model */ protected class MemTripleSource implements TripleSource { protected final int snapshot; protected final ReadMode readMode; protected final boolean includeInferred; public MemTripleSource(boolean includeInferred, int snapshot, ReadMode readMode) { this.includeInferred = includeInferred; this.snapshot = snapshot; this.readMode = readMode; } public CloseableIteration<MemStatement, QueryEvaluationException> getStatements(Resource subj, URI pred, Value obj, Resource... contexts) { return store.createStatementIterator(QueryEvaluationException.class, subj, pred, obj, !includeInferred, snapshot, readMode, contexts); } public MemValueFactory getValueFactory() { return store.getValueFactory(); } } // end inner class MemTripleSource /*-------------------------------------* * Inner class MemEvaluationStatistics * *-------------------------------------*/ /** * Uses the MemoryStore's statement sizes to give cost estimates based on the * size of the expected results. This process could be improved with * repository statistics about size and distribution of statements. * * @author Arjohn Kampman * @author James Leigh */ protected class MemEvaluationStatistics extends EvaluationStatistics { @Override protected CardinalityCalculator getCardinalityCalculator(Set<String> boundVars) { return new MemCardinalityCalculator(boundVars); } protected class MemCardinalityCalculator extends CardinalityCalculator { public MemCardinalityCalculator(Set<String> boundVars) { super(boundVars); } @Override public void meet(StatementPattern sp) { Resource subj = (Resource)getConstantValue(sp.getSubjectVar()); URI pred = (URI)getConstantValue(sp.getPredicateVar()); Value obj = getConstantValue(sp.getObjectVar()); Resource context = (Resource)getConstantValue(sp.getContextVar()); MemValueFactory valueFactory = store.getValueFactory(); // Perform look-ups for value-equivalents of the specified values MemResource memSubj = valueFactory.getMemResource(subj); MemURI memPred = valueFactory.getMemURI(pred); MemValue memObj = valueFactory.getMemValue(obj); MemResource memContext = valueFactory.getMemResource(context); if (subj != null && memSubj == null || pred != null && memPred == null || obj != null && memObj == null || context != null && memContext == null) { // non-existent subject, predicate, object or context cardinality = 0; return; } // Search for the smallest list that can be used by the iterator List<Integer> listSizes = new ArrayList<Integer>(4); if (memSubj != null) { listSizes.add(memSubj.getSubjectStatementCount()); } if (memPred != null) { listSizes.add(memPred.getPredicateStatementCount()); } if (memObj != null) { listSizes.add(memObj.getObjectStatementCount()); } if (memContext != null) { listSizes.add(memContext.getContextStatementCount()); } if (listSizes.isEmpty()) { cardinality = store.size(); int sqrtFactor = 2 * countBoundVars(sp); if (sqrtFactor > 1) { cardinality = Math.pow(cardinality, 1.0 / sqrtFactor); } } else { cardinality = Collections.min(listSizes); int constantVarCount = countConstantVars(sp); int boundVarCount = countBoundVars(sp); // Subtract 1 from constantVarCount as this was used for the list // size int sqrtFactor = 2 * boundVarCount + Math.max(0, constantVarCount - 1); if (sqrtFactor > 1) { cardinality = Math.pow(cardinality, 1.0 / sqrtFactor); } } } protected Value getConstantValue(Var var) { if (var != null) { return var.getValue(); } return null; } } } // end inner class MemCardinalityCalculator }