package edu.washington.escience.myria.operator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import edu.washington.escience.myria.Schema; import edu.washington.escience.myria.Type; import edu.washington.escience.myria.column.Column; import edu.washington.escience.myria.storage.MutableTupleBuffer; import edu.washington.escience.myria.storage.TupleBatch; import edu.washington.escience.myria.storage.TupleBatchBuffer; /** * Orders tuples in memory. * * Note: not efficient so only use it in tests */ public final class InMemoryOrderBy extends UnaryOperator { /** Required for Java serialization. */ private static final long serialVersionUID = 1L; /** * Which columns to sort the tuples by. */ private final int[] sortColumns; /** * True for each column that should be sorted ascending. */ private final boolean[] ascending; /** * Buffers tuples until they are all returned. */ private TupleBatchBuffer ans; /** * Tuple data stored as columns until it is sorted. */ private MutableTupleBuffer table; /** * @param child the source of the tuples. */ public InMemoryOrderBy(final Operator child) { this(child, null, null); } /** * A list of indexes into columns that defines the sort order. */ private ArrayList<Integer> indexes; /** * @param child the source of the tuples. * @param sortColumns the columns that should be ordered by * @param ascending true for each column that should be sorted ascending */ public InMemoryOrderBy(final Operator child, final int[] sortColumns, final boolean[] ascending) { super(child); this.sortColumns = sortColumns; this.ascending = ascending; } @Override protected void init(final ImmutableMap<String, Object> execEnvVars) throws Exception { Preconditions.checkArgument(sortColumns.length == ascending.length); ans = new TupleBatchBuffer(getSchema()); table = new MutableTupleBuffer(getSchema()); } @Override protected TupleBatch fetchNextReady() throws Exception { TupleBatch nexttb = ans.popFilled(); if (nexttb != null) { return nexttb; } else if (ans.numTuples() > 0) { return ans.popAny(); } if (table.numTuples() > 0 && getChild().eos()) { setEOS(); return null; } while (!getChild().eos()) { TupleBatch tb = getChild().nextReady(); if (tb != null) { for (int row = 0; row < tb.numTuples(); ++row) { List<? extends Column<?>> inputColumns = tb.getDataColumns(); for (int column = 0; column < tb.numColumns(); ++column) { table.put(column, inputColumns.get(column), row); } } } else if (!getChild().eos()) { return null; } } Preconditions.checkState(getChild().eos()); sort(); nexttb = ans.popFilled(); if (nexttb == null && ans.numTuples() > 0) { return ans.popAny(); } return nexttb; } /** * Comparator for tuples made up of columns. */ class TupleComparator implements Comparator<Integer> { @Override public int compare(final Integer rowIdx, final Integer otherRowIdx) { int i = 0; for (int columnIdx : sortColumns) { int compared = 0; switch (getSchema().getColumnType(columnIdx)) { case INT_TYPE: compared = Type.compareRaw( table.getInt(columnIdx, rowIdx), table.getInt(columnIdx, otherRowIdx)); break; case FLOAT_TYPE: compared = Type.compareRaw( table.getFloat(columnIdx, rowIdx), table.getFloat(columnIdx, otherRowIdx)); break; case LONG_TYPE: compared = Type.compareRaw( table.getLong(columnIdx, rowIdx), table.getLong(columnIdx, otherRowIdx)); break; case DOUBLE_TYPE: compared = Type.compareRaw( table.getDouble(columnIdx, rowIdx), table.getDouble(columnIdx, otherRowIdx)); break; case BOOLEAN_TYPE: compared = Type.compareRaw( table.getBoolean(columnIdx, rowIdx), table.getBoolean(columnIdx, otherRowIdx)); break; case STRING_TYPE: compared = Type.compareRaw( table.getString(columnIdx, rowIdx), table.getString(columnIdx, otherRowIdx)); break; case DATETIME_TYPE: compared = Type.compareRaw( table.getDateTime(columnIdx, rowIdx), table.getDateTime(columnIdx, otherRowIdx)); break; case BLOB_TYPE: compared = Type.compareRaw( table.getBlob(columnIdx, rowIdx), table.getBlob(columnIdx, otherRowIdx)); break; } if (compared != 0) { if (ascending[i]) { return compared; } else { return -compared; } } i++; } return 0; } } /** * Sorts the tuples. First, we get an array of indexes by which we sort the data. Then we actually reorder the rows. */ public void sort() { final int numTuples = table.numTuples(); indexes = new ArrayList<>(); indexes.ensureCapacity(numTuples); for (int i = 0; i < numTuples; i++) { indexes.add(i); } TupleComparator comparator = new TupleComparator(); Collections.sort(indexes, comparator); for (int rowIdx = 0; rowIdx < numTuples; rowIdx++) { ans.append(table, indexes.get(rowIdx)); } } @Override protected Schema generateSchema() { Operator child = getChild(); if (child == null) { return null; } return child.getSchema(); } }