/* * StreamCruncher: Copyright (c) 2006-2008, Ashwin Jayaprakash. All Rights Reserved. * Contact: ashwin {dot} jayaprakash {at} gmail {dot} com * Web: http://www.StreamCruncher.com * * This file is part of StreamCruncher. * * StreamCruncher is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * StreamCruncher 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with StreamCruncher. If not, see <http://www.gnu.org/licenses/>. */ package streamcruncher.innards.core.partition.function; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.PriorityQueue; import java.util.Set; import streamcruncher.api.aggregator.AbstractAggregator; import streamcruncher.api.artifact.RowSpec; import streamcruncher.innards.core.QueryContext; import streamcruncher.innards.core.partition.CalculateTSFunctionPair; import streamcruncher.innards.core.partition.Row; import streamcruncher.innards.core.partition.RowBuffer; import streamcruncher.innards.util.AggregatorHolder; import streamcruncher.innards.util.SourceToTargetMapper; import streamcruncher.util.AppendOnlyPrimitiveLongList; import streamcruncher.util.AtomicX; import streamcruncher.util.PerpetualResultSet; /* * Author: Ashwin Jayaprakash Date: Feb 26, 2006 Time: 9:43:16 PM */ public class AggregateFunction extends Function { protected final Function innerFunction; protected final int[] sourceLocationForTargetCols; protected final AggregatorHolder[] holders; protected final HashMap<Long, Object[]> rowIdAndAggregatedColumns; protected final int sourceIdColumnBufferPos; protected final int finalIdColumnBufferPos; protected final int finalTimestampColumnBufferPos; protected final int finalVersionColumnBufferPos; protected Long currentRowId; protected Object[] currentRowValues; /** * @param realTableRowSpec * @param finalTableRowSpec * @param rowIdGenerator * @param triples * @param innerFunction * @param pinned */ public AggregateFunction(RowSpec realTableRowSpec, RowSpec finalTableRowSpec, AtomicX rowIdGenerator, AggregatorHolder[] triples, Function innerFunction, boolean pinned) { super(realTableRowSpec, finalTableRowSpec, rowIdGenerator); this.innerFunction = innerFunction; this.innerFunction.setHomeFunction(this); this.innerFunction.setPinned(pinned); this.sourceLocationForTargetCols = new SourceToTargetMapper(realTableRowSpec, finalTableRowSpec).map(); this.holders = triples; this.rowIdAndAggregatedColumns = new HashMap<Long, Object[]>(); this.sourceIdColumnBufferPos = realTableRowSpec.getIdColumnPosition(); this.finalIdColumnBufferPos = finalTableRowSpec.getIdColumnPosition(); this.finalVersionColumnBufferPos = finalTableRowSpec.getVersionColumnPosition(); this.finalTimestampColumnBufferPos = finalTableRowSpec.getTimestampColumnPosition(); } /** * {@inheritDoc} */ @Override public void setDirtyFunctions(Set<Function> dirtyFunctions) { super.setDirtyFunctions(dirtyFunctions); innerFunction.setDirtyFunctions(dirtyFunctions); } /** * {@inheritDoc} */ @Override public void setUnprocessedDataFunctions(Set<Function> unprocessedFunctions) { super.setUnprocessedDataFunctions(unprocessedFunctions); innerFunction.setUnprocessedDataFunctions(unprocessedFunctions); } /** * {@inheritDoc} */ @Override public void setCalculateTSFunctionPairs( PriorityQueue<CalculateTSFunctionPair> calculateTSFunctionPairs) { super.setCalculateTSFunctionPairs(calculateTSFunctionPairs); innerFunction.setCalculateTSFunctionPairs(calculateTSFunctionPairs); } /** * @return Returns the innerFunction. */ public Function getInnerFunction() { return innerFunction; } // -------------------- @Override public void onCalculate(QueryContext context) { innerFunction.calculate(context); } @Override protected void onCycleStart(QueryContext context) throws Exception { innerFunction.cycleStart(context); } @Override protected void process(QueryContext context, PerpetualResultSet currRow) throws Exception { innerFunction.process(context, currRow); } @SuppressWarnings("unchecked") @Override protected boolean onCycleEnd(QueryContext context) throws Exception { boolean noActionRequired = innerFunction.cycleEnd(context); // -------------------- AppendOnlyPrimitiveLongList longList = innerFunction.getOustedRowIds(); List<Object[]> removedValues = (longList.getSize() > 0) ? new ArrayList<Object[]>(longList .getSize()) : null; while (longList.getSize() > 0) { Object[] removedColValues = rowIdAndAggregatedColumns.remove(longList.remove()); removedValues.add(removedColValues); } // -------------------- RowBuffer buffer = innerFunction.getProcessedRowBuffer(); List<Row> processed = buffer.getRows(); List<Object[]> addedValues = (processed.size() > 0) ? new ArrayList<Object[]>(processed .size()) : null; /* * Add only if we are not going to be discarded. Otherwise, don't * bother. */ if (noActionRequired == false) { for (Row row : processed) { Object[] sourceColumnValues = row.getColumns(); // Source Row's Id. Long rowId = ((Number) sourceColumnValues[sourceIdColumnBufferPos]).longValue(); rowIdAndAggregatedColumns.put(rowId, sourceColumnValues); addedValues.add(sourceColumnValues); // Copy the unchanged columns. if (currentRowValues == null) { String[] columnNames = finalTableRowSpec.getColumnNames(); Object[] columns = new Object[columnNames.length]; // Copy the values. Aggregates will be produced later. for (int i = 1; i < sourceLocationForTargetCols.length; i++) { if (sourceLocationForTargetCols[i] > -1) { columns[i] = sourceColumnValues[sourceLocationForTargetCols[i]]; } } currentRowValues = columns; currentRowValues[finalIdColumnBufferPos] = 0L; currentRowValues[finalTableRowSpec.getTimestampColumnPosition()] = 0L; currentRowValues[finalTableRowSpec.getVersionColumnPosition()] = 0L; } } } processed.clear(); // -------------------- if (removedValues != null || addedValues != null) { if (noActionRequired == false) { boolean someAggregateChanged = false; Object[] newAggregates = new Object[holders.length]; for (int i = 0; i < holders.length; i++) { AbstractAggregator aggregator = holders[i].getAggregator(); newAggregates[i] = aggregator.aggregate(removedValues, addedValues); int pos = holders[i].getTargetColumnPos(); if (currentRowValues[pos] == null || newAggregates[i] == null || newAggregates[i].equals(currentRowValues[pos]) == false) { someAggregateChanged = true; } } /* * If the aggregate value does not change, then it should not be * re-written. */ if (someAggregateChanged) { if (currentRowId != null) { oustedRowIds.add(currentRowId); currentRowId = null; } Row newRow = processedRowBuffer.addNewRowWithAutoValues(context.getRunCount()); Object[] columns = newRow.getColumns(); /* * Number could be Integer or Long, from AtomicX. */ currentRowId = ((Number) columns[finalIdColumnBufferPos]).longValue(); for (int i = 0; i < currentRowValues.length; i++) { // Don't overwrite auto-values. if (i == finalIdColumnBufferPos || i == finalTimestampColumnBufferPos || i == finalVersionColumnBufferPos) { continue; } columns[i] = currentRowValues[i]; } for (int i = 0; i < holders.length; i++) { int pos = holders[i].getTargetColumnPos(); currentRowValues[pos] = newAggregates[i]; columns[pos] = newAggregates[i]; } } } else { if (currentRowId != null) { oustedRowIds.add(currentRowId); currentRowId = null; } } } // -------------------- return noActionRequired; } }