/**
* 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.*;
import com.foundationdb.qp.operator.API;
import com.foundationdb.qp.operator.Cursor;
import com.foundationdb.qp.operator.Operator;
import com.foundationdb.qp.operator.QueryBindings;
import com.foundationdb.qp.operator.QueryContext;
import com.foundationdb.qp.operator.StoreAdapter;
import com.foundationdb.qp.row.BindableRow;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.row.ValuesHolderRow;
import com.foundationdb.qp.rowtype.*;
import com.foundationdb.qp.rowtype.Schema;
import com.foundationdb.qp.util.SchemaCache;
import com.foundationdb.server.api.dml.ColumnSelector;
import com.foundationdb.server.collation.AkCollator;
import com.foundationdb.server.test.it.ITBase;
import com.foundationdb.server.types.common.BigDecimalWrapperImpl;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.server.types.value.ValueSources;
import com.foundationdb.util.Strings;
import com.geophile.z.SpatialObject;
import org.junit.After;
import org.junit.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.foundationdb.qp.operator.API.cursor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class OperatorITBase extends ITBase
{
private static final Logger LOG = LoggerFactory.getLogger(OperatorITBase.class.getName());
/** Override if derived IT manages its own transaction */
protected boolean doAutoTransaction() {
return true;
}
@Before
public final void runAllSetup() {
setupCreateSchema();
if(doAutoTransaction()) {
txnService().beginTransaction(session());
}
schema = SchemaCache.globalSchema(ais());
assert schema != null : "no schema in ais";
adapter = newStoreAdapter();
setupPostCreateSchema();
}
@After
public final void after_endTransaction() {
if(doAutoTransaction()) {
txnService().commitTransaction(session());
}
}
protected void setupCreateSchema() {
customer = createTable(
"schema", "customer",
"cid int not null primary key",
"name varchar(20)");
createIndex("schema", "customer", "name", "name");
order = createTable(
"schema", "order",
"oid int not null primary key",
"cid int",
"salesman varchar(20)",
"grouping foreign key (cid) references customer(cid)");
createIndex("schema", "order", "salesman", "salesman");
createIndex("schema", "order", "cid", "cid");
item = createTable(
"schema", "item",
"iid int not null primary key",
"oid int",
"grouping foreign key (oid) references \"order\"(oid)");
createIndex("schema", "item", "oid", "oid");
createIndex("schema", "item", "oid2", "oid", "iid");
address = createTable(
"schema", "address",
"aid int not null primary key",
"cid int",
"address varchar(100)",
"grouping foreign key (cid) references customer(cid)");
createIndex("schema", "address", "cid", "cid");
createIndex("schema", "address", "address", "address");
createLeftGroupIndex(new TableName("schema", "customer"), "cname_ioid", "customer.name", "item.oid");
}
protected void setupPostCreateSchema() {
customerRowType = schema.tableRowType(table(customer));
orderRowType = schema.tableRowType(table(order));
itemRowType = schema.tableRowType(table(item));
addressRowType = schema.tableRowType(table(address));
orderHKeyRowType = schema.newHKeyRowType(orderRowType.hKey());
customerNameIndexRowType = indexType(customer, "name");
orderSalesmanIndexRowType = indexType(order, "salesman");
orderCidIndexRowType = indexType(order, "cid");
itemOidIndexRowType = indexType(item, "oid");
itemOidIidIndexRowType = indexType(item, "oid", "iid");
itemIidIndexRowType = indexType(item, "iid");
customerCidIndexRowType = indexType(customer, "cid");
addressCidIndexRowType = indexType(address, "cid");
addressAddressIndexRowType = indexType(address, "address");
customerNameItemOidIndexRowType = groupIndexType(Index.JoinType.LEFT, "customer.name", "item.oid");
coi = group(customer);
customerOrdinal = ddl().getTable(session(), customer).getOrdinal();
orderOrdinal = ddl().getTable(session(), order).getOrdinal();
itemOrdinal = ddl().getTable(session(), item).getOrdinal();
addressOrdinal = ddl().getTable(session(), address).getOrdinal();
db = new Row[]{ row(customer, 1L, "xyz"),
row(customer, 2L, "abc"),
row(order, 11L, 1L, "ori"),
row(order, 12L, 1L, "david"),
row(order, 21L, 2L, "tom"),
row(order, 22L, 2L, "jack"),
row(item, 111L, 11L),
row(item, 112L, 11L),
row(item, 121L, 12L),
row(item, 122L, 12L),
row(item, 211L, 21L),
row(item, 212L, 21L),
row(item, 221L, 22L),
row(item, 222L, 22L)};
queryContext = queryContext(adapter);
queryBindings = queryContext.createBindings();
}
protected void testCursorLifecycle(Operator scan, CursorLifecycleTestCase testCase, AkCollator ... collators)
{
Cursor cursor = cursor(scan, queryContext, queryBindings);
cursor.openBindings();
cursor.nextBindings();
// Check idle following creation
assertTrue(cursor.isClosed());
// Check active following open
testCase.firstSetup();
cursor.open();
assertTrue(cursor.isActive());
// Check idle following close
cursor.close();
assertTrue(cursor.isClosed());
// Check active following re-open
testCase.firstSetup();
cursor.open();
assertTrue(cursor.isActive());
cursor.close();
// Check active during iteration
testCase.firstSetup();
if (testCase.hKeyComparison()) {
compareRenderedHKeys(testCase.firstExpectedHKeys(), cursor, testCase.reopenTopLevel());
} else {
compareRows(testCase.firstExpectedRows(), cursor, testCase.reopenTopLevel());
}
assertTrue(cursor.isClosed());
// Check close during iteration.
if (testCase.hKeyComparison()
? testCase.firstExpectedHKeys().length > 1
: testCase.firstExpectedRows().length > 1) {
testCase.firstSetup();
if (testCase.reopenTopLevel())
cursor.openTopLevel();
else
cursor.open();
cursor.next();
assertTrue(cursor.isActive());
cursor.close();
assertTrue(cursor.isClosed());
}
// Check that a second execution works
testCase.secondSetup();
if (testCase.hKeyComparison()) {
compareRenderedHKeys(testCase.secondExpectedHKeys(), cursor, testCase.reopenTopLevel());
} else {
compareRows(testCase.secondExpectedRows(), cursor, testCase.reopenTopLevel());
}
assertTrue(cursor.isClosed());
}
protected void use(Row[] db)
{
writeRows(db);
}
protected Group group(int tableId)
{
return ais().getTable(tableId).getGroup();
}
protected Table table(int tableId)
{
return ais().getTable(tableId);
}
protected IndexRowType indexType(int tableId, String... columnNamesArray)
{
List<String> searchIndexColumnNames = Arrays.asList(columnNamesArray);
Table table = table(tableId);
for (Index index : table.getIndexesIncludingInternal()) {
List<String> indexColumnNames = new ArrayList<>();
for (IndexColumn indexColumn : index.getKeyColumns()) {
indexColumnNames.add(indexColumn.getColumn().getName());
}
if (searchIndexColumnNames.equals(indexColumnNames)) {
return schema.tableRowType(table(tableId)).indexRowType(index);
}
}
return null;
}
protected IndexRowType groupIndexType(TableName groupName, String... columnNamesArray)
{
List<String> searchIndexColumnNames = Arrays.asList(columnNamesArray);
for (Index index : ais().getGroup(groupName).getIndexes()) {
List<String> indexColumnNames = new ArrayList<>();
for (IndexColumn indexColumn : index.getKeyColumns()) {
Column column = indexColumn.getColumn();
indexColumnNames.add(String.format("%s.%s",
column.getTable().getName().getTableName(), column.getName()));
}
if (searchIndexColumnNames.equals(indexColumnNames)) {
return schema.indexRowType(index);
}
}
return null;
}
protected IndexRowType groupIndexType(Index.JoinType joinType, String ... columnNames)
{
IndexRowType selectedGroupIndexRowType = null;
for (IndexRowType groupIndexRowType : schema.groupIndexRowTypes()) {
boolean match = groupIndexRowType.index().getJoinType() == joinType;
for (IndexColumn indexColumn : groupIndexRowType.index().getKeyColumns()) {
Column column = indexColumn.getColumn();
String indexColumnName = String.format("%s.%s", column.getTable().getName().getTableName(),
column.getName());
// Why do index column names lose case?!
if (indexColumn.getPosition() < columnNames.length &&
!columnNames[indexColumn.getPosition()].equalsIgnoreCase(indexColumnName)) {
match = false;
}
}
if (match) {
selectedGroupIndexRowType = groupIndexRowType;
}
}
return selectedGroupIndexRowType;
}
protected ColumnSelector columnSelector(final Index index)
{
final int columnCount = index.getKeyColumns().size();
return new ColumnSelector() {
@Override
public boolean includesColumn(int columnPosition) {
return columnPosition < columnCount;
}
};
}
protected TestRow row(String hKeyString, RowType rowType, Object... fields)
{
return new TestRow(rowType, fields, hKeyString);
}
protected Row row(IndexRowType indexRowType, Object... objs) {
/*
try {
*/
ValuesHolderRow row = new ValuesHolderRow(indexRowType);
for (int i = 0; i < objs.length; i++) {
Object obj = objs[i];
Value value = row.valueAt(i);
if (obj == null) {
value.putNull();
} else if (obj instanceof Integer) {
if (ValueSources.underlyingType(value) == UnderlyingType.INT_64)
value.putInt64(((Integer) obj).longValue());
else
value.putInt32((Integer) obj);
} else if (obj instanceof Long) {
if (ValueSources.underlyingType(value) == UnderlyingType.INT_32)
value.putInt32(((Long) obj).intValue());
else
value.putInt64((Long) obj);
} else if (obj instanceof String) {
value.putString((String) obj, null);
} else if (obj instanceof BigDecimal) {
value.putObject(new BigDecimalWrapperImpl((BigDecimal) obj));
} else if (obj instanceof SpatialObject) {
value.putObject(obj);
} else {
fail(obj.toString());
}
}
return row;
/*
return new PersistitIndexRow(adapter, indexRowType, objs);
} catch(PersistitException e) {
throw new RuntimeException(e);
}
*/
}
protected String hKeyValue(Long x)
{
return x == null ? "null" : String.format("(long)%d", x);
}
// Useful when scanning is expected to throw an exception
protected void scan(Cursor cursor)
{
List<Row> actualRows = new ArrayList<>(); // So that result is viewable in debugger
try {
cursor.openTopLevel();
Row actualRow;
while ((actualRow = cursor.next()) != null) {
actualRows.add(actualRow);
}
} finally {
cursor.closeTopLevel();
}
}
@SuppressWarnings("unused") // useful for debugging
protected void dumpToAssertion(Cursor cursor)
{
List<String> strings = new ArrayList<>();
try {
cursor.openTopLevel();
Row row;
while ((row = cursor.next()) != null) {
strings.add(String.valueOf(row));
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
cursor.closeTopLevel();
}
strings.add(0, strings.size() == 1 ? "1 string:" : strings.size() + " strings:");
throw new AssertionError(Strings.join(strings));
}
@SuppressWarnings("unused") // useful for debugging
protected void dumpToAssertion(Operator plan)
{
dumpToAssertion(cursor(plan, queryContext, queryBindings));
}
protected void dump(Cursor cursor)
{
cursor.openTopLevel();
Row row;
while ((row = cursor.next()) != null) {
LOG.debug("{}", String.valueOf(row));
}
cursor.closeTopLevel();
}
protected void dump(Operator plan)
{
dump(cursor(plan, queryContext, queryBindings));
}
protected void compareRenderedHKeys(String[] expected, Cursor cursor)
{
compareRenderedHKeys(expected, cursor, true);
}
protected void compareRenderedHKeys(String[] expected, Cursor cursor, boolean topLevel)
{
int count;
try {
if (topLevel)
cursor.openTopLevel();
else
cursor.open();
count = 0;
List<Row> actualRows = new ArrayList<>(); // So that result is viewable in debugger
Row actualRow;
while ((actualRow = cursor.next()) != null) {
assertEquals(expected[count], actualRow.hKey().toString());
count++;
actualRows.add(actualRow);
}
} finally {
if (topLevel)
cursor.closeTopLevel();
else
cursor.close();
}
assertEquals(expected.length, count);
}
protected int ordinal(RowType rowType)
{
return rowType.table().getOrdinal();
}
public Operator rowsToValueScan(Row... rows) {
List<BindableRow> bindableRows = new ArrayList<>();
RowType type = null;
for(Row row : rows) {
RowType newType = row.rowType();
if(type == null) {
type = newType;
} else if(type != newType) {
fail("Multiple row types: " + type + " vs " + newType);
}
bindableRows.add(BindableRow.of(row));
}
return API.valuesScan_Default(bindableRows, type);
}
protected int customer;
protected int order;
protected int item;
protected int address;
protected TableRowType customerRowType;
protected TableRowType orderRowType;
protected TableRowType itemRowType;
protected TableRowType addressRowType;
protected HKeyRowType orderHKeyRowType;
protected IndexRowType customerCidIndexRowType;
protected IndexRowType customerNameIndexRowType;
protected IndexRowType orderSalesmanIndexRowType;
protected IndexRowType orderCidIndexRowType;
protected IndexRowType itemOidIndexRowType;
protected IndexRowType itemOidIidIndexRowType;
protected IndexRowType itemIidIndexRowType;
protected IndexRowType addressCidIndexRowType;
protected IndexRowType addressAddressIndexRowType;
protected IndexRowType customerNameItemOidIndexRowType;
protected Group coi;
protected Schema schema;
protected Row[] db;
protected Row[] emptyDB = new Row[0];
protected StoreAdapter adapter;
protected QueryBindings queryBindings;
protected QueryContext queryContext;
protected AkCollator ciCollator;
protected int customerOrdinal;
protected int orderOrdinal;
protected int itemOrdinal;
protected int addressOrdinal;
protected static abstract class CursorLifecycleTestCase
{
public boolean hKeyComparison()
{
return false;
}
public void firstSetup()
{}
public Row[] firstExpectedRows()
{
fail();
return null;
}
public String[] firstExpectedHKeys()
{
fail();
return null;
}
public void secondSetup()
{}
public Row[] secondExpectedRows()
{
return firstExpectedRows();
}
public String[] secondExpectedHKeys()
{
return firstExpectedHKeys();
}
public boolean reopenTopLevel() {
return false;
}
}
protected class TransactionContext implements AutoCloseable
{
public TransactionContext()
{
txnService().beginTransaction(session());
}
public void close()
{
txnService().commitTransaction(session());
}
}
}