/**
* 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.Cursor;
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.server.api.dml.SetColumnSelector;
import org.junit.Test;
import java.util.EnumSet;
import static com.foundationdb.qp.operator.API.*;
import static com.foundationdb.server.test.ExpressionGenerators.field;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class NWaySkipScanIT extends OperatorITBase
{
@Override
protected void setupCreateSchema()
{
t = createTable(
"schema", "t",
"id int not null",
"x int",
"y int",
"z int",
"primary key(id)");
createIndex("schema", "t", "idx_x", "x");
createIndex("schema", "t", "idx_y", "y");
createIndex("schema", "t", "idx_z", "z");
}
@Override
protected void setupPostCreateSchema()
{
tRowType = schema.tableRowType(table(t));
tIdIndexRowType = indexType(t, "id");
tXIndexRowType = indexType(t, "x");
tYIndexRowType = indexType(t, "y");
tZIndexRowType = indexType(t, "z");
queryContext = queryContext(adapter);
queryBindings = queryContext.createBindings();
db = new Row[] {
row(t, 1000L, 71L, 81L, 91L),
row(t, 1001L, 71L, 81L, 92L),
row(t, 1002L, 71L, 82L, 91L),
row(t, 1003L, 71L, 82L, 92L),
row(t, 1004L, 72L, 81L, 91L),
row(t, 1005L, 72L, 81L, 92L),
row(t, 1006L, 72L, 82L, 91L),
row(t, 1007L, 72L, 82L, 92L),
row(t, 1008L, 73L, null, null),
};
use(db);
}
private static final IntersectOption LEFT = IntersectOption.OUTPUT_LEFT;
private static final IntersectOption RIGHT = IntersectOption.OUTPUT_RIGHT;
private int t;
private int child;
private RowType tRowType;
private RowType childRowType;
private IndexRowType tIdIndexRowType;
private IndexRowType tXIndexRowType;
private IndexRowType tYIndexRowType;
private IndexRowType tZIndexRowType;
@Test
public void testTwoIntersects()
{
for (int x = 71; x <= 72; x++) {
for (int y = 81; y <= 82; y++) {
for (int z = 91; z <= 92; z++) {
testTwoIntersects(x, intersectXYintersectZ(x, y, LEFT, z, LEFT, true));
testTwoIntersects(x, intersectXYintersectZ(x, y, LEFT, z, LEFT, false));
testTwoIntersects(z, intersectXYintersectZ(x, y, LEFT, z, RIGHT, true));
testTwoIntersects(z, intersectXYintersectZ(x, y, LEFT, z, RIGHT, false));
testTwoIntersects(y, intersectXYintersectZ(x, y, RIGHT, z, LEFT, true));
testTwoIntersects(y, intersectXYintersectZ(x, y, RIGHT, z, LEFT, false));
testTwoIntersects(z, intersectXYintersectZ(x, y, RIGHT, z, RIGHT, true));
testTwoIntersects(z, intersectXYintersectZ(x, y, RIGHT, z, RIGHT, false));
}
}
}
}
@Test
public void testTwoUnions()
{
Operator plan = unionXXunionX(71, 72, 73);
Row[] expected = new Row[] {
row(tXIndexRowType, 71L, 1000L),
row(tXIndexRowType, 71L, 1001L),
row(tXIndexRowType, 71L, 1002L),
row(tXIndexRowType, 71L, 1003L),
row(tXIndexRowType, 72L, 1004L),
row(tXIndexRowType, 72L, 1005L),
row(tXIndexRowType, 72L, 1006L),
row(tXIndexRowType, 72L, 1007L),
row(tXIndexRowType, 73L, 1008L),
};
compareRows(expected, cursor(plan, queryContext, queryBindings));
}
@Test
public void testIntersectThenUnion()
{
Row[] expected = new Row[] {
row(tXIndexRowType, 71L, 1000L),
row(tXIndexRowType, 71L, 1001L),
row(tXIndexRowType, 72L, 1004L),
row(tXIndexRowType, 72L, 1005L),
row(tXIndexRowType, 72L, 1006L),
row(tXIndexRowType, 72L, 1007L),
};
compareRows(expected, cursor(intersectXYunionX(71, 81, 72, false), queryContext, queryBindings));
compareRows(expected, cursor(intersectXYunionX(71, 81, 72, true), queryContext, queryBindings));
}
@Test
public void testIntersectWithEmptyInputThenUnion()
{
Row[] expected = new Row[] {
row(tXIndexRowType, 72L, 1004L),
row(tXIndexRowType, 72L, 1005L),
row(tXIndexRowType, 72L, 1006L),
row(tXIndexRowType, 72L, 1007L),
};
// Left input to intersection is empty
{
compareRows(expected, cursor(intersectXYunionX(99, 81, 72, false), queryContext, queryBindings));
compareRows(expected, cursor(intersectXYunionX(99, 81, 72, true), queryContext, queryBindings));
}
// Right input to intersection is empty
{
compareRows(expected, cursor(intersectXYunionX(71, 99, 72, false), queryContext, queryBindings));
compareRows(expected, cursor(intersectXYunionX(71, 99, 72, true), queryContext, queryBindings));
}
// Both inputs to intersection are empty
{
compareRows(expected, cursor(intersectXYunionX(99, 99, 72, false), queryContext, queryBindings));
compareRows(expected, cursor(intersectXYunionX(99, 99, 72, true), queryContext, queryBindings));
}
}
@Test
public void testUnionThenIntersect()
{
{
Row[] expected = {
row(tXIndexRowType, 71, 1000L),
row(tXIndexRowType, 71, 1001L),
row(tXIndexRowType, 72, 1004L),
row(tXIndexRowType, 72, 1005L),
};
compareRows(expected, cursor(unionXXintersectY(71, 72, 81, LEFT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(71, 72, 81, LEFT, true), queryContext, queryBindings));
}
{
Row[] expected = {
row(tXIndexRowType, 81, 1000L),
row(tXIndexRowType, 81, 1001L),
row(tXIndexRowType, 81, 1004L),
row(tXIndexRowType, 81, 1005L),
};
compareRows(expected, cursor(unionXXintersectY(71, 72, 81, RIGHT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(71, 72, 81, RIGHT, true), queryContext, queryBindings));
}
{
Row[] expected = {
row(tXIndexRowType, 71, 1002L),
row(tXIndexRowType, 71, 1003L),
row(tXIndexRowType, 72, 1006L),
row(tXIndexRowType, 72, 1007L),
};
compareRows(expected, cursor(unionXXintersectY(71, 72, 82, LEFT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(71, 72, 82, LEFT, true), queryContext, queryBindings));
}
{
Row[] expected = {
row(tXIndexRowType, 82, 1002L),
row(tXIndexRowType, 82, 1003L),
row(tXIndexRowType, 82, 1006L),
row(tXIndexRowType, 82, 1007L),
};
compareRows(expected, cursor(unionXXintersectY(71, 72, 82, RIGHT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(71, 72, 82, RIGHT, true), queryContext, queryBindings));
}
}
@Test
public void testUnionWithEmptyInputThenIntersect()
{
// Left input to union is empty
{
Row[] expected = {
row(tXIndexRowType, 72, 1004L),
row(tXIndexRowType, 72, 1005L),
};
compareRows(expected, cursor(unionXXintersectY(99, 72, 81, LEFT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(99, 72, 81, LEFT, true), queryContext, queryBindings));
}
// Right input to union is empty
{
Row[] expected = {
row(tXIndexRowType, 71, 1000L),
row(tXIndexRowType, 71, 1001L),
};
compareRows(expected, cursor(unionXXintersectY(71, 99, 81, LEFT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(71, 99, 81, LEFT, true), queryContext, queryBindings));
}
// Both inputs to union are empty
{
Row[] expected = {
};
compareRows(expected, cursor(unionXXintersectY(99, 99, 81, LEFT, false), queryContext, queryBindings));
compareRows(expected, cursor(unionXXintersectY(99, 99, 81, LEFT, true), queryContext, queryBindings));
}
}
private void testTwoIntersects(long key, Operator plan)
{
Cursor cursor = cursor(plan, queryContext, queryBindings);
cursor.openTopLevel();
Row row = cursor.next();
assertEquals(Long.valueOf(key), getLong(row, 0));
assertNull(cursor.next());
}
private Operator intersectXYintersectZ(int x, int y, IntersectOption xyOutput, int z, IntersectOption xyzOutput, boolean skip)
{
Ordering xOrdering = new Ordering();
xOrdering.append(field(tXIndexRowType, 1), true);
Ordering yOrdering = new Ordering();
yOrdering.append(field(tYIndexRowType, 1), true);
Ordering zOrdering = new Ordering();
zOrdering.append(field(tZIndexRowType, 1), true);
IntersectOption scanType = skip ? IntersectOption.SKIP_SCAN : IntersectOption.SEQUENTIAL_SCAN;
return intersect_Ordered(
intersect_Ordered(
indexScan_Default(
tXIndexRowType,
xEq(x),
xOrdering),
indexScan_Default(
tYIndexRowType,
yEq(y),
yOrdering),
tXIndexRowType,
tYIndexRowType,
1,
1,
ascending(true),
JoinType.INNER_JOIN,
EnumSet.of(scanType, xyOutput),
null,
true),
indexScan_Default(
tZIndexRowType,
zEq(z),
zOrdering),
xyOutput == LEFT ? tXIndexRowType : tYIndexRowType,
tZIndexRowType,
1,
1,
ascending(true),
JoinType.INNER_JOIN,
EnumSet.of(scanType, xyzOutput),
null,
true);
}
private Operator unionXXunionX(int x1, int x2, int x3)
{
Ordering ordering = new Ordering();
ordering.append(field(tXIndexRowType, 1), true);
return union_Ordered(
union_Ordered(
indexScan_Default(
tXIndexRowType,
xEq(x1),
ordering),
indexScan_Default(
tXIndexRowType,
yEq(x2),
ordering),
tXIndexRowType,
tXIndexRowType,
1,
1,
ascending(true),
false),
indexScan_Default(
tXIndexRowType,
xEq(x3),
ordering),
tXIndexRowType,
tXIndexRowType,
1,
1,
ascending(true),
false);
}
private Operator intersectXYunionX(int x1, int y, int x2, boolean skip)
{
Ordering xOrdering = new Ordering();
xOrdering.append(field(tXIndexRowType, 1), true);
Ordering yOrdering = new Ordering();
yOrdering.append(field(tYIndexRowType, 1), true);
IntersectOption scanType = skip ? IntersectOption.SKIP_SCAN : IntersectOption.SEQUENTIAL_SCAN;
return union_Ordered(
intersect_Ordered(
indexScan_Default(
tXIndexRowType,
xEq(x1),
xOrdering),
indexScan_Default(
tYIndexRowType,
yEq(y),
yOrdering),
tXIndexRowType,
tYIndexRowType,
1,
1,
ascending(true),
JoinType.INNER_JOIN,
EnumSet.of(scanType, LEFT),
null,
true),
indexScan_Default(
tXIndexRowType,
xEq(x2),
xOrdering),
tXIndexRowType,
tXIndexRowType,
1,
1,
ascending(true),
false);
}
private Operator unionXXintersectY(int x1, int x2, int y, IntersectOption intersectOutput, boolean skip)
{
Ordering xOrdering = new Ordering();
xOrdering.append(field(tXIndexRowType, 1), true);
Ordering yOrdering = new Ordering();
yOrdering.append(field(tYIndexRowType, 1), true);
IntersectOption scanType = skip ? IntersectOption.SKIP_SCAN : IntersectOption.SEQUENTIAL_SCAN;
return intersect_Ordered(
union_Ordered(
indexScan_Default(
tXIndexRowType,
xEq(x1),
xOrdering),
indexScan_Default(
tXIndexRowType,
xEq(x2),
xOrdering),
tXIndexRowType,
tXIndexRowType,
1,
1,
ascending(true),
false),
indexScan_Default(
tYIndexRowType,
yEq(y),
yOrdering),
tXIndexRowType,
tYIndexRowType,
1,
1,
ascending(true),
JoinType.INNER_JOIN,
EnumSet.of(scanType, intersectOutput),
null,
true);
}
private IndexKeyRange xEq(long x)
{
IndexBound bound = new IndexBound(row(tXIndexRowType, x), new SetColumnSelector(0));
return IndexKeyRange.bounded(tXIndexRowType, bound, true, bound, true);
}
private IndexKeyRange yEq(long y)
{
IndexBound bound = new IndexBound(row(tYIndexRowType, y), new SetColumnSelector(0));
return IndexKeyRange.bounded(tYIndexRowType, bound, true, bound, true);
}
private IndexKeyRange zEq(long z)
{
IndexBound bound = new IndexBound(row(tZIndexRowType, z), new SetColumnSelector(0));
return IndexKeyRange.bounded(tZIndexRowType, bound, true, bound, true);
}
private boolean[] ascending(boolean... ascending)
{
return ascending;
}
}