/**
* 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.keyupdate;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.Index;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.row.ValuesHolderRow;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.qp.util.SchemaCache;
import com.foundationdb.server.test.it.ITBase;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.util.tap.Tap;
import com.foundationdb.util.tap.TapReport;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import java.util.concurrent.Callable;
import static com.foundationdb.server.test.it.keyupdate.Schema.*;
import static org.junit.Assert.*;
public abstract class KeyUpdateBase extends ITBase {
@Before
public final void before() throws Exception
{
testStore = new TestStore(store());
rowDefsToCounts = new TreeMap<>();
createSchema();
confirmColumns();
populateTables();
}
protected abstract void confirmColumns();
protected void confirmColumn(RowType type, Integer expectedId, String columnName) {
assert columnName != null;
assertNotNull("column ID for " + columnName, expectedId);
Column column = type.fieldColumn(expectedId);
assertNotNull ("no Column with id = "+expectedId + ", name="+columnName, column);
assertEquals("Column name", columnName, column.getName());
}
@Test
@SuppressWarnings("unused") // JUnit will invoke this
public void testInitialState() throws Exception
{
checkDB();
checkInitialState();
}
protected void assertSameFields(KeyUpdateRow expected, KeyUpdateRow actual) {
assertEquals(expected.rowType().table(), actual.rowType().table());
}
protected final void dbInsert(final KeyUpdateRow row) throws Exception
{
transactionally(new Callable<Void>() {
public Void call() throws Exception {
testStore.writeRow(session(), row);
Integer oldCount = rowDefsToCounts.get(row.rowType().table().getTableId());
oldCount = (oldCount == null) ? 1 : oldCount+1;
rowDefsToCounts.put(row.rowType().table().getTableId(), oldCount);
return null;
}
});
}
protected final void dbUpdate(final KeyUpdateRow oldRow, final KeyUpdateRow newRow) throws Exception
{
transactionally(new Callable<Void>() {
public Void call() throws Exception {
testStore.updateRow(session(), oldRow, newRow, null);
return null;
}
});
}
protected final void dbDelete(final KeyUpdateRow row) throws Exception
{
transactionally(new Callable<Void>() {
public Void call() throws Exception {
testStore.deleteRow(session(), row);
Integer oldCount = rowDefsToCounts.get(row.rowType().table().getTableId());
assertNotNull(oldCount);
rowDefsToCounts.put(row.rowType().table().getTableId(), oldCount - 1);
return null;
}
});
}
private int countAllRows() {
int total = 0;
for (Integer count : rowDefsToCounts.values()) {
total += count;
}
return total;
}
protected final void checkDB() throws Exception
{
transactionally(new Callable<Void>() {
public Void call() throws Exception {
// Records
RecordCollectingTreeRecordVisistor testVisitor = new RecordCollectingTreeRecordVisistor();
RecordCollectingTreeRecordVisistor realVisitor = new RecordCollectingTreeRecordVisistor();
testStore.traverse(session(), group, testVisitor, realVisitor);
assertEquals(testVisitor.records().size(), realVisitor.records().size());
for (int i = 0; i < testVisitor.records().size(); i++) {
assertEquals(testVisitor.records().get(i), realVisitor.records().get(i));
}
assertEquals(testVisitor.records(), realVisitor.records());
assertEquals("records count", countAllRows(), testVisitor.records().size());
// Check indexes
CollectingIndexKeyVisitor indexVisitor;
if (checkChildPKs()) {
// Vendor PK index
indexVisitor = new CollectingIndexKeyVisitor();
testStore.traverse(session(), vendorRT.table().getPrimaryKey().getIndex(), indexVisitor, -1, 0);
assertEquals(vendorPKIndex(testVisitor.records()), indexVisitor.records());
assertEquals("vendor PKs", countRows(vendorRT), indexVisitor.records().size());
// Customer PK index
indexVisitor = new CollectingIndexKeyVisitor();
testStore.traverse(session(), customerRT.table().getPrimaryKey().getIndex(), indexVisitor, -1, 0);
assertEquals(customerPKIndex(testVisitor.records()), indexVisitor.records());
assertEquals("customer PKs", countRows(customerRT), indexVisitor.records().size());
// Order PK index
indexVisitor = new CollectingIndexKeyVisitor();
testStore.traverse(session(), orderRT.table().getPrimaryKey().getIndex(), indexVisitor, -1, 0);
assertEquals(orderPKIndex(testVisitor.records()), indexVisitor.records());
assertEquals("order PKs", countRows(orderRT), indexVisitor.records().size());
// Item PK index
indexVisitor = new CollectingIndexKeyVisitor();
testStore.traverse(session(), itemRT.table().getPrimaryKey().getIndex(), indexVisitor, -1, 0);
assertEquals(itemPKIndex(testVisitor.records()), indexVisitor.records());
assertEquals("order PKs", countRows(itemRT), indexVisitor.records().size());
}
// Order priority index
indexVisitor = new CollectingIndexKeyVisitor();
testStore.traverse(session(), index(orderRT, "priority"), indexVisitor, -1, 0);
assertEquals(orderPriorityIndex(testVisitor.records()), indexVisitor.records());
assertEquals("order PKs", countRows(orderRT), indexVisitor.records().size());
// Order timestamp index
indexVisitor = new CollectingIndexKeyVisitor();
testStore.traverse(session(), index(orderRT, "when"), indexVisitor, -1, 0);
assertEquals(orderWhenIndex(testVisitor.records()), indexVisitor.records());
assertEquals("order PKs", countRows(orderRT), indexVisitor.records().size());
return null;
}
});
}
private int countRows(RowType rowType) {
return rowDefsToCounts.get(rowType.table().getTableId());
}
private Index index(RowType rowType, String indexName) {
Index index = rowType.table().getIndex(indexName);
if (index == null) {
throw new NoSuchElementException(indexName);
}
return index;
}
protected final void checkInitialState() throws Exception
{
transactionally(new Callable<Void>() {
public Void call() throws Exception {
RecordCollectingTreeRecordVisistor testVisitor = new RecordCollectingTreeRecordVisistor();
RecordCollectingTreeRecordVisistor realVisitor = new RecordCollectingTreeRecordVisistor();
testStore.traverse(session(), group, testVisitor, realVisitor);
Iterator<TreeRecord> expectedIterator = testVisitor.records().iterator();
Iterator<TreeRecord> actualIterator = realVisitor.records().iterator();
Map<Integer, Integer> expectedCounts = new HashMap<>();
expectedCounts.put(vendorRT.table().getTableId(), 0);
expectedCounts.put(customerRT.table().getTableId(), 0);
expectedCounts.put(orderRT.table().getTableId(), 0);
expectedCounts.put(itemRT.table().getTableId(), 0);
Map<Integer, Integer> actualCounts = new HashMap<>();
actualCounts.put(customerRT.table().getTableId(), 0);
actualCounts.put(vendorRT.table().getTableId(), 0);
actualCounts.put(orderRT.table().getTableId(), 0);
actualCounts.put(itemRT.table().getTableId(), 0);
while (expectedIterator.hasNext() && actualIterator.hasNext()) {
TreeRecord expected = expectedIterator.next();
TreeRecord actual = actualIterator.next();
assertEquals(expected, actual);
assertEquals(hKey((KeyUpdateRow) expected.row()), actual.hKey());
checkInitialState(actual.row());
expectedCounts.put(expected.row().rowType().table().getTableId(),
expectedCounts.get(expected.row().rowType().table().getTableId()) + 1);
actualCounts.put(actual.row().rowType().table().getTableId(),
actualCounts.get(actual.row().rowType().table().getTableId()) + 1);
}
assertEquals(2, expectedCounts.get(vendorRT.table().getTableId()).intValue());
assertEquals(6, expectedCounts.get(customerRT.table().getTableId()).intValue());
assertEquals(18, expectedCounts.get(orderRT.table().getTableId()).intValue());
assertEquals(54, expectedCounts.get(itemRT.table().getTableId()).intValue());
assertEquals(2, actualCounts.get(vendorRT.table().getTableId()).intValue());
assertEquals(6, actualCounts.get(customerRT.table().getTableId()).intValue());
assertEquals(18, actualCounts.get(orderRT.table().getTableId()).intValue());
assertEquals(54, actualCounts.get(itemRT.table().getTableId()).intValue());
assertTrue(!expectedIterator.hasNext() && !actualIterator.hasNext());
return null;
}
});
}
protected void checkInitialState(Row row)
{
RowType rowType = row.rowType();
if (rowType == vendorRT) {
assertEquals(row.value(v_vx).getInt64(), row.value(v_vid).getInt64() * 100);
} else if (rowType == customerRT) {
assertEquals(row.value(c_cx).getInt64(), row.value(c_cid).getInt64() * 100);
} else if (rowType == orderRT) {
assertEquals(row.value(o_cid).getInt64(), row.value(o_oid).getInt64() / 10);
assertEquals(row.value(o_ox).getInt64(), row.value(o_oid).getInt64() * 100);
} else if (rowType == itemRT) {
assertEquals(row.value(i_oid).getInt64(), row.value(i_iid).getInt64() / 10);
assertEquals(row.value(i_ix).getInt64(), row.value(i_iid).getInt64() * 100);
} else {
fail();
}
}
/**
* Given a list of records, a RowDef and a list of columns, extracts the index entries.
* @param records the records to take entries from
* @param rowDef the rowdef of records to look at
* @param columns a union of either Integer (the column ID) or HKeyElement.
* Any other types will throw a RuntimeException
* @return a list representing indexes of these records
*/
protected final List<List<Object>> indexFromRecords(List<TreeRecord> records, RowType rowType, Object... columns) {
List<List<Object>> indexEntries = new ArrayList<>();
for (TreeRecord record : records) {
if (record.row().rowType() == rowType) {
List<Object> indexEntry = new ArrayList<>(columns.length);
for (Object column : columns) {
final Object indexEntryElement;
if (column instanceof Integer) {
indexEntryElement = record.row().value( (Integer)column ).getInt64();
}
else if (column instanceof HKeyElement) {
indexEntryElement = record.hKey().objectArray()[ ((HKeyElement) column).getIndex() ];
}
else if (column instanceof NullSeparatorColumn) {
indexEntryElement = 0L;
}
else {
String msg = String.format(
"column must be an Integer or HKeyElement: %s in %s:",
column == null ? "null" : column.getClass().getName(),
Arrays.toString(columns)
);
throw new RuntimeException(msg);
}
indexEntry.add(indexEntryElement);
}
indexEntries.add(indexEntry);
}
}
Collections.sort(indexEntries,
new Comparator<List<Object>>() {
@Override
public int compare(List<Object> x, List<Object> y) {
// compare priorities
Long px = (Long) x.get(0);
Long py = (Long) y.get(0);
return px.compareTo(py);
}
}
);
return indexEntries;
}
protected KeyUpdateRow createTestRow (RowType rowType, Object... values) {
return new KeyUpdateRow (rowType, store(), values);
}
protected KeyUpdateRow updateRow (KeyUpdateRow oldRow, int column, Long newValue, KeyUpdateRow newParent) {
KeyUpdateRow row = updateRow (oldRow, column, newValue);
row.parent(newParent);
KeyUpdateRow newGrandparent = newParent == null ? null : newParent.parent();
row.hKey(hKey(row, newParent, newGrandparent));
return row;
}
protected KeyUpdateRow updateRow (KeyUpdateRow row, int column, Long newValue) {
List<Value> values = row.values();
List<Value> newValues = new ArrayList<Value>(row.rowType().nFields());
for (Value value : values) {
newValues.add(new Value(value.getType(), value.getInt64()));
}
if (newValue == null) {
newValues.get(column).putNull();
} else {
newValues.get(column).putInt64(newValue);
}
KeyUpdateRow copy = new KeyUpdateRow (row.rowType(), row.getStore(), newValues);
copy.parent(row.parent());
copy.hKey(hKey(copy, row.parent()));
return copy;
}
protected KeyUpdateRow updateRow (KeyUpdateRow row, int column1, Long newValue1, int column2, Long newValue2) {
List<Value> values = row.values();
List<Value> newValues = new ArrayList<Value>(row.rowType().nFields());
for (Value value : values) {
newValues.add(new Value(value.getType(), value.getInt64()));
}
if (newValue1 == null) {
newValues.get(column1).putNull();
} else {
newValues.get(column1).putInt64(newValue1);
}
if (newValue2 == null) {
newValues.get(column2).putNull();
} else {
newValues.get(column2).putInt64(newValue2);
}
KeyUpdateRow copy = new KeyUpdateRow (row.rowType(), row.getStore(), newValues);
copy.parent(row.parent());
copy.hKey(hKey(copy, row.parent()));
return copy;
}
protected KeyUpdateRow updateRow (KeyUpdateRow row,
int column1, Long newValue1,
int column2, Long newValue2,
KeyUpdateRow newParent) {
KeyUpdateRow newRow = updateRow(row, column1, newValue1, column2, newValue2);
newRow.parent(newParent);
KeyUpdateRow newGrandparent = newParent == null ? null : newParent.parent();
newRow.hKey(hKey(newRow, newParent, newGrandparent));
return newRow;
}
protected KeyUpdateRow copyRow(KeyUpdateRow row)
{
KeyUpdateRow copy = new KeyUpdateRow (row.rowType(), row.getStore(), row.values());
copy.parent(row.parent());
copy.hKey(hKey(row, row.parent()));
return copy;
}
protected final KeyUpdateRow kurow(RowType type, Object... values) {
KeyUpdateRow row = new KeyUpdateRow(type, store(), values);
row.hKey(hKey(row));
return row;
}
protected final KeyUpdateRow row (KeyUpdateRow parent, RowType type, Object... values) {
KeyUpdateRow row = new KeyUpdateRow(type, store(), values);
row.hKey(hKey(row, parent, null));
return row;
}
protected final KeyUpdateRow row (KeyUpdateRow parent, KeyUpdateRow grandparent, RowType type, Object... values) {
KeyUpdateRow row = new KeyUpdateRow (type, store(), values);
row.hKey(hKey(row, parent, grandparent));
return row;
}
protected static final class HKeyElement {
private final int index;
public static HKeyElement from(int index) {
return new HKeyElement(index);
}
public HKeyElement(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
protected void startMonitoringHKeyPropagation()
{
Tap.setEnabled(HKEY_PROPAGATION_TAP_PATTERN, true);
Tap.reset(HKEY_PROPAGATION_TAP_PATTERN);
}
protected void checkHKeyPropagation(int propagateDownGroupCalls, int propagateDownGroupRowReplace)
{
for (TapReport report : Tap.getReport(HKEY_PROPAGATION_TAP_PATTERN)) {
if (report.getName().endsWith("propagate_hkey_change")) {
assertEquals(propagateDownGroupCalls, report.getInCount());
} else if (report.getName().endsWith("propagate_hkey_change_row_replace")) {
assertEquals(propagateDownGroupRowReplace, report.getInCount());
} else {
fail();
}
}
}
protected HKey hKey(KeyUpdateRow row, KeyUpdateRow newParent)
{
return hKey(row, newParent, null);
}
private static final String HKEY_PROPAGATION_TAP_PATTERN = ".*propagate_hkey_change.*";
abstract protected void createSchema() throws Exception;
abstract protected void populateTables() throws Exception;
abstract protected boolean checkChildPKs();
abstract protected HKey hKey(KeyUpdateRow row);
abstract protected HKey hKey(KeyUpdateRow row, KeyUpdateRow newParent, KeyUpdateRow newGrandparent);
abstract protected List<List<Object>> vendorPKIndex(List<TreeRecord> records);
abstract protected List<List<Object>> customerPKIndex(List<TreeRecord> records);
abstract protected List<List<Object>> orderPKIndex(List<TreeRecord> records);
abstract protected List<List<Object>> itemPKIndex(List<TreeRecord> records);
abstract protected List<List<Object>> orderPriorityIndex(List<TreeRecord> records);
abstract protected List<List<Object>> orderWhenIndex(List<TreeRecord> records);
protected TestStore testStore;
protected Map<Integer,Integer> rowDefsToCounts;
protected static class NullSeparatorColumn {}
}