/** * 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.it.qp; import com.foundationdb.ais.model.Group; import com.foundationdb.qp.operator.Cursor; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.*; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.Set; import static com.foundationdb.qp.operator.API.JoinType.INNER_JOIN; import static com.foundationdb.qp.operator.API.InputPreservationOption.DISCARD_INPUT; import static com.foundationdb.qp.operator.API.InputPreservationOption.KEEP_INPUT; import static com.foundationdb.qp.operator.API.*; import static org.junit.Assert.assertTrue; public class Product3WayIT extends OperatorITBase { @Override protected void setupCreateSchema() { // Don't call super.before(). This is a different schema from most operator ITs. r = createTable( "schema", "r", "rid int not null primary key", "rvalue varchar(20)"); createIndex("schema", "r", "rvalue", "rvalue"); a = createTable( "schema", "a", "aid int not null primary key", "rid int", "avalue varchar(20)", "grouping foreign key(rid) references r(rid)"); createIndex("schema", "a", "avalue", "avalue"); b = createTable( "schema", "b", "bid int not null primary key", "rid int", "bvalue varchar(20)", "grouping foreign key(rid) references r(rid)"); createIndex("schema", "b", "bvalue", "bvalue"); c = createTable( "schema", "c", "cid int not null primary key", "rid int", "cvalue varchar(20)", "grouping foreign key(rid) references r(rid)"); createIndex("schema", "c", "cvalue", "cvalue"); } @Override protected void setupPostCreateSchema() { rRowType = schema.tableRowType(table(r)); aRowType = schema.tableRowType(table(a)); bRowType = schema.tableRowType(table(b)); cRowType = schema.tableRowType(table(c)); aValueIndexRowType = indexType(a, "avalue"); rValueIndexRowType = indexType(r, "rvalue"); rabc = group(r); db = new Row[]{ row(r, 1L, "r1"), row(r, 2L, "r2"), row(a, 13L, 1L, "a13"), row(a, 14L, 1L, "a14"), row(a, 23L, 2L, "a23"), row(a, 24L, 2L, "a24"), row(b, 15L, 1L, "b15"), row(b, 16L, 1L, "b16"), row(b, 25L, 2L, "b25"), row(b, 26L, 2L, "b26"), row(c, 17L, 1L, "c17"), row(c, 18L, 1L, "c18"), row(c, 27L, 2L, "c27"), row(c, 28L, 2L, "c28"), }; queryContext = queryContext(adapter); queryBindings = queryContext.createBindings(); use(db); } // Test assumption about ordinals @Test public void testOrdinalOrder() { assertTrue(ordinal(rRowType) < ordinal(aRowType)); assertTrue(ordinal(aRowType) < ordinal(bRowType)); assertTrue(ordinal(bRowType) < ordinal(cRowType)); } // Test operator execution public void testProductAfterIndexScanOfA_NestedLoops_RABC() { Operator RA = flatten_HKeyOrdered( ancestorLookup_Default( indexScan_Default(aValueIndexRowType, false), rabc, aValueIndexRowType, Arrays.asList(aRowType, rRowType), DISCARD_INPUT), rRowType, aRowType, INNER_JOIN); Operator RB = flatten_HKeyOrdered( branchLookup_Nested( rabc, RA.rowType(), rRowType, null, list(bRowType), KEEP_INPUT, 0, 1), rRowType, bRowType, INNER_JOIN); Operator RAB = product_Nested(RB, RA.rowType(), null, RB.rowType(), 0); Operator RC = flatten_HKeyOrdered( branchLookup_Nested( rabc, RAB.rowType(), rRowType, null, list(cRowType), KEEP_INPUT, 1, 1), rRowType, cRowType, INNER_JOIN); Operator RABC = product_Nested(RC, RAB.rowType(), null, RC.rowType(), 1); RowType rabcRowType = RABC.rowType(); Operator plan = map_NestedLoops( map_NestedLoops(RA, RAB, 0, pipelineMap(), 1), RABC, 1, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 15L, 1L, "b15", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 15L, 1L, "b15", 18L, 1L, "c18"), row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 16L, 1L, "b16", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 16L, 1L, "b16", 18L, 1L, "c18"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 15L, 1L, "b15", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 15L, 1L, "b15", 18L, 1L, "c18"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 16L, 1L, "b16", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 16L, 1L, "b16", 18L, 1L, "c18"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 25L, 2L, "b25", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 25L, 2L, "b25", 28L, 2L, "c28"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 26L, 2L, "b26", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 26L, 2L, "b26", 28L, 2L, "c28"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 25L, 2L, "b25", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 25L, 2L, "b25", 28L, 2L, "c28"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 26L, 2L, "b26", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 26L, 2L, "b26", 28L, 2L, "c28"), }; compareRows(expected, cursor); } @Test public void testProductAfterIndexScanOfA_NestedLoops_RACB() { // Like testProductAfterIndexScanOfA_NestedLoops_RABC, but branches are included in a different order. Operator RA = flatten_HKeyOrdered( ancestorLookup_Default( indexScan_Default(aValueIndexRowType, false), rabc, aValueIndexRowType, Arrays.asList(aRowType, rRowType), DISCARD_INPUT), rRowType, aRowType, INNER_JOIN); Operator RC = flatten_HKeyOrdered( branchLookup_Nested( rabc, RA.rowType(), rRowType, null, list(cRowType), KEEP_INPUT, 0, 1), rRowType, cRowType, INNER_JOIN); Operator RAC = product_Nested(RC, RA.rowType(), null, RC.rowType(), 0); Operator RB = flatten_HKeyOrdered( branchLookup_Nested( rabc, RAC.rowType(), rRowType, null, list(bRowType), KEEP_INPUT, 1, 1), rRowType, bRowType, INNER_JOIN); Operator RACB = product_Nested(RB, RAC.rowType(), null, RB.rowType(), 1); RowType racbRowType = RACB.rowType(); Operator plan = map_NestedLoops( map_NestedLoops(RA, RAC, 0, pipelineMap(), 1), RACB, 1, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(racbRowType, 1L, "r1", 13L, 1L, "a13", 17L, 1L, "c17", 15L, 1L, "b15"), row(racbRowType, 1L, "r1", 13L, 1L, "a13", 17L, 1L, "c17", 16L, 1L, "b16"), row(racbRowType, 1L, "r1", 13L, 1L, "a13", 18L, 1L, "c18", 15L, 1L, "b15"), row(racbRowType, 1L, "r1", 13L, 1L, "a13", 18L, 1L, "c18", 16L, 1L, "b16"), row(racbRowType, 1L, "r1", 14L, 1L, "a14", 17L, 1L, "c17", 15L, 1L, "b15"), row(racbRowType, 1L, "r1", 14L, 1L, "a14", 17L, 1L, "c17", 16L, 1L, "b16"), row(racbRowType, 1L, "r1", 14L, 1L, "a14", 18L, 1L, "c18", 15L, 1L, "b15"), row(racbRowType, 1L, "r1", 14L, 1L, "a14", 18L, 1L, "c18", 16L, 1L, "b16"), row(racbRowType, 2L, "r2", 23L, 2L, "a23", 27L, 2L, "c27", 25L, 2L, "b25"), row(racbRowType, 2L, "r2", 23L, 2L, "a23", 27L, 2L, "c27", 26L, 2L, "b26"), row(racbRowType, 2L, "r2", 23L, 2L, "a23", 28L, 2L, "c28", 25L, 2L, "b25"), row(racbRowType, 2L, "r2", 23L, 2L, "a23", 28L, 2L, "c28", 26L, 2L, "b26"), row(racbRowType, 2L, "r2", 24L, 2L, "a24", 27L, 2L, "c27", 25L, 2L, "b25"), row(racbRowType, 2L, "r2", 24L, 2L, "a24", 27L, 2L, "c27", 26L, 2L, "b26"), row(racbRowType, 2L, "r2", 24L, 2L, "a24", 28L, 2L, "c28", 25L, 2L, "b25"), row(racbRowType, 2L, "r2", 24L, 2L, "a24", 28L, 2L, "c28", 26L, 2L, "b26"), }; compareRows(expected, cursor); } @Test public void testProductAfterIndexScanOfR() { Operator rScan = ancestorLookup_Default( indexScan_Default(rValueIndexRowType, false), rabc, rValueIndexRowType, Arrays.asList(rRowType), DISCARD_INPUT); Operator flattenRA = flatten_HKeyOrdered( branchLookup_Nested( rabc, rRowType, rRowType, null, list(aRowType), KEEP_INPUT, 0, 1), rRowType, aRowType, INNER_JOIN); Operator RA = product_Nested(flattenRA, rRowType, null, flattenRA.rowType(), 0); Operator flattenRB = flatten_HKeyOrdered( branchLookup_Nested( rabc, RA.rowType(), rRowType, null, list(bRowType), KEEP_INPUT, 1, 1), rRowType, bRowType, INNER_JOIN); Operator RAB = product_Nested(flattenRB, RA.rowType(), null, flattenRB.rowType(), 1); Operator flattenRC = flatten_HKeyOrdered( branchLookup_Nested( rabc, RAB.rowType(), rRowType, null, list(cRowType), KEEP_INPUT, 2, 1), rRowType, cRowType, INNER_JOIN); Operator RABC = product_Nested(flattenRC, RAB.rowType(), null, flattenRC.rowType(), 2); RowType rabcRowType = RABC.rowType(); Operator plan = map_NestedLoops( map_NestedLoops( map_NestedLoops(rScan, RA, 0, pipelineMap(), 1), RAB, 1, pipelineMap(), 1), RABC, 2, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 15L, 1L, "b15", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 15L, 1L, "b15", 18L, 1L, "c18"), row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 16L, 1L, "b16", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 13L, 1L, "a13", 16L, 1L, "b16", 18L, 1L, "c18"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 15L, 1L, "b15", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 15L, 1L, "b15", 18L, 1L, "c18"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 16L, 1L, "b16", 17L, 1L, "c17"), row(rabcRowType, 1L, "r1", 14L, 1L, "a14", 16L, 1L, "b16", 18L, 1L, "c18"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 25L, 2L, "b25", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 25L, 2L, "b25", 28L, 2L, "c28"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 26L, 2L, "b26", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 23L, 2L, "a23", 26L, 2L, "b26", 28L, 2L, "c28"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 25L, 2L, "b25", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 25L, 2L, "b25", 28L, 2L, "c28"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 26L, 2L, "b26", 27L, 2L, "c27"), row(rabcRowType, 2L, "r2", 24L, 2L, "a24", 26L, 2L, "b26", 28L, 2L, "c28"), }; compareRows(expected, cursor); } // TODO: Test handling of rows whose type is not involved in product. private Set<TableRowType> removeDescendentTypes(AisRowType type) { Set<TableRowType> keepTypes = type.schema().userTableTypes(); keepTypes.removeAll(Schema.descendentTypes(type, keepTypes)); return keepTypes; } private List<TableRowType> list(TableRowType... rowTypes) { return Arrays.asList(rowTypes); } protected int r; protected int a; protected int c; protected int b; protected TableRowType rRowType; protected TableRowType aRowType; protected TableRowType cRowType; protected TableRowType bRowType; protected IndexRowType aValueIndexRowType; protected IndexRowType rValueIndexRowType; protected Group rabc; }