/** * 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.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.operator.Cursor; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.TableRowType; import com.foundationdb.qp.util.SchemaCache; import com.foundationdb.server.api.dml.SetColumnSelector; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import static com.foundationdb.qp.operator.API.*; public class AncestorLookup_NestedIT extends OperatorITBase { @Override protected void setupCreateSchema() { 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"); bValueIndexRowType = indexType(b, "bvalue"); cValueIndexRowType = indexType(c, "cvalue"); 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); } protected int lookaheadQuantum() { return 1; } // Test argument validation @Test(expected = IllegalArgumentException.class) public void testALNGroupTableNull() { ancestorLookup_Nested(null, aValueIndexRowType, Collections.singleton(aRowType), 0, 1); } @Test(expected = IllegalArgumentException.class) public void testALNRowTypeNull() { ancestorLookup_Nested(rabc, null, Collections.singleton(aRowType), 0, 1); } @Test(expected = IllegalArgumentException.class) public void testALNAncestorTypesNull() { ancestorLookup_Nested(rabc, aValueIndexRowType, null, 0, 1); } @Test(expected = IllegalArgumentException.class) public void testALNAncestorTypesEmpty() { ancestorLookup_Nested(rabc, aValueIndexRowType, Collections.<TableRowType>emptyList(), 0, 1); } @Test(expected = IllegalArgumentException.class) public void testALNBadBindingPosition() { ancestorLookup_Nested(rabc, aValueIndexRowType, Collections.singleton(aRowType), -1, 1); } // Test operator execution @Test public void testAIndexToA() { Operator plan = map_NestedLoops( indexScan_Default(aValueIndexRowType), ancestorLookup_Nested(rabc, aValueIndexRowType, Collections.singleton(aRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(aRowType, 13L, 1L, "a13"), row(aRowType, 14L, 1L, "a14"), row(aRowType, 23L, 2L, "a23"), row(aRowType, 24L, 2L, "a24"), }; compareRows(expected, cursor); } @Test public void testAIndexToAAndR() { Operator plan = map_NestedLoops( indexScan_Default(aValueIndexRowType), ancestorLookup_Nested(rabc, aValueIndexRowType, Arrays.asList(aRowType, rRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rRowType, 1L, "r1"), row(aRowType, 13L, 1L, "a13"), row(rRowType, 1L, "r1"), row(aRowType, 14L, 1L, "a14"), row(rRowType, 2L, "r2"), row(aRowType, 23L, 2L, "a23"), row(rRowType, 2L, "r2"), row(aRowType, 24L, 2L, "a24"), }; compareRows(expected, cursor); } @Test public void testAIndexToARAndA() { Operator plan = map_NestedLoops( indexScan_Default(aValueIndexRowType), ancestorLookup_Nested(rabc, aValueIndexRowType, Arrays.asList(rRowType, aRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rRowType, 1L, "r1"), row(aRowType, 13L, 1L, "a13"), row(rRowType, 1L, "r1"), row(aRowType, 14L, 1L, "a14"), row(rRowType, 2L, "r2"), row(aRowType, 23L, 2L, "a23"), row(rRowType, 2L, "r2"), row(aRowType, 24L, 2L, "a24"), }; compareRows(expected, cursor); } @Test public void testAIndexToAToR() { Operator plan = map_NestedLoops( map_NestedLoops( indexScan_Default(aValueIndexRowType), ancestorLookup_Nested(rabc, aValueIndexRowType, Arrays.asList(aRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1), ancestorLookup_Nested(rabc, aRowType, Arrays.asList(rRowType), 1, lookaheadQuantum()), 1, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rRowType, 1L, "r1"), row(rRowType, 1L, "r1"), row(rRowType, 2L, "r2"), row(rRowType, 2L, "r2"), }; compareRows(expected, cursor); } // Multiple index tests @Test public void testABIndexToR() { Operator abIndexScan = hKeyUnion_Ordered( indexScan_Default(aValueIndexRowType, false, aValueRange("a13", "a14")), indexScan_Default(bValueIndexRowType, false, bValueRange("b25", "b26")), aValueIndexRowType, bValueIndexRowType, 2, 2, 2, rRowType); Operator plan = map_NestedLoops( abIndexScan, ancestorLookup_Nested(rabc, abIndexScan.rowType(), Collections.singleton(rRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rRowType, 1L, "r1"), row(rRowType, 2L, "r2"), }; compareRows(expected, cursor); } @Test public void testABCIndexToR() { Operator abIndexScan = hKeyUnion_Ordered( indexScan_Default(aValueIndexRowType, false, aValueRange("a13", "a14")), indexScan_Default(bValueIndexRowType, false, bValueRange("b15", "b16")), aValueIndexRowType, bValueIndexRowType, 2, 2, 2, rRowType); Operator abcIndexScan = hKeyUnion_Ordered( abIndexScan, indexScan_Default(cValueIndexRowType, false, cValueRange("c17", "c18")), abIndexScan.rowType(), cValueIndexRowType, 1, 2, 1, rRowType); Operator plan = map_NestedLoops( abcIndexScan, ancestorLookup_Nested(rabc, abcIndexScan.rowType(), Collections.singleton(rRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1); Cursor cursor = cursor(plan, queryContext, queryBindings); Row[] expected = new Row[]{ row(rRowType, 1L, "r1"), }; compareRows(expected, cursor); } @Test public void testCursor() { Operator plan = map_NestedLoops( indexScan_Default(aValueIndexRowType), ancestorLookup_Nested(rabc, aValueIndexRowType, Collections.singleton(aRowType), 0, lookaheadQuantum()), 0, pipelineMap(), 1); CursorLifecycleTestCase testCase = new CursorLifecycleTestCase() { @Override public Row[] firstExpectedRows() { return new Row[] { row(aRowType, 13L, 1L, "a13"), row(aRowType, 14L, 1L, "a14"), row(aRowType, 23L, 2L, "a23"), row(aRowType, 24L, 2L, "a24"), }; } @Override public boolean reopenTopLevel() { return true; } }; testCursorLifecycle(plan, testCase); } private IndexKeyRange aValueRange(String lo, String hi) { return IndexKeyRange.bounded(aValueIndexRowType, aValueBound(lo), true, aValueBound(hi), true); } private IndexKeyRange bValueRange(String lo, String hi) { return IndexKeyRange.bounded(bValueIndexRowType, bValueBound(lo), true, bValueBound(hi), true); } private IndexKeyRange cValueRange(String lo, String hi) { return IndexKeyRange.bounded(cValueIndexRowType, cValueBound(lo), true, cValueBound(hi), true); } private IndexBound aValueBound(String a) { return new IndexBound(row(aValueIndexRowType, a), new SetColumnSelector(0)); } private IndexBound bValueBound(String b) { return new IndexBound(row(bValueIndexRowType, b), new SetColumnSelector(0)); } private IndexBound cValueBound(String c) { return new IndexBound(row(cValueIndexRowType, c), new SetColumnSelector(0)); } 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 bValueIndexRowType; protected IndexRowType cValueIndexRowType; protected IndexRowType rValueIndexRowType; protected Group rabc; }