/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.qp.operator; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.GroupIndex; import com.foundationdb.ais.model.Sequence; import com.foundationdb.ais.model.TableIndex; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.storeadapter.Sorter; import com.foundationdb.qp.storeadapter.indexcursor.IterationHelper; import com.foundationdb.qp.row.IndexRow; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.rowtype.Schema; import com.foundationdb.server.service.session.Session; import com.foundationdb.server.service.tree.KeyCreator; import com.foundationdb.server.store.Store; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.util.Strings; import com.foundationdb.util.tap.InOutTap; import org.junit.Assert; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public final class OperatorTestHelper { // OperatorTestHelper interface public static void check(Operator plan, Collection<? extends Row> expecteds, RowCheck additionalCheck) { List<Row> actuals = execute(plan); if (expecteds.size() != actuals.size()) { assertEquals("output", Strings.join(expecteds), Strings.join(actuals)); assertEquals("size (expecteds=" + expecteds+", actuals=" + actuals + ')', expecteds.size(), actuals.size()); } int rowCount = 0; Iterator<? extends Row> expectedsIter = expecteds.iterator(); for (Row actual : actuals) { Row expected = expectedsIter.next(); int actualWidth = actual.rowType().nFields(); assertEquals("row width", expected.rowType().nFields(), actualWidth); for (int i = 0; i < actualWidth; ++i) { checkRowInstance(expected, actual, i, rowCount, actuals, expecteds); } if (additionalCheck != null) additionalCheck.check(actual); ++rowCount; } } private static void checkRowInstance(Row expected, Row actual, int i, int rowCount, List<Row> actuals, Collection<? extends Row> expecteds) { ValueSource actualSource = actual.value(i); ValueSource expectedSource = expected.value(i); TInstance actualType = actual.rowType().typeAt(i); TInstance expectedType = expected.rowType().typeAt(i); if (actualType == null || expectedType == null) { assert actualSource.isNull() && expectedSource.isNull(); return; } assertTrue(expectedType + " != " + actualType, expectedType.equalsExcludingNullable(actualType)); if(!TClass.areEqual(actualSource, expectedSource) && !(actualSource.isNull() && expectedSource.isNull())) { Assert.assertEquals( String.format("row[%d] field[%d]", rowCount, i), str(expecteds), str(actuals)); assertEquals(String.format("row[%d] field[%d]", rowCount, i), expectedSource, actualSource); throw new AssertionError("should have failed by now!"); } } public static void check(Operator plan, Collection<? extends Row> expecteds) { check(plan, expecteds, null); } public static Cursor open(Operator plan) { QueryContext queryContext = new SimpleQueryContext(ADAPTER); QueryBindings queryBindings = queryContext.createBindings(); QueryBindingsCursor queryBindingsCursor = new SingletonQueryBindingsCursor(queryBindings); Cursor result = plan.cursor(queryContext, queryBindingsCursor); reopen(result); return result; } public static void reopen(Cursor cursor) { cursor.openTopLevel(); } public static List<Row> execute(Operator plan) { List<Row> rows = new ArrayList<>(); Cursor cursor = open(plan); try { for(Row row = cursor.next(); row != null; row = cursor.next()) { rows.add(row); } return rows; } finally { cursor.close(); } } public static Schema schema() { return new Schema(new com.foundationdb.ais.model.AkibanInformationSchema()); } // for use in this class private static String str(Collection<? extends Row> rows) { return Strings.join(rows); } private OperatorTestHelper() {} // "const"s static final TestAdapter ADAPTER = new TestAdapter(); // nested classes public interface RowCheck { void check(Row row); } private static class TestAdapter extends StoreAdapter { @Override public GroupCursor newGroupCursor(Group group) { throw new UnsupportedOperationException(); } @Override public Cursor newIndexCursor(QueryContext context, IndexRowType indexType, IndexKeyRange keyRange, API.Ordering ordering, IndexScanSelector selector, boolean openAllSubCursors) { throw new UnsupportedOperationException(); } @Override protected Store getUnderlyingStore() { throw new UnsupportedOperationException(); } @Override public void updateRow(Row oldRow, Row newRow) { throw new UnsupportedOperationException(); } @Override public void writeRow(Row newRow, Collection<TableIndex> indexes, Collection<GroupIndex> groupIndexes) { throw new UnsupportedOperationException(); } @Override public void deleteRow(Row oldRow, boolean cascadeDefault) { throw new UnsupportedOperationException(); } @Override public Sorter createSorter(QueryContext context, QueryBindings bindings, RowCursor input, RowType rowType, API.Ordering ordering, API.SortOption sortOption, InOutTap loadTap) { throw new UnsupportedOperationException(); } @Override public long getQueryTimeoutMilli() { return -1; } @Override public long rowCount(Session session, RowType tableType) { throw new UnsupportedOperationException(); } @Override public long sequenceNextValue(Sequence sequence) { throw new UnsupportedOperationException(); } @Override public long sequenceCurrentValue(Sequence sequence) { throw new UnsupportedOperationException(); } @Override public IndexRow takeIndexRow(IndexRowType indexRowType) { throw new UnsupportedOperationException(); } @Override public void returnIndexRow(IndexRow indexRow) { throw new UnsupportedOperationException(); } @Override public IterationHelper createIterationHelper(IndexRowType indexRowType) { throw new UnsupportedOperationException(); } public TestAdapter() { super(null, null); } @Override public IndexRow newIndexRow(IndexRowType indexRowType) { throw new UnsupportedOperationException(); } @Override public KeyCreator getKeyCreator() { throw new UnsupportedOperationException(); } @Override public AkibanInformationSchema getAIS() { throw new UnsupportedOperationException(); } } }