ghidra/Ghidra/Framework/DB/src/test/java/db/DBFixedKeyTableTest.java
ghidra1 6783ae669f GT-3294 Added support for sparse DB schemas. No need to version
ProgramUserData which does not utilize index tables
2020-11-24 16:10:38 -05:00

1202 lines
36 KiB
Java

/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.junit.*;
import db.buffers.*;
import generic.test.AbstractGenericTest;
import ghidra.util.exception.CancelledException;
import utilities.util.FileUtilities;
public class DBFixedKeyTableTest extends AbstractGenericTest {
private static final int BUFFER_SIZE = 256;// keep small for chained buffer testing
private static final int CACHE_SIZE = 4 * 1024 * 1024;
private static final int SMALL_ITER_REC_CNT = 16000;
private static final int BIG_ITER_REC_CNT = 48000;
private static final String table1Name = "TABLE1";
private File testDir;
private static final String dbName = "test";
private BufferFileManager fileMgr;
private DBHandle dbh;
private BufferFile bfile;
private static FixedField10 MAX_VALUE = new FixedField10();
static {
byte[] maxBytes = new byte[10];
Arrays.fill(maxBytes, (byte) 0xff);
MAX_VALUE.setBinaryData(maxBytes);
}
private static FixedField10 MIN_VALUE = new FixedField10(); // all zeros
@Before
public void setUp() throws Exception {
testDir = createTempDirectory(getClass().getSimpleName());
dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
}
@After
public void tearDown() throws Exception {
if (dbh != null) {
dbh.close();
}
if (bfile != null) {
bfile.close();
}
FileUtilities.deleteDir(testDir);
}
private void saveAsAndReopen(String name) throws IOException {
try {
BufferFileManager mgr = DBTestUtils.getBufferFileManager(testDir, name);
BufferFile bf = new LocalManagedBufferFile(dbh.getBufferSize(), mgr, -1);
dbh.saveAs(bf, true, null);
dbh.close();
fileMgr = mgr;
}
catch (CancelledException e) {
Assert.fail("Should not happen");
}
bfile = new LocalManagedBufferFile(fileMgr, true, -1, -1);
dbh = new DBHandle(bfile);
}
private FixedField10 insertOneFixedKeyRecord(boolean testStoredDB, boolean testGetRecord,
int varDataSize) throws IOException {
long txId = dbh.startTransaction();
Table table =
DBTestUtils.createFixedKeyTable(dbh, table1Name, DBTestUtils.ALL_TYPES, false, false);
Record rec = null;
try {
rec = DBTestUtils.createFixedKeyRecord(table, varDataSize, true);
}
catch (DuplicateKeyException e) {
Assert.fail("Duplicate key error");
}
dbh.endTransaction(txId, true);
if (testStoredDB) {
saveAsAndReopen(dbName);
table = dbh.getTable(table1Name);
}
if (testGetRecord) {
assertEquals(rec, table.getRecord(rec.getKeyField()));
}
return (FixedField10) rec.getKeyField();
}
/**
* Test normal record storage within a leaf node.
* Chained-buffers will not be utilized.
* @throws IOException
*/
@Test
public void testInsertSmallFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, false, 1);
}
/**
* Test normal record storage within a leaf node.
* Use empty value for variable length fields.
* Chained-buffers will not be utilized.
* @throws IOException
*/
@Test
public void testInsertFixedKeyRecordWithEmptyField() throws IOException {
insertOneFixedKeyRecord(false, false, 0);
}
/**
* Test normal record storage within a leaf node.
* Use NULL value for variable length fields.
* Chained-buffers will not be utilized.
* @throws IOException
*/
@Test
public void testInsertFixedKeyRecordWithNullField() throws IOException {
insertOneFixedKeyRecord(false, false, -1);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will not utilize an index buffer.
* @throws IOException
*/
@Test
public void testInsertSingleChainedBufferFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, false, BUFFER_SIZE / 4);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will be forced to utilize 1 buffer for storing indexes.
* @throws IOException
*/
@Test
public void testInsertMultChainedBuffersFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, false, 2 * BUFFER_SIZE);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will be forced to utilize 3 buffers for storing indexes.
* @throws IOException
*/
@Test
public void testInsertVeryLargeChainedBuffersFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, false, 2 * BUFFER_SIZE * (BUFFER_SIZE / 4));
}
/**
* Test normal record storage within a leaf node.
* Chained-buffers will not be utilized.
* @throws IOException
*/
@Test
public void testGetSmallFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, true, 1);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will not utilize an index buffer.
* @throws IOException
*/
@Test
public void testGetSingleChainedBufferFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, true, BUFFER_SIZE / 4);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will be forced to utilize 1 buffer for storing indexes.
* @throws IOException
*/
@Test
public void testGetMultChainedBuffersFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, true, 2 * BUFFER_SIZE);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will be forced to utilize 3 buffers for storing indexes.
* @throws IOException
*/
@Test
public void testGetVeryLargeChainedBuffersFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(false, true, 2 * BUFFER_SIZE * (BUFFER_SIZE / 4));
}
/**
* Test normal record storage within a leaf node.
* Chained-buffers will not be utilized.
* @throws IOException
*/
@Test
public void testGetStoredSmallFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(true, true, 1);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will not utilize an index buffer.
* @throws IOException
*/
@Test
public void testGetStoredSingleChainedBufferFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(true, true, BUFFER_SIZE / 4);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will be forced to utilize 1 buffer for storing indexes.
* @throws IOException
*/
@Test
public void testGetStoredMultChainedBuffersFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(true, true, 2 * BUFFER_SIZE);
}
/**
* Test the use of chained-buffers for record storage.
* Chained-buffers will be forced to utilize 3 buffers for storing indexes.
* @throws IOException
*/
@Test
public void testGetStoredVeryLargeChainedBuffersFixedKeyRecord() throws IOException {
insertOneFixedKeyRecord(true, true, 2 * BUFFER_SIZE * (BUFFER_SIZE / 4));
}
@Test
public void testGetMissingFixedKeyRecord() throws IOException {
FixedField10 key = insertOneFixedKeyRecord(false, false, 1);
byte[] k = key.getBinaryData();
++k[0];
FixedField10 otherKey = new FixedField10(k); // incremented key
assertNull(dbh.getTable(table1Name).getRecord(otherKey));
}
@Test
public void testStoredGetMissingFixedKeyRecord() throws IOException {
FixedField10 key = insertOneFixedKeyRecord(true, false, 1);
byte[] k = key.getBinaryData();
++k[0];
FixedField10 otherKey = new FixedField10(k); // incremented key
assertNull(dbh.getTable(table1Name).getRecord(otherKey));
}
/**
* Insert the specified number of records using random keys.
* @param table table instance, if null one will be created
* @param recordCnt number of records to insert.
* @param varDataSize size of variable length data fields.
* @return Record[] records which were inserted.
*/
private Record[] createRandomFixedKeyTableRecords(Table table, int recordCnt, int varDataSize)
throws IOException {
long txId = dbh.startTransaction();
if (table == null) {
table = DBTestUtils.createFixedKeyTable(dbh, table1Name, DBTestUtils.ALL_TYPES, false,
false);
}
Record[] recs = new Record[recordCnt];
for (int i = 0; i < recordCnt; i++) {
try {
recs[i] = DBTestUtils.createFixedKeyRecord(table, varDataSize, true);
}
catch (DuplicateKeyException e) {
Assert.fail("Duplicate key error");
}
}
dbh.endTransaction(txId, true);
return recs;
}
/**
* Insert the specified number of records using random keys.
* @param recordCnt number of records to insert.
* @param varDataSize size of variable length data fields.
* @return Record[] records which were inserted.
*/
private Record[] createOrderedFixedKeyTableRecords(int recordCnt, long keyIncrement,
int varDataSize) throws IOException {
long txId = dbh.startTransaction();
Table table =
DBTestUtils.createFixedKeyTable(dbh, table1Name, DBTestUtils.ALL_TYPES, false, false);
FixedField10 key = new FixedField10(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
Record[] recs = new Record[recordCnt];
for (int i = 0; i < recordCnt; i++) {
try {
recs[i] = DBTestUtils.createRecord(table, key, varDataSize, true);
}
catch (DuplicateKeyException e) {
Assert.fail("Duplicate key error");
}
key = (FixedField10) DBTestUtils.addToFixedField(key, keyIncrement);
}
dbh.endTransaction(txId, true);
return recs;
}
/**
* Test record iterator.
* @param testStoredDB test against a stored database if true, else test against cached database only.
* @param recordCnt number of records to test
* @param keyIncrement key increment, 0 = random
* @param varDataSize size of variable length data fields.
* @throws IOException
*/
private void iterateFixedKeyRecords(boolean testStoredDB, int recordCnt, long keyIncrement,
int varDataSize) throws IOException {
Record[] recs = null;
if (keyIncrement == 0) {
recs = createRandomFixedKeyTableRecords(null, recordCnt, varDataSize);
}
else {
recs = createOrderedFixedKeyTableRecords(recordCnt, keyIncrement, varDataSize);
}
Arrays.sort(recs);
if (testStoredDB) {
saveAsAndReopen(dbName);
}
Table table = dbh.getTable(table1Name);
assertEquals(recordCnt, table.getRecordCount());
//assertEquals(recs[recordCnt - 1].getKeyField(), table.getMaxKey());
// Forward iteration (no start)
int recIx = 0;
RecordIterator iter = table.iterator();
while (iter.hasNext()) {
assertEquals(recs[recIx++], iter.next());
}
assertEquals(recordCnt, recIx);
// Forward iteration (start in middle)
recIx = recordCnt / 2;
iter = table.iterator(recs[recIx].getKeyField());
while (iter.hasNext()) {
assertEquals(recs[recIx++], iter.next());
}
assertEquals(recordCnt, recIx);
// Reverse iteration (no start)
recIx = recordCnt - 1;
iter = table.iterator(MAX_VALUE);
while (iter.hasPrevious()) {
assertEquals(recs[recIx--], iter.previous());
}
assertEquals(-1, recIx);
// Reverse iteration (start in middle)
recIx = recordCnt / 2;
iter = table.iterator(recs[recIx].getKeyField());
while (iter.hasPrevious()) {
assertEquals(recs[recIx--], iter.previous());
}
assertEquals(-1, recIx);
// Range iteration (forward)
int minIx = recordCnt / 10;
int maxIx = 2 * minIx;
iter = table.iterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[minIx].getKeyField());
recIx = minIx;
while (iter.hasNext()) {
assertEquals(recs[recIx++], iter.next());
}
assertEquals(recIx, maxIx + 1);
// Range iteration (reverse)
iter = table.iterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[maxIx].getKeyField());
recIx = maxIx;
while (iter.hasPrevious()) {
assertEquals(recs[recIx--], iter.previous());
}
assertEquals(recIx, minIx - 1);
// Assumes consecutive keys do not exist - assumption could be a problem with random keys
if (keyIncrement > 1 || keyIncrement < -1) {
// Range iteration (forward) starting at min and not on existing record.
FixedField missingMinKey = DBTestUtils.addToFixedField(recs[minIx].getKeyField(), -1);
FixedField missingMaxKey = DBTestUtils.addToFixedField(recs[maxIx].getKeyField(), 1);
iter = table.iterator(DBTestUtils.addToFixedField(missingMinKey, 1), missingMaxKey,
missingMinKey);
recIx = minIx;
assertTrue(!iter.hasPrevious());
while (iter.hasNext()) {
assertEquals(recs[recIx++], iter.next());
}
assertEquals(recIx, maxIx + 1);
// Range iteration - no records (forward and reverse).
iter = table.iterator(missingMinKey, missingMinKey, missingMinKey);
assertTrue(!iter.hasNext());
assertTrue(!iter.hasPrevious());
iter = table.iterator(missingMaxKey, missingMaxKey, missingMaxKey);
assertTrue(!iter.hasNext());
assertTrue(!iter.hasPrevious());
// Range iteration (reverse) starting at max and not on existing record
iter = table.iterator(missingMinKey, DBTestUtils.addToFixedField(missingMaxKey, -1),
missingMaxKey);
recIx = maxIx;
assertTrue(!iter.hasNext());
while (iter.hasPrevious()) {
assertEquals(recs[recIx--], iter.previous());
}
assertEquals(recIx, minIx - 1);
// Range iteration (reverse) starting at mid and not on existing record
int midIx = (minIx + maxIx) >>> 1;
FixedField missingMidKey = DBTestUtils.addToFixedField(recs[midIx].getKeyField(), -1);
iter = table.iterator(missingMinKey, missingMaxKey, missingMidKey);
recIx = midIx - 1;
while (iter.hasPrevious()) {
assertEquals(recs[recIx--], iter.previous());
}
assertEquals(recIx, minIx - 1);
// Range iteration (forward) starting at mid and not on existing record
iter = table.iterator(missingMinKey, missingMaxKey, missingMidKey);
recIx = midIx;
while (iter.hasNext()) {
assertEquals(recs[recIx++], iter.next());
}
assertEquals(recIx, maxIx + 1);
}
// delete (forward)
long txId = dbh.startTransaction();
iter = table.iterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[minIx].getKeyField());
recIx = minIx;
while (iter.hasNext()) {
iter.next();
iter.delete();
++recIx;
}
dbh.endTransaction(txId, true);
recordCnt -= maxIx - minIx + 1;
assertEquals(table.getRecordCount(), recordCnt);
iter = table.iterator(recs[minIx - 1].getKeyField(), recs[maxIx + 1].getKeyField(),
recs[minIx - 1].getKeyField());
assertEquals(recs[minIx - 1], iter.next());
assertEquals(recs[maxIx + 1], iter.next());
// Range iteration (reverse)
txId = dbh.startTransaction();
minIx = minIx * 3;
maxIx += minIx;
iter = table.iterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[maxIx].getKeyField());
recIx = maxIx;
while (iter.hasPrevious()) {
iter.previous();
iter.delete();
--recIx;
}
dbh.endTransaction(txId, true);
recordCnt -= maxIx - minIx + 1;
assertEquals(table.getRecordCount(), recordCnt);
iter = table.iterator(recs[minIx - 1].getKeyField(), recs[maxIx + 1].getKeyField(),
recs[minIx - 1].getKeyField());
assertEquals(recs[minIx - 1], iter.next());
assertEquals(recs[maxIx + 1], iter.next());
}
@Test
public void testFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, SMALL_ITER_REC_CNT, 1, 1);
}
@Test
public void testRandomFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, SMALL_ITER_REC_CNT, 0, 1);
}
@Test
public void testForwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, SMALL_ITER_REC_CNT, 2, 1);
}
@Test
public void testBackwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, SMALL_ITER_REC_CNT, -2, 1);
}
@Test
public void testStoredFixedKeyRandomRecordIterator() throws IOException {
iterateFixedKeyRecords(true, SMALL_ITER_REC_CNT, 0, 1);
}
@Test
public void testStoredForwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(true, SMALL_ITER_REC_CNT, 2, 1);
}
@Test
public void testStoredBackwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(true, SMALL_ITER_REC_CNT, -2, 1);
}
@Test
public void testBigRandomFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, BIG_ITER_REC_CNT, 0, 1);
}
@Test
public void testBigForwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, BIG_ITER_REC_CNT, 2, 1);
}
@Test
public void testBigBackwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(false, BIG_ITER_REC_CNT, -2, 1);
}
@Test
public void testBigStoredRandomFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(true, BIG_ITER_REC_CNT, 0, 1);
}
@Test
public void testBigStoredForwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(true, BIG_ITER_REC_CNT, 2, 1);
}
@Test
public void testBigStoredBackwardFixedKeyRecordIterator() throws IOException {
iterateFixedKeyRecords(true, BIG_ITER_REC_CNT, -2, 1);
}
/**
* Test key iterator.
* @param testStoredDB test against a stored database if true, else test against cached database only.
* @param recordCnt number of records to test
* @param keyIncrement key increment, 0 = random
* @param varDataSize size of variable length data fields.
* @throws IOException
*/
private void iterateFixedKeys(boolean testStoredDB, int recordCnt, long keyIncrement,
int varDataSize) throws IOException {
Record[] recs = null;
if (keyIncrement == 0) {
recs = createRandomFixedKeyTableRecords(null, recordCnt, varDataSize);
}
else {
recs = createOrderedFixedKeyTableRecords(recordCnt, keyIncrement, varDataSize);
}
Arrays.sort(recs);
if (testStoredDB) {
saveAsAndReopen(dbName);
}
Table table = dbh.getTable(table1Name);
assertEquals(recordCnt, table.getRecordCount());
// Forward iteration (no start)
int recIx = 0;
DBFieldIterator iter = table.fieldKeyIterator();
while (iter.hasNext()) {
assertEquals(recs[recIx++].getKeyField(), iter.next());
}
assertEquals(recordCnt, recIx);
// Forward iteration (start in middle)
recIx = recordCnt / 2;
iter = table.fieldKeyIterator(recs[recIx].getKeyField());
while (iter.hasNext()) {
assertEquals(recs[recIx++].getKeyField(), iter.next());
}
assertEquals(recordCnt, recIx);
// Reverse iteration (no start)
recIx = recordCnt - 1;
iter = table.fieldKeyIterator(MAX_VALUE);
while (iter.hasPrevious()) {
assertEquals(recs[recIx--].getKeyField(), iter.previous());
}
assertEquals(-1, recIx);
// Reverse iteration (start in middle)
recIx = recordCnt / 2;
iter = table.fieldKeyIterator(recs[recIx].getKeyField());
while (iter.hasPrevious()) {
assertEquals(recs[recIx--].getKeyField(), iter.previous());
}
assertEquals(-1, recIx);
// Range iteration (forward) starting at min on existing record
int minIx = recordCnt / 10;
int maxIx = 2 * minIx;
iter = table.fieldKeyIterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[minIx].getKeyField());
recIx = minIx;
while (iter.hasNext()) {
assertEquals(recs[recIx++].getKeyField(), iter.next());
}
assertEquals(recIx, maxIx + 1);
// Range iteration (reverse) starting at max on existing record
iter = table.fieldKeyIterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[maxIx].getKeyField());
recIx = maxIx;
while (iter.hasPrevious()) {
assertEquals(recs[recIx--].getKeyField(), iter.previous());
}
assertEquals(recIx, minIx - 1);
// Assumes consecutive keys do not exist - assumption could be a problem with random keys
if (keyIncrement != 1) {
// Range iteration (forward) starting at min and not on existing record.
FixedField missingMinKey = DBTestUtils.addToFixedField(recs[minIx].getKeyField(), -1);
FixedField missingMaxKey = DBTestUtils.addToFixedField(recs[maxIx].getKeyField(), 1);
iter = table.fieldKeyIterator(missingMinKey, missingMaxKey, missingMinKey);
recIx = minIx;
assertTrue(!iter.hasPrevious());
while (iter.hasNext()) {
assertEquals(recs[recIx++].getKeyField(), iter.next());
}
assertEquals(recIx, maxIx + 1);
// Range iteration - no records (forward and reverse).
iter = table.fieldKeyIterator(missingMinKey, missingMinKey, missingMinKey);
assertTrue(!iter.hasNext());
assertTrue(!iter.hasPrevious());
iter = table.fieldKeyIterator(missingMaxKey, missingMaxKey, missingMaxKey);
assertTrue(!iter.hasNext());
assertTrue(!iter.hasPrevious());
// Range iteration (reverse) starting at max and not on existing record
iter = table.fieldKeyIterator(missingMinKey, missingMaxKey, missingMaxKey);
recIx = maxIx;
assertTrue(!iter.hasNext());
while (iter.hasPrevious()) {
assertEquals(recs[recIx--].getKeyField(), iter.previous());
}
assertEquals(recIx, minIx - 1);
// Range iteration (reverse) starting at mid and not on existing record
int midIx = (minIx + maxIx) / 2;
FixedField missingMidKey = DBTestUtils.addToFixedField(recs[midIx].getKeyField(), -1);
iter = table.fieldKeyIterator(missingMinKey, missingMaxKey, missingMidKey);
recIx = midIx - 1;
while (iter.hasPrevious()) {
assertEquals(recs[recIx--].getKeyField(), iter.previous());
}
assertEquals(recIx, minIx - 1);
// Range iteration (forward) starting at mid and not on existing record
iter = table.fieldKeyIterator(missingMinKey, missingMaxKey, missingMidKey);
recIx = midIx;
while (iter.hasNext()) {
assertEquals(recs[recIx++].getKeyField(), iter.next());
}
assertEquals(recIx, maxIx + 1);
}
// delete (forward)
long txId = dbh.startTransaction();
iter = table.fieldKeyIterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[minIx].getKeyField());
recIx = minIx;
while (iter.hasNext()) {
assertEquals(recs[recIx++].getKeyField(), iter.next());
iter.delete();
}
assertEquals(recIx, maxIx + 1);
dbh.endTransaction(txId, true);
recordCnt -= maxIx - minIx + 1;
assertEquals(table.getRecordCount(), recordCnt);
iter = table.fieldKeyIterator(recs[minIx - 1].getKeyField(), recs[maxIx + 1].getKeyField(),
recs[minIx - 1].getKeyField());
assertEquals(recs[minIx - 1].getKeyField(), iter.next());
assertEquals(recs[maxIx + 1].getKeyField(), iter.next());
// Range iteration (reverse)
txId = dbh.startTransaction();
minIx = minIx * 3;
maxIx += minIx;
iter = table.fieldKeyIterator(recs[minIx].getKeyField(), recs[maxIx].getKeyField(),
recs[maxIx].getKeyField());
recIx = maxIx;
while (iter.hasPrevious()) {
assertEquals(recs[recIx--].getKeyField(), iter.previous());
iter.delete();
}
assertEquals(recIx, minIx - 1);
dbh.endTransaction(txId, true);
recordCnt -= maxIx - minIx + 1;
assertEquals(table.getRecordCount(), recordCnt);
iter = table.fieldKeyIterator(recs[minIx - 1].getKeyField(), recs[maxIx + 1].getKeyField(),
recs[minIx - 1].getKeyField());
assertEquals(recs[minIx - 1].getKeyField(), iter.next());
assertEquals(recs[maxIx + 1].getKeyField(), iter.next());
}
@Test
public void testRandomFixedKeyIterator() throws IOException {
iterateFixedKeys(false, SMALL_ITER_REC_CNT, 0, 1);
}
@Test
public void testForwardFixedKeyIterator() throws IOException {
iterateFixedKeys(false, SMALL_ITER_REC_CNT, 1, 1);
}
@Test
public void testForwardSkippingFixedKeyIterator() throws IOException {
iterateFixedKeys(false, SMALL_ITER_REC_CNT, 3, 1);
}
@Test
public void testForwardDeleteIterator() throws IOException {
Record[] recs = createOrderedFixedKeyTableRecords(SMALL_ITER_REC_CNT, 2, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
assertEquals(SMALL_ITER_REC_CNT, table.getRecordCount());
int recIx = 0;
long txId = dbh.startTransaction();
RecordIterator iter = table.iterator();
while (iter.hasNext()) {
assertEquals(recs[recIx++], iter.next());
iter.delete();
}
assertEquals(SMALL_ITER_REC_CNT, recIx);
dbh.endTransaction(txId, true);
}
@Test
public void testReverseDeleteIterator() throws IOException {
Record[] recs = createOrderedFixedKeyTableRecords(SMALL_ITER_REC_CNT, 2, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
assertEquals(SMALL_ITER_REC_CNT, table.getRecordCount());
long txId = dbh.startTransaction();
int recIx = SMALL_ITER_REC_CNT - 1;
RecordIterator iter = table.iterator(recs[recIx].getKeyField());
while (iter.hasPrevious()) {
assertEquals(recs[recIx--], iter.previous());
iter.delete();
}
assertEquals(-1, recIx);
dbh.endTransaction(txId, true);
}
@Test
public void testGetFixedKeyRecordAfter() throws IOException {
Record[] recs = createRandomFixedKeyTableRecords(null, 16000, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
// After test
for (int i = 1000; i < 16000; i += 1000) {
Record rec = table.getRecordAfter(recs[i].getKeyField());
assertEquals(rec.getKeyField(), recs[i + 1].getKeyField());
}
// End test
Record rec = table.getRecordAfter(recs[15999].getKeyField());
assertNull(rec);
}
private int findHoleAfterFixedKey(Record[] recs, int startIx) {
for (int i = startIx; i < recs.length - 1; i++) {
FixedField f = DBTestUtils.addToFixedField(recs[i].getKeyField(), 1);
if (f.compareTo(recs[i + 1].getKeyField()) < 0) {
return i;
}
}
return -1;
}
private int findHoleBeforeFixedKey(Record[] recs, int startIx) {
for (int i = startIx; i < recs.length; i++) {
FixedField f = DBTestUtils.addToFixedField(recs[i - 1].getKeyField(), 1);
if (f.compareTo(recs[i].getKeyField()) < 0) {
return i;
}
}
return -1;
}
@Test
public void testGetFixedKeyRecordAtOrAfter() throws IOException {
Record[] recs = createRandomFixedKeyTableRecords(null, 16000, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
// At and After tests
for (int i = 1000; i < 16000; i += 1000) {
Record rec = table.getRecordAtOrAfter(recs[i].getKeyField());
assertEquals(rec.getKeyField(), recs[i].getKeyField());
int ix = findHoleAfterFixedKey(recs, i + 500);
if (ix < 0) {
Assert.fail("Bad test data");
}
rec = table.getRecordAtOrAfter(DBTestUtils.addToFixedField(recs[ix].getKeyField(), 1));
assertEquals(recs[ix + 1].getKeyField(), rec.getKeyField());
}
// End tests
Field lastKey = recs[15999].getKeyField();
if (lastKey == MAX_VALUE) {
Assert.fail("Bad test data");
}
Record rec = table.getRecordAtOrAfter(lastKey);
assertEquals(rec.getKeyField(), lastKey);
rec = table.getRecordAtOrAfter(DBTestUtils.addToFixedField(lastKey, 1));
assertNull(rec);
}
@Test
public void testGetFixedKeyRecordAtOrBefore() throws IOException {
Record[] recs = createRandomFixedKeyTableRecords(null, 16000, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
// At and Before tests
for (int i = 1000; i < 16000; i += 1000) {
Record rec = table.getRecordAtOrBefore(recs[i].getKeyField());
assertEquals(rec.getKeyField(), recs[i].getKeyField());
int ix = findHoleBeforeFixedKey(recs, i + 500);
if (ix < 0) {
Assert.fail("Bad test data");
}
rec =
table.getRecordAtOrBefore(DBTestUtils.addToFixedField(recs[ix].getKeyField(), -1));
assertEquals(recs[ix - 1].getKeyField(), rec.getKeyField());
}
// End tests
Field firstKey = recs[0].getKeyField();
if (firstKey.equals(MIN_VALUE)) {
Assert.fail("Bad test data");
}
Record rec = table.getRecordAtOrBefore(firstKey);
assertEquals(rec.getKeyField(), firstKey);
rec = table.getRecordAtOrBefore(DBTestUtils.addToFixedField(firstKey, -1));
assertNull(rec);
}
@Test
public void testDeleteFixedKeyRecord() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
long txId = dbh.startTransaction();
for (int i = 0; i < cnt; i += 1000) {
table.deleteRecord(recs[i].getKeyField());
assertEquals(--cnt, table.getRecordCount());
}
dbh.endTransaction(txId, true);
RecordIterator iter = table.iterator();
int recIx = 1;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
if ((recIx % 1000) == 0) {
++recIx;
}
}
assertEquals(SMALL_ITER_REC_CNT + 1, recIx);
}
@Test
public void testForwardDeleteFixedKeyRecord() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
long txId = dbh.startTransaction();
int count = cnt;
for (int i = 0; i < count; i++) {
table.deleteRecord(recs[i].getKeyField());
assertEquals(--cnt, table.getRecordCount());
}
dbh.endTransaction(txId, true);
}
@Test
public void testReverseDeleteFixedKeyRecord() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
long txId = dbh.startTransaction();
for (int i = cnt - 1; i >= 0; i--) {
table.deleteRecord(recs[i].getKeyField());
assertEquals(--cnt, table.getRecordCount());
}
dbh.endTransaction(txId, true);
}
@Test
public void testDeleteAllFixedKeyRecords() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
long txId = dbh.startTransaction();
Table table = dbh.getTable(table1Name);
table.deleteAll();
dbh.endTransaction(txId, true);
assertEquals(0, table.getRecordCount());
RecordIterator iter = table.iterator();
assertTrue(!iter.hasNext());
// Repopulate table
recs = createRandomFixedKeyTableRecords(table, cnt, 1);
assertEquals(cnt, table.getRecordCount());
Arrays.sort(recs);
iter = table.iterator();
int recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
}
private void deleteFixedKeyRangeRecords(int count, int startIx, int endIx) throws IOException {
Record[] recs = createRandomFixedKeyTableRecords(null, count, 1);
Arrays.sort(recs);
long txId = dbh.startTransaction();
Table table = dbh.getTable(table1Name);
table.deleteRecords(recs[startIx].getKeyField(), recs[endIx].getKeyField());
dbh.endTransaction(txId, true);
RecordIterator iter = table.iterator();
int recIx = startIx != 0 ? 0 : (endIx + 1);
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
if (recIx == startIx) {
recIx = endIx + 1;
}
}
assertEquals(recIx, count);
}
@Test
public void testDeleteFixedKeyRangeRecords() throws IOException {
deleteFixedKeyRangeRecords(SMALL_ITER_REC_CNT, SMALL_ITER_REC_CNT / 4,
SMALL_ITER_REC_CNT / 2);
}
@Test
public void testDeleteFixedKeyRangeRecords2() throws IOException {
deleteFixedKeyRangeRecords(SMALL_ITER_REC_CNT, (SMALL_ITER_REC_CNT / 4) + 1,
(SMALL_ITER_REC_CNT / 2) + 1);
}
@Test
public void testDeleteFixedKeyRangeAllRecords() throws IOException {
deleteFixedKeyRangeRecords(SMALL_ITER_REC_CNT, 0, SMALL_ITER_REC_CNT - 1);
}
@Test
public void testUpdateFixedKeyRecord() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
long txId = dbh.startTransaction();
for (int i = 0; i < cnt; i += 100) {
DBTestUtils.fillRecord(recs[i], BUFFER_SIZE);
table.putRecord(recs[i]);
}
dbh.endTransaction(txId, true);
RecordIterator iter = table.iterator();
int recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
}
@Test
public void testUpdateBigFixedKeyRecord() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
long txId = dbh.startTransaction();
for (int i = 0; i < cnt; i += 100) {
DBTestUtils.fillRecord(recs[i], (BUFFER_SIZE / 8) * BUFFER_SIZE);
table.putRecord(recs[i]);
}
dbh.endTransaction(txId, true);
RecordIterator iter = table.iterator();
int recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
txId = dbh.startTransaction();
for (int i = 0; i < cnt; i += 100) {
DBTestUtils.fillRecord(recs[i], 1);
table.putRecord(recs[i]);
}
dbh.endTransaction(txId, true);
iter = table.iterator();
recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
}
@Test
public void testUpdateUndoFixedKeyRecords() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
assertTrue(!dbh.canUndo());
assertTrue(!dbh.canRedo());
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
assertTrue(dbh.canUndo());
assertTrue(!dbh.canRedo());
// Update records
long txId = dbh.startTransaction();
for (int i = 0; i < cnt; i += 100) {
Record rec = table.getSchema().createRecord(recs[i].getKeyField());
DBTestUtils.fillRecord(rec, (BUFFER_SIZE / 8) * BUFFER_SIZE);
table.putRecord(rec);
}
assertEquals(cnt, table.getRecordCount());
assertTrue(!dbh.canUndo());
assertTrue(!dbh.canRedo());
dbh.endTransaction(txId, false);// rollback updates
assertTrue(dbh.canUndo());
assertTrue(!dbh.canRedo());
RecordIterator iter = table.iterator();
int recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
// Add records
txId = dbh.startTransaction();
for (int i = 0; i < 100; i++) {
try {
DBTestUtils.createFixedKeyRecord(table, BUFFER_SIZE, true);
}
catch (DuplicateKeyException e) {
Assert.fail("Duplicate key error");
}
}
dbh.endTransaction(txId, true);
assertTrue(dbh.undo());
iter = table.iterator();
recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
}
@Test
public void testUpdateUndoRedoFixedKeyRecords() throws IOException {
int cnt = SMALL_ITER_REC_CNT;
assertTrue(!dbh.canUndo());
assertTrue(!dbh.canRedo());
Record[] recs = createRandomFixedKeyTableRecords(null, cnt, 1);
assertTrue(dbh.canUndo());
assertTrue(!dbh.canRedo());
Arrays.sort(recs);
Table table = dbh.getTable(table1Name);
// Update records
long txId = dbh.startTransaction();
for (int i = 0; i < cnt; i += 100) {
DBTestUtils.fillRecord(recs[i], (BUFFER_SIZE / 8) * BUFFER_SIZE);
table.putRecord(recs[i]);
}
assertEquals(cnt, table.getRecordCount());
assertTrue(!dbh.canUndo());
assertTrue(!dbh.canRedo());
dbh.endTransaction(txId, true);
assertTrue(dbh.canUndo());
assertTrue(!dbh.canRedo());
assertTrue(dbh.undo());
assertTrue(dbh.canUndo());
assertTrue(dbh.canRedo());
assertTrue(dbh.redo());
assertTrue(dbh.canUndo());
assertTrue(!dbh.canRedo());
RecordIterator iter = table.iterator();
int recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(cnt, recIx);
// Add records
Record[] newRecs = new Record[recs.length + 100];
System.arraycopy(recs, 0, newRecs, 0, recs.length);
txId = dbh.startTransaction();
for (int i = 0; i < 100; i++) {
try {
newRecs[i + recs.length] =
DBTestUtils.createFixedKeyRecord(table, BUFFER_SIZE, true);
}
catch (DuplicateKeyException e) {
Assert.fail("Duplicate key error");
}
}
dbh.endTransaction(txId, true);
recs = newRecs;
Arrays.sort(recs);
assertTrue(dbh.undo());
assertTrue(dbh.redo());
iter = table.iterator();
recIx = 0;
while (iter.hasNext()) {
Record rec = iter.next();
assertEquals(recs[recIx++], rec);
}
assertEquals(recs.length, recIx);
}
}