/** * 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.server.test.costmodel; import com.foundationdb.qp.operator.Cursor; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.operator.TimeOperator; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.rowtype.Schema; import com.foundationdb.qp.rowtype.TableRowType; import com.foundationdb.qp.util.SchemaCache; import com.foundationdb.server.error.InvalidOperationException; import com.foundationdb.server.types.texpressions.TPreparedBoundField; import com.foundationdb.server.types.texpressions.TPreparedExpression; import com.foundationdb.server.types.texpressions.TPreparedField; import org.junit.Test; import java.util.*; import static com.foundationdb.qp.operator.API.*; public class Select_HashTableCT extends CostModelBase { static int ROW_BINDING_POSITION = 100; static int TABLE_BINDING_POSITION = 200; @Test public void run() throws Exception { createSchema(); populateDB(); drivingRowTypes = new TableRowType[] { d1RowType,d2RowType,d3RowType,d4RowType,d5RowType}; hashedRowTypes = new TableRowType[] { h1RowType,h2RowType,h3RowType,h4RowType,h5RowType}; drivingTableIDs = new int[] {d1,d2,d3,d4,d5}; hashedTableIDs = new int[] {h1,h2,h3,h4,h5}; // Load-only run(MEASURED_RUNS, true, true, 2, 1); System.out.println("********************LOADING TESTS********************"); for(int columns = 2; columns <= 6; columns++) { for (int joins = 1; joins < columns; joins++) { run(MEASURED_RUNS, true, true, columns, joins); } } //Load and Join run(MEASURED_RUNS, false, true, 2, 1); System.out.println("********************FULL TESTS********************"); for(int columns = 2; columns <= 6; columns++) { for (int joins = 1; joins < columns; joins++) { System.gc(); run(MEASURED_RUNS, false, true, columns, joins); } } } private void createSchema() throws InvalidOperationException { // Schema is similar to that in Select_BloomHashTableIT String schemaName = schemaName(); String d1TableName = newTableName(); String d2TableName = newTableName(); String d3TableName = newTableName(); String d4TableName = newTableName(); String d5TableName = newTableName(); String h1TableName = newTableName(); String h2TableName = newTableName(); String h3TableName = newTableName(); String h4TableName = newTableName(); String h5TableName = newTableName(); String emptyTable = newTableName(); d1 = createTable( schemaName, d1TableName, "x int"); h1 = createTable( schemaName, h1TableName, "x int"); d2 = createTable( schemaName, d2TableName, "x int", "y int"); h2 = createTable( schemaName, h2TableName, "x int", "y int"); d3 = createTable( schemaName, d3TableName, "x int", "y int", "z int"); h3 = createTable( schemaName, h3TableName, "x int", "y int", "z int"); d4 = createTable( schemaName, d4TableName, "x int", "y int", "z int", "a int"); h4 = createTable( schemaName, h4TableName, "x int", "y int", "z int", "a int"); d5 = createTable( schemaName, d5TableName, "x int", "y int", "z int", "a int", "b int"); h5 = createTable( schemaName, h5TableName, "x int", "y int", "z int", "a int", "b int"); emptyTableID = createTable( schemaName, emptyTable, "x int", "y int", "z int", "a int", "b int"); schema = SchemaCache.globalSchema(ais()); d1RowType = schema.tableRowType(table(d1)); h1RowType = schema.tableRowType(table(h1)); d2RowType = schema.tableRowType(table(d2)); h2RowType = schema.tableRowType(table(h2)); d3RowType = schema.tableRowType(table(d3)); h3RowType = schema.tableRowType(table(h3)); d4RowType = schema.tableRowType(table(d4)); h4RowType = schema.tableRowType(table(h4)); d5RowType = schema.tableRowType(table(d5)); h5RowType = schema.tableRowType(table(h5)); emptyTableRowType = schema.tableRowType(table(emptyTableID)); adapter = newStoreAdapter(); queryContext = queryContext(adapter); queryBindings = queryContext.createBindings(); } /** x+=2 gives it a 50% match rate **/ protected void populateDB() { int multiples[] = {53,97,193,389,769}; for (int x = 0; x < HASHED_ROWS[0]/2; x++) { writeRow(h1, x, x * multiples[0]); // x, hidden_pk writeRow(h1, x, x * multiples[0]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[0]; x +=2) { writeRow(d1, x, x * multiples[0]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[1]/2; x++) { writeRow(h2, x, x * multiples[0], x * multiples[1]); // x, hidden_pk writeRow(h2, x, x * multiples[0], x * multiples[1]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[1]; x +=2) { writeRow(d2, x, x * multiples[0], x * multiples[1]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[2]/2; x++) { writeRow(h3, x, x * multiples[0], x * multiples[1], x * multiples[2]); // x, hidden_pk writeRow(h3, x, x * multiples[0], x * multiples[1], x * multiples[2]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[2]; x +=2) { writeRow(d3, x, x * multiples[0], x * multiples[1], x * multiples[2]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[3]/2; x++) { writeRow(h4, x, x * multiples[0], x * multiples[1], x * multiples[2], x * multiples[3]); // x, hidden_pk writeRow(h4, x, x * multiples[0], x * multiples[1], x * multiples[2], x * multiples[3]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[3]; x +=2) { writeRow(d4, x, x * multiples[0], x * multiples[1], x * multiples[2], x * multiples[3]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[4] /2; x++) { writeRow(h5, x, x * multiples[0], x * multiples[1], x * multiples[2], x * multiples[3], x * multiples[4]); // x, hidden_pk writeRow(h5, x, x * multiples[0], x * multiples[1], x * multiples[2], x * multiples[3], x * multiples[4]); // x, hidden_pk } for (int x = 0; x < HASHED_ROWS[4] * 2; x += 2 ){ writeRow(d5, x, x * multiples[0], x * multiples[1], x * multiples[2], x * multiples[3], x * multiples[4]); // x, hidden_pk } } private void run(int runs, boolean loadOnly, boolean report, int columnCount, int joinColumns) { Operator plan = loadOnly ? planLoadOnly(columnCount, joinColumns) : planLoadAndSelect(columnCount, joinColumns); Row row; long start = System.nanoTime(); for (int r = 0; r < runs; r++) { Cursor cursor = cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); while ((row = cursor.next()) != null) { } } long stop = System.nanoTime(); long planNsec = stop - start; if (loadOnly) { planNsec -= (innerTimeOperator.elapsedNsec() + outerTimeOperator.elapsedNsec()); double averageUsecPerRow = planNsec / (1000.0 * runs * HASHED_ROWS[columnCount - 2]); if (report) { System.out.println(String.format("\nload only: joined on %d out of %d columns: %2.5s usec/row", joinColumns, columnCount, averageUsecPerRow)); } } else { planNsec -= (innerTimeOperator.elapsedNsec() + outerTimeOperator.elapsedNsec()); double averageUsecPerRow = planNsec / (1000.0 * runs * HASHED_ROWS[columnCount - 2]); if (report) { System.out.println(String.format("\nload & join: joined on %d out of %d columns: %2.5s usec/row", joinColumns, columnCount, averageUsecPerRow)); } } } public Operator planLoadOnly(int columnCount, int joinColumns) { RowType innerRowType = hashedRowTypes[columnCount -2]; RowType outerRowType = emptyTableRowType; // filterInput loads the filter with F rows containing the given testId. Operator innerStream = new TimeOperator(groupScan_Default(innerRowType.table().getGroup())); Operator outerStream = groupScan_Default(outerRowType.table().getGroup()); // For the index scan retrieving rows from the F(x) index given a D index row // Use a bloom filter loaded by filterInput. Then for each input row, check the filter (projecting // D rows on (x)), and, for positives, check F using an index scan keyed by D.x. int joinFields[] = new int[joinColumns]; for(int i = 0; i <joinColumns; i++) { joinFields[i] = i; } return hashJoinPlan(outerRowType, innerRowType,outerStream,innerStream, joinFields, joinFields); } public Operator planLoadAndSelect(int columnCount, int joinColumns) { RowType innerRowType = hashedRowTypes[columnCount - 2]; RowType outerRowType = drivingRowTypes[columnCount -2]; // filterInput loads the filter with F rows containing the given testId. Operator innerStream = groupScan_Default(innerRowType.table().getGroup()); Operator outerStream = groupScan_Default(outerRowType.table().getGroup()); int joinFields[] = new int[joinColumns]; for(int i = 0; i <joinColumns; i++) { joinFields[i] = i; } return hashJoinPlan(outerRowType, innerRowType, outerStream, innerStream, joinFields, joinFields); } private Operator hashJoinPlan( RowType outerRowType, RowType innerRowType, Operator outerStream, Operator innerStream, int outerJoinFields[], int innerJoinFields[]) { List<TPreparedExpression> expressions = new ArrayList<>(); for( int i = 0; i < outerRowType.nFields(); i++){ expressions.add(new TPreparedBoundField(outerRowType, ROW_BINDING_POSITION, i)); } for( int i = 0, j = 0; i < innerRowType.nFields(); i++){ if(j < innerJoinFields.length && innerJoinFields[j] == i) { j++; }else{ expressions.add(new TPreparedField(innerRowType.typeAt(i), i)); } } List<TPreparedExpression> outerExpressions = new ArrayList<>(); for (int i : outerJoinFields){ outerExpressions.add(new TPreparedBoundField(outerRowType, ROW_BINDING_POSITION, i)); } List<TPreparedExpression> innerExpressions = new ArrayList<>(); for(int i : innerJoinFields){ innerExpressions.add(new TPreparedField(innerRowType.typeAt(i), i)); } innerTimeOperator = new TimeOperator(innerStream); outerTimeOperator = new TimeOperator(outerStream); Operator project = project_Default( hashTableLookup_Default( innerRowType, outerExpressions, TABLE_BINDING_POSITION ), innerRowType, expressions ); return using_HashTable( innerTimeOperator, innerRowType, innerExpressions, TABLE_BINDING_POSITION++, map_NestedLoops( outerTimeOperator, project, ROW_BINDING_POSITION++, false, 1 ), null, null ); } private static final int WARMUP_RUNS = 100; private static final int MEASURED_RUNS = 80; private int d1, d2, d3, d4, d5; private int h1, h2, h3, h4, h5; //private static final long HASHED_ROWS[] = { 2000,1500,1200,1000, 800}; private static final long HASHED_ROWS[] = { 2000,1750,1500,1250, 1000}; private int emptyTableID; private TableRowType d1RowType; private TableRowType d2RowType; private TableRowType d3RowType; private TableRowType d4RowType; private TableRowType d5RowType; private TableRowType h1RowType; private TableRowType h2RowType; private TableRowType h3RowType; private TableRowType h4RowType; private TableRowType h5RowType; private TableRowType emptyTableRowType; int drivingTableIDs[]; int hashedTableIDs[]; TableRowType drivingRowTypes[]; TableRowType hashedRowTypes[]; private TimeOperator innerTimeOperator; private TimeOperator outerTimeOperator; }