/** * 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.qp.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.operator.API; import com.foundationdb.qp.operator.ExpressionGenerator; import com.foundationdb.qp.operator.Operator; 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.qp.rowtype.TableRowType; import com.foundationdb.server.api.dml.SetColumnSelector; import org.junit.Test; import static com.foundationdb.qp.operator.API.*; import static com.foundationdb.qp.operator.API.IntersectOption.*; import static com.foundationdb.server.test.ExpressionGenerators.field; public class MultiIndexCrossBranchIT extends OperatorITBase { @Override protected void setupCreateSchema() { p = createTable( "schema", "p", "pid int not null primary key", "x int"); createIndex("schema", "p", "px", "x"); c = createTable( "schema", "c", "cid int not null primary key", "pid int", "y int", "grouping foreign key (pid) references p(pid)"); createIndex("schema", "c", "cy", "y"); d = createTable( "schema", "d", "did int not null primary key", "pid int", "z int", "grouping foreign key (pid) references p(pid)"); createIndex("schema", "d", "dz", "z"); } @Override protected void setupPostCreateSchema() { pRowType = schema.tableRowType(table(p)); cRowType = schema.tableRowType(table(c)); dRowType = schema.tableRowType(table(d)); pXIndexRowType = indexType(p, "x"); cYIndexRowType = indexType(c, "y"); dZIndexRowType = indexType(d, "z"); hKeyRowType = schema.newHKeyRowType(pRowType.table().hKey()); coi = group(p); queryContext = queryContext(adapter); queryBindings = queryContext.createBindings(); db = new Row[]{ // 0x: Both sides empty // 1x: C empty row(p, 10L, 1L), row(d, 1900L, 10L, 1L), row(d, 1901L, 10L, 1L), row(d, 1902L, 10L, 1L), // 2x: D empty row(p, 20L, 2L), row(c, 2800L, 20L, 2L), row(c, 2801L, 20L, 2L), row(c, 2802L, 20L, 2L), // 3x: C, D non-empty row(p, 30L, 3L), row(c, 3800L, 30L, 3L), row(c, 3801L, 30L, 3L), row(c, 3802L, 30L, 3L), row(d, 3900L, 30L, 3L), row(d, 3901L, 30L, 3L), }; use(db); } @Test public void test0xAND() { Operator plan = intersectCyDz(0, OUTPUT_LEFT); Row[] expected = new Row[]{ }; compareRows(expected, cursor(plan, queryContext, queryBindings)); plan = intersectCyDz(0, OUTPUT_RIGHT); expected = new Row[]{ }; compareRows(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test1xAND() { Operator plan = intersectCyDz(1, OUTPUT_LEFT); Row[] expected = new Row[]{ }; compareRows(expected, cursor(plan, queryContext, queryBindings)); plan = intersectCyDz(1, OUTPUT_RIGHT); expected = new Row[]{ }; compareRows(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test2xAND() { Operator plan = intersectCyDz(2, OUTPUT_LEFT); Row[] expected = new Row[]{ }; compareRows(expected, cursor(plan, queryContext, queryBindings)); plan = intersectCyDz(2, OUTPUT_RIGHT); expected = new Row[]{ }; compareRows(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test3xAND() { Operator plan = intersectCyDz(3, OUTPUT_LEFT); Row[] expected = new Row[]{ row(cRowType, 3L, 30L, 3800L), row(cRowType, 3L, 30L, 3801L), row(cRowType, 3L, 30L, 3802L), }; compareRows(expected, cursor(plan, queryContext, queryBindings)); plan = intersectCyDz(3, OUTPUT_RIGHT); expected = new Row[]{ row(dRowType, 3L, 30L, 3900L), row(dRowType, 3L, 30L, 3901L), }; compareRows(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test0xOR() { Operator plan = unionCyDz(0); String[] expected = new String[]{ }; compareRenderedHKeys(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test1xOR() { Operator plan = unionCyDz(1); String[] expected = new String[]{ pKey(10L), }; compareRenderedHKeys(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test2xOR() { Operator plan = unionCyDz(2); String[] expected = new String[]{ pKey(20L), }; compareRenderedHKeys(expected, cursor(plan, queryContext, queryBindings)); } @Test public void test3xOR() { Operator plan = unionCyDz(3); String[] expected = new String[]{ pKey(30L), }; compareRenderedHKeys(expected, cursor(plan, queryContext, queryBindings)); } private Operator intersectCyDz(int key, IntersectOption side) { Operator plan = intersect_Ordered( indexScan_Default( cYIndexRowType, cYEQ(key), ordering(field(cYIndexRowType, 1), true, field(cYIndexRowType, 2), true)), indexScan_Default( dZIndexRowType, dZEQ(key), ordering(field(dZIndexRowType, 1), true, field(dZIndexRowType, 2), true)), cYIndexRowType, dZIndexRowType, 2, 2, 1, JoinType.INNER_JOIN, side, null, true); return plan; } private Operator unionCyDz(int key) { Operator plan = hKeyUnion_Ordered( indexScan_Default( cYIndexRowType, cYEQ(key), ordering(field(cYIndexRowType, 1), true, field(cYIndexRowType, 2), true)), indexScan_Default( dZIndexRowType, dZEQ(key), ordering(field(dZIndexRowType, 1), true, field(dZIndexRowType, 2), true)), cYIndexRowType, dZIndexRowType, 2, 2, 1, pRowType); return plan; } private IndexKeyRange cYEQ(long y) { IndexBound yBound = new IndexBound(row(cYIndexRowType, y), new SetColumnSelector(0)); return IndexKeyRange.bounded(cYIndexRowType, yBound, true, yBound, true); } private IndexKeyRange dZEQ(long z) { IndexBound zBound = new IndexBound(row(dZIndexRowType, z), new SetColumnSelector(0)); return IndexKeyRange.bounded(dZIndexRowType, zBound, true, zBound, true); } private Ordering ordering(Object... objects) { Ordering ordering = API.ordering(); int i = 0; while (i < objects.length) { ExpressionGenerator expression = (ExpressionGenerator) objects[i++]; Boolean ascending = (Boolean) objects[i++]; ordering.append(expression, ascending); } return ordering; } private String pKey(Long pid) { return String.format("{%d,%s}", pRowType.table().getOrdinal(), hKeyValue(pid)); } private int p; private int c; private int d; private TableRowType pRowType; private TableRowType cRowType; private TableRowType dRowType; private IndexRowType pXIndexRowType; private IndexRowType cYIndexRowType; private IndexRowType dZIndexRowType; private RowType hKeyRowType; }