/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Plugged In Software Pty Ltd. All Rights Reserved. * * Contributor(s): * getModel() contributed by Netymon Pty Ltd on behalf of * The Australian Commonwealth Government under contract 4500507038. * DefinablePrefixAnnotation contributed by Netymon Pty Ltd on behalf of * The Australian Commonwealth Government under contract 4500507038. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.resolver.store; // Third party packages import org.apache.log4j.Logger; // Standard Java packages import java.util.*; // Locally written packages import org.mulgara.query.*; import org.mulgara.resolver.spi.ReresolvableResolution; import org.mulgara.store.nodepool.NodePool; import org.mulgara.store.statement.StatementStore; import org.mulgara.store.statement.StatementStoreException; import org.mulgara.store.tuples.Annotation; import org.mulgara.store.tuples.AbstractTuples; import org.mulgara.store.tuples.DefinablePrefixAnnotation; import org.mulgara.store.tuples.StoreTuples; import org.mulgara.store.tuples.Tuples; import org.mulgara.store.tuples.TuplesOperations; /** * Tuples backed by the graph, corresponding to a particular constraint. This * class retains the original constraint so that the graph index it's resolved * against can be resolved anew as its variables are bound. * * @created 2003-08-06 * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a> * @version $Revision: 1.11 $ * @modified $Date: 2005/05/06 04:07:58 $ * @maintenanceAuthor $Author: amuys $ * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * @copyright © 2003 <A href="http://www.PIsoftware.com/">Plugged In * Software Pty Ltd</A> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ class StatementStoreResolution extends AbstractTuples implements ReresolvableResolution { private final static long ROWCOUNT_UNCALCULATED = -1; private final static int ROWCARD_UNCALCULATED = -1; private final static Logger logger = Logger.getLogger(StatementStoreResolution.class); /** The constraint these tuples were generated to satisfy. */ private Constraint constraint; /** The graph from which these tuples were generated. */ private StatementStore store; /** Number of rows, constrained only by the fixed-prefix */ private long[] rowCount = new long[] { ROWCOUNT_UNCALCULATED }; /** The row cardinality, constrained only by the fixed-prefix */ private int[] rowCardinality = new int[] { ROWCARD_UNCALCULATED }; /** The unrestrained statement-store indexable by the defined index */ private Tuples indexedTuples; /** Mapping of statement variable order to constraint order */ private int[] columnOrder; /** Prefix definition derived from the constraint */ private boolean[] baseDefinition; /** Number of fixed terms in the base Definition */ private int fixedLength; /** The prefix definition defining the graph-tuples'-index */ private boolean[] prefixDefinition; /** * Number of terms defining which graph-tuples-index to use, * hence minimum length of prefix to beforeFirst */ private int boundLength; /** The prefix used to index into the graph-tuples. */ private long[] prefix; // /** Variable array */ // private Variable[] variables; /** mapping between variable index and tuples index. */ private int[] variableToColumn; /** * true if this tuples should appear to be empty because a query node exists * in the constraint. * TODO remove this when the statement store permits query nodes. */ private boolean isEmpty; /** Used to uniquely identify an equiv-class of StatementStoreResolution across clones. */ private final int id; /** * Find a graph index that satisfies a constraint. * * @param constraint the constraint to satisfy * @param store the store to resolve against * @throws IllegalArgumentException if <var>constraint</var> or <var>graph * </var> is <code>null</code> * @throws TuplesException EXCEPTION TO DO */ StatementStoreResolution(Constraint constraint, StatementStore store) throws TuplesException { if (logger.isDebugEnabled()) { logger.debug("Resolving constraint against statementStore: " + constraint); } this.constraint = constraint; this.store = store; // TODO remove this when the statement store permits query nodes. isEmpty = toGraphTuplesIndex(constraint.getElement(0)) < 0 || toGraphTuplesIndex(constraint.getElement(1)) < 0 || toGraphTuplesIndex(constraint.getElement(2)) < 0 || toGraphTuplesIndex(constraint.getModel()) < 0; baseDefinition = calculateBaseDefinition(constraint); fixedLength = calculatePrefixLength(baseDefinition); id = System.identityHashCode(baseDefinition); defineIndex(baseDefinition); } private static long calculateRowCount(Constraint constraint, StatementStore store) throws TuplesException { try { Tuples countTuples = store.findTuples(toGraphTuplesIndex(constraint.getElement(0)), toGraphTuplesIndex(constraint.getElement(1)), toGraphTuplesIndex(constraint.getElement(2)), toGraphTuplesIndex(constraint.getModel())); long rowCount = countTuples.getRowCount(); countTuples.close(); return rowCount; } catch (StatementStoreException es) { throw new TuplesException("Error accessing StatementStore", es); } } private static int calculateRowCardinality(Constraint constraint, StatementStore store) throws TuplesException { Tuples countTuples = null; int cardinality = 0; try { countTuples = store.findTuples(toGraphTuplesIndex(constraint.getElement(0)), toGraphTuplesIndex(constraint.getElement(1)), toGraphTuplesIndex(constraint.getElement(2)), toGraphTuplesIndex(constraint.getModel())); int rowCount = 0; countTuples.beforeFirst(); while (countTuples.next()) { if (++rowCount > 1) { break; } } switch (rowCount) { case 0: cardinality = Cursor.ZERO; break; case 1: cardinality = Cursor.ONE; break; default: cardinality = Cursor.MANY; } } catch (StatementStoreException es) { throw new TuplesException("Error accessing StatementStore", es); } catch (TuplesException te) { if (countTuples != null) { try { countTuples.close(); } catch (TuplesException e) { /* Already throwing an exception, so ignore */ } } throw te; } countTuples.close(); return cardinality; } private static long toGraphTuplesIndex(ConstraintElement constraintElement) throws TuplesException { if (constraintElement instanceof Variable) { return NodePool.NONE; } if (constraintElement instanceof LocalNode) { return ((LocalNode) constraintElement).getValue(); } throw new TuplesException("Unsupported constraint element: " + constraintElement + " (" + constraintElement.getClass() + ")"); } private static boolean[] calculateBaseDefinition(Constraint constraint) { return new boolean[] { constraint.getElement(0) instanceof LocalNode, constraint.getElement(1) instanceof LocalNode, constraint.getElement(2) instanceof LocalNode, constraint.getModel() instanceof LocalNode }; } private static int calculatePrefixLength(boolean[] definition) { int length = 0; for (int i = 0; i < definition.length; i++) { if (definition[i]) { length++; } } return length; } /** * @param bound constraints to be bound post-beforeFirst. In constraint-order. */ @SuppressWarnings("unchecked") protected void defineIndex(boolean[] bound) throws TuplesException { assert bound.length == 4; if (isEmpty) { setVariables(Collections.EMPTY_LIST); return; } boundLength = calculateBoundPrefixLength(bound); prefixDefinition = (boolean[])bound.clone(); if (indexedTuples != null) { if (logger.isDebugEnabled()) { logger.debug("Orig indexedTuples.variables = " + toString(indexedTuples.getVariables())); } indexedTuples.close(); } try { if (logger.isDebugEnabled()) { logger.debug("findingTuples for prefixDefinition(" + prefixDefinition[0] + ", " + prefixDefinition[1] + ", " + prefixDefinition[2] + ", " + prefixDefinition[3] + ") on " + id); } indexedTuples = store.findTuples(prefixDefinition[0], prefixDefinition[1], prefixDefinition[2], prefixDefinition[3]); columnOrder = ((StoreTuples)indexedTuples).getColumnOrder(); if (logger.isDebugEnabled()) { logger.debug("prefixDefinition = " + toString(prefixDefinition)); logger.debug("indexedTuples.variables = " + toString(indexedTuples.getVariables())); logger.debug("columnOrder = " + toString(columnOrder)); logger.debug("Column order returned " + toString(columnOrder)); } } catch (StatementStoreException es) { throw new TuplesException("findTuples failed", es); } prefix = new long[4]; List<Variable> variableList = new ArrayList<Variable>(); variableToColumn = new int[4]; int variableIndex = 0; // Note: baseDefinition is in 'constraint-order' // prefixDefinition is cloned from bound, hence in 'constraint-order' // constraint.getElement is in 'constraint-order' // prefix is in 'column-order' // variableList is in 'column-order' // columnOrder[] converts 'column-order' -> 'constraint-order' // variableToColumn converts 'variableIndex' -> 'columnIndex' // i is iterating over prefix, so is in 'column-order' if (logger.isDebugEnabled()) { logger.debug("Initialising prefix[] on " + id + " baseDefn = " + toString(baseDefinition) + " constraint = " + constraint + " prefixDefn = " + toString(prefixDefinition)); } for (int i = 0; i < columnOrder.length; i++) { if (baseDefinition[columnOrder[i]]) { if ((i > 0) && (prefix[i - 1] == 0)) { throw new TuplesException("Undefined hole in prefix returned from findTuples/4b. " + "Requested: " + toString(prefixDefinition) + " " + "Recvd: " + toString(columnOrder)); } long node = ((LocalNode)constraint.getElement(columnOrder[i])).getValue(); if (node <= 0) { throw new TuplesException( "Bad LocalNode in constraint. constraint.getElement(" + columnOrder[i] + ") returned a LocalNode with value: " + node + " constraint=" + constraint ); } prefix[i] = node; } else { Variable var = (Variable)constraint.getElement(columnOrder[i]); if (!var.equals(Variable.FROM)) { variableList.add(var); prefix[i] = prefixDefinition[columnOrder[i]] ? -1 : 0; variableToColumn[variableIndex++] = i; } } } if (variableIndex < variableToColumn.length) { // Resize variableToColumn. int[] v2c = new int[variableIndex]; System.arraycopy(variableToColumn, 0, v2c, 0, v2c.length); variableToColumn = v2c; } if (logger.isDebugEnabled()) { logger.debug("prefix defined = " + toString(prefix)); } setVariables(variableList); } private int calculateBoundPrefixLength(boolean[] bound) throws TuplesException { int boundLength = 0; for (int i = 0; i < 4; i++) { if (baseDefinition[i] && !bound[i]) { // Check prefix definition matches base definition derived from constraint. throw new TuplesException("index request dosn't match constraint"); } if (bound[i]) { boundLength++; } } assert boundLength >= fixedLength; return boundLength; } public ReresolvableResolution reresolve(final Map<? extends ConstraintElement, Long> bindings) throws TuplesException { boolean reconstrain = false; ConstraintElement[] e = new ConstraintElement[4]; for (int i = 0; i < 4; i++) { e[i] = constraint.getElement(i); if (e[i] instanceof Variable) { Long value = bindings.get(e[i]); if (value != null) { e[i] = new LocalNode(value.longValue()); reconstrain = true; } } } if (reconstrain) { ConstraintImpl newConstraint = new ConstraintImpl(e[0], e[1], e[2], e[3]); return new StatementStoreResolution(newConstraint, store); } else { return null; } } public void beforeFirst() throws TuplesException { beforeFirst(Tuples.NO_PREFIX, 0); } public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException { if (isEmpty) return; if (logger.isDebugEnabled()) { logger.debug("beforeFirst called on: " + TuplesOperations.tuplesSummary(this) + " with prefix: " + AbstractTuples.toString(prefix)); } if (prefix.length > 4) { throw new TuplesException("Prefix too long"); } long[] fullPrefix = calcFullPrefix(prefix); indexedTuples.beforeFirst(fullPrefix, suffixTruncation); } private long[] calcFullPrefix(long[] providedPrefix) throws TuplesException { long[] fullPrefix = new long[fixedLength + providedPrefix.length]; if (logger.isDebugEnabled()) { logger.debug("calcFullPrefix on " + TuplesOperations.tuplesSummary(this)); logger.debug("providedPrefix = " + toString(providedPrefix) + " on " + id); logger.debug("fullPrefix.length = " + fullPrefix.length); logger.debug("prefix = " + toString(prefix)); } if (fullPrefix.length < boundLength) { throw new TuplesException("Prefix failed to meet defined minimum prefix"); } int variableIndex = 0; for (int i = 0; i < fullPrefix.length; i++) { if (prefix[i] < -1) { throw new TuplesException("Query Node used in constraint."); } else if (prefix[i] > 0) { fullPrefix[i] = prefix[i]; } else { // Check for query nodes in the providedPrefix. // TODO remove this when the statement store permits query nodes. if (providedPrefix[variableIndex] < 0) { // A query node. Force a prefix that results in an empty set of // matching rows. return new long[] { Long.MAX_VALUE }; } fullPrefix[i] = providedPrefix[variableIndex++]; } } return fullPrefix; } public boolean next() throws TuplesException { return !isEmpty && indexedTuples.next(); } public long getColumnValue(int column) throws TuplesException { if (logger.isDebugEnabled()) { logger.debug("getColumnValue(" + column + ") on " + id + " with variables = " + toString(getVariables()) + " columnOrder = " + toString(columnOrder) + " var->col = " + toString(variableToColumn) + " constraint = " + constraint); } return indexedTuples.getColumnValue(variableToColumn[column]); } public List<Tuples> getOperands() { return Collections.emptyList(); } public long getRowCount() throws TuplesException { if (isEmpty) return 0; if (rowCount[0] == ROWCOUNT_UNCALCULATED) { rowCount[0] = calculateRowCount(constraint, store); } return rowCount[0]; } public long getRowUpperBound() throws TuplesException { return getRowCount(); } public long getRowExpectedCount() throws TuplesException { return getRowCount(); } public int getRowCardinality() throws TuplesException { if (isEmpty) return Cursor.ZERO; if (rowCardinality[0] == ROWCARD_UNCALCULATED) { long count = rowCount[0]; if (count != ROWCOUNT_UNCALCULATED) { rowCardinality[0] = count == 0 ? Cursor.ZERO : count == 1 ? Cursor.ONE : Cursor.MANY; } else { rowCardinality[0] = calculateRowCardinality(constraint, store); } } return rowCardinality[0]; } public boolean isColumnEverUnbound(int column) { return false; } public boolean hasNoDuplicates() { return true; } public void renameVariables(Constraint constraint) { throw new UnsupportedOperationException("We really don't want to do that"); } /** * Gets the Constraint attribute of the StatementStoreResolution object * * @return The Constraint value */ public Constraint getConstraint() { return constraint; } /** * Gets the store attribute of the StatementStoreResolution object * * @return The store */ public StatementStore getGraph() { return store; } public boolean isComplete() { return false; } /** * METHOD TO DO * * @return RETURNED VALUE TO DO */ public Object clone() { StatementStoreResolution cloned = (StatementStoreResolution)super.clone(); // constraint immutable // store immutable // rowCount native cloned.indexedTuples = (Tuples)indexedTuples.clone(); // columnOrder derived from indexedTuples, realloced if changed // baseDefinition derived from immutable constraint // fixedLength derived from baseDefinition // prefixDefinition realloced before write // boundLength native // prefix realloced before write return cloned; } public void close() throws TuplesException { if (indexedTuples != null) { indexedTuples.close(); } } public String toString() { return indexedTuples.toString() + " from constraint " + constraint; } protected StatementStoreResolution getSSR() { return this; } public Annotation getAnnotation(Class<? extends Annotation> annotation) { if (annotation == DefinablePrefixAnnotation.class) { return new DefinablePrefixAnnotation() { public void definePrefix(Set<Variable> boundVars) throws TuplesException { boolean[] bound = new boolean[4]; Constraint constraint = getConstraint(); for (int i = 0; i < 4; i++) { ConstraintElement elem = constraint.getElement(i); if (elem instanceof LocalNode) { bound[i] = true; } else if (boundVars.contains(elem)) { bound[i] = true; } else { bound[i] = false; } } if (logger.isDebugEnabled()) { logger.debug("Tuples: " + TuplesOperations.tuplesSummary(getSSR())); logger.debug("binding definition = " + AbstractTuples.toString(bound)); } defineIndex(bound); } }; } else { return null; } } }