/* * 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): N/A. * * [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; // Java 2 standard packages import java.util.*; // Third party packages import org.apache.log4j.*; // Locally written packages import org.mulgara.query.*; import org.mulgara.query.rdf.LiteralImpl; import org.mulgara.resolver.spi.LocalizeException; import org.mulgara.resolver.spi.ResolverSession; import org.mulgara.store.tuples.AbstractTuples; import org.mulgara.store.tuples.Tuples; import org.mulgara.util.StackTrace; /** * Wrapper around a partially-evaluated {@link Tuples} instance, evaluating * atomic-valued aggregate functions such as {@link Count}. * * @created 2004-02-26 * * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a> * * @version $Revision: 1.9 $ * * @modified $Date: 2005/02/22 08:16:06 $ * * @maintenanceAuthor $Author: newmana $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright © 2001-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 AppendAggregateTuples extends AbstractTuples { private final static Logger logger = Logger.getLogger(AppendAggregateTuples.class.getName()); /** * The aggregate functions extracted from the <code>SELECT</code> clause. */ private List<Query> queryList; /** * Whether the corresponding index of this instance is an index into * the {@link #queryList} (if <code>true</code>) or the wrapped * {@link #tuples} (if <code>false</code>). */ private boolean[] columnIsAggregate; private int[] columnAggregateIndex; /** The session to localize into. */ private ResolverSession session; /** The wrapped {@link Answer} instance. */ private Tuples tuples; /** Cache of values calculated for the current row. */ private long[] cache; /** Whether the {@link #cache} is valid for the current row. */ private boolean[] isCacheValid; /** Exists *only* so we can call resolveMap() */ private LocalQueryResolver context; /** * Wrap an {@link Answer} instance. * * @param session the session against which to evaluate aggregate * functions * @param tuples the {@link Tuples} to wrap * @param variableList the <code>SELECT</code> clause containing the * aggregate functions to evaluate * @throws IllegalArgumentException if <var>session</var> or * <var>tuples</var> are * <code>null</code> * @throws TuplesException if there's trouble reading <var>tuples</var> */ AppendAggregateTuples(ResolverSession session, LocalQueryResolver context, Tuples tuples, List<? extends SelectElement> variableList) throws TuplesException { if (logger.isDebugEnabled()) { logger.debug("Generating variable list for " + tuples + " and " + variableList); logger.debug("AppendAggregateTuples instantiated " + hashCode()); } if (session == null) { throw new IllegalArgumentException("Null \"session\" parameter"); } if (tuples == null) { throw new IllegalArgumentException("Null \"tuples\" parameter"); } // Initialize fields this.context = context; this.columnIsAggregate = new boolean[variableList.size()]; this.columnAggregateIndex = new int[variableList.size()]; this.session = session; this.tuples = (Tuples) tuples.clone(); // Prep variable list Variable[] tupleVars = this.tuples.getVariables(); // the size of variableList may be larger due to repeated variables, // while the size of the tuples may be larger due to aggregates int uniqueTupleVarLen = Math.min(variableList.size(), tupleVars.length); Set<Variable> newVariableList = new LinkedHashSet<Variable>(); for (int i = 0; i < uniqueTupleVarLen; i++) { assert variableList.contains(tupleVars[i]); newVariableList.add(tupleVars[i]); columnAggregateIndex[i] = -1; if (logger.isDebugEnabled()) { logger.debug("" + hashCode() + " columnAggregateIndex[" + i + "] = -1"); } columnIsAggregate[i] = false; } if (logger.isDebugEnabled()) { logger.debug("" + hashCode() + " tupleVars.length = " + tupleVars.length + " unique vars = " + uniqueTupleVarLen); } // Calculate the rest of the variable list int aggregateIndex = 0; queryList = new ArrayList<Query>(); for (int i = 0; i < variableList.size(); i++) { Object element = variableList.get(i); if (element instanceof Count) { columnAggregateIndex[uniqueTupleVarLen + aggregateIndex] = aggregateIndex; if (logger.isDebugEnabled()) { logger.debug("" + hashCode() + " columnAggregateIndex[" + uniqueTupleVarLen + aggregateIndex + "] = " + aggregateIndex); } newVariableList.add(((Count)element).getVariable()); columnIsAggregate[uniqueTupleVarLen + aggregateIndex] = true; aggregateIndex++; Query query = ((Count)element).getQuery(); queryList.add((Query)query.clone()); } } if (logger.isDebugEnabled()) { logger.info("Generated variable list " + newVariableList); } setVariables(new ArrayList<Variable>(newVariableList)); if (logger.isDebugEnabled()) { logger.debug("Set variable list " + Arrays.asList(getVariables())); } // Initialize cache fields dependent on queryList cache = new long[queryList.size()]; isCacheValid = new boolean[cache.length]; } // // Methods implementing AbstractTuples // public void beforeFirst() throws TuplesException { tuples.beforeFirst(); } public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException { if (prefix.length == 0 && suffixTruncation == 0) { beforeFirst(); } else { throw new TuplesException( "AppendAggregateTuples.beforeFirst not implemented" ); } } public Object clone() { AppendAggregateTuples cloned = (AppendAggregateTuples)super.clone(); cloned.session = session; cloned.columnIsAggregate = cloned.columnIsAggregate; cloned.tuples = (Tuples) tuples.clone(); cloned.cache = (long[]) cache.clone(); cloned.isCacheValid = (boolean[]) isCacheValid.clone(); cloned.queryList = new ArrayList<Query>(); for (Query query : queryList) { cloned.queryList.add((Query)query.clone()); } if (logger.isDebugEnabled()) { logger.debug("AppendAggregateTuples clone " + cloned.hashCode() + " from " + hashCode()); } return cloned; } public void close() throws TuplesException { if (logger.isDebugEnabled()) { logger.debug("closing AppendAggregateTuples " + hashCode() + "\n" + new StackTrace()); } for (Query query : queryList) { if (logger.isDebugEnabled()) { logger.debug("AppendAggregateTuples " + hashCode() + " closing Query " + query.hashCode()); } query.close(); } tuples.close(); } public long getColumnValue(int column) throws TuplesException { if (logger.isDebugEnabled()) { logger.debug("AppendAggregateTuples " + hashCode() + ": getting column " + column); } int index = columnAggregateIndex[column]; if (index < 0) { return tuples.getColumnValue(column); } if (logger.isDebugEnabled()) { logger.debug("" + hashCode() + " Column is an aggregate"); } if (!isCacheValid[index]) { try { // Add the values of the current row to the WHERE clause of the // aggregate function's query Query query = queryList.get(index); if (logger.isDebugEnabled()) { logger.debug("" + hashCode() + " Base aggregate query: " + query); } // Evaluate the aggregate query Tuples tuples = context.resolveMap(query, createBindingMap(this.tuples)); if (logger.isDebugEnabled()) { logger.debug("Resolved aggregate to " + tuples); } if (logger.isDebugEnabled()) { logger.debug("Row count = " + tuples.getRowCount()); } cache[index] = session.localize(new LiteralImpl(tuples.getRowCount())); isCacheValid[index] = true; tuples.close(); } catch (LocalizeException le) { throw new TuplesException("Error localising subquery", le); } catch (QueryException e) { throw new TuplesException("Couldn't evaluate aggregate function", e); } catch (RuntimeException re) { logger.error("RuntimeException thrown in " + hashCode(), re); throw re; } } // Return the evaluated column value assert isCacheValid[index]; return cache[index]; } private Map<Variable,Value> createBindingMap(Tuples tuples) throws TuplesException { Map<Variable,Value> bindings = new HashMap<Variable,Value>(); Variable[] vars = tuples.getVariables(); for (int i = 0; i < columnIsAggregate.length; i++) { if (!columnIsAggregate[i]) { long columnValue = tuples.getColumnValue(i); if (columnValue != Tuples.UNBOUND) { bindings.put(vars[i], new LocalNode(columnValue)); } } } return bindings; } public long getRowCount() throws TuplesException { return tuples.getRowCount(); } public long getRowUpperBound() throws TuplesException { return tuples.getRowUpperBound(); } public long getRowExpectedCount() throws TuplesException { return tuples.getRowExpectedCount(); } public int getRowCardinality() throws TuplesException { return tuples.getRowCardinality(); } public boolean isEmpty() throws TuplesException { return tuples.isEmpty(); } /** * @return the same value as the source column, or <code>true</code> in the * case of appended aggregate columns (we have no certain way of knowing * that the aggregate function is defined for the input row) */ public boolean isColumnEverUnbound(int column) throws TuplesException { int index = columnAggregateIndex[column]; if (index < 0) { return tuples.isColumnEverUnbound(column); } else { return true; } } public boolean hasNoDuplicates() throws TuplesException { return tuples.hasNoDuplicates(); } public List<Tuples> getOperands() { return Collections.singletonList(tuples); } public boolean next() throws TuplesException { // The current row is changing, so the cache array will no longer be valid Arrays.fill(isCacheValid, false); return tuples.next(); } }