ghidra/Ghidra/Framework/DB/src/test/java/db/DBTest.java
2020-12-28 13:34:09 -05:00

551 lines
15 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 ghidra.util.exception.DuplicateFileException;
import utilities.util.FileUtilities;
/**
* Test the creation of a new database with indexed and non-indexed tables.
* Tests include the removal of tables and are performed for both a stored and
* non-stored database.
*/
public class DBTest extends AbstractGenericTest {
private static final int BUFFER_SIZE = 256;
private static final int CACHE_SIZE = 4 * 1024 * 1024;
private File testDir;
private static final String dbName = "test";
private static final String dbName2 = "test2";
private BufferFileManager fileMgr;
private DBHandle dbh;
private BufferFile bfile;
@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 void saveAndReopen() throws IOException {
assertTrue(dbh.canUpdate());
long dbId = dbh.getDatabaseId();
try {
dbh.save(null, null, null);
}
catch (CancelledException e) {
Assert.fail("Should not happen");
}
dbh.close();
bfile = new LocalManagedBufferFile(fileMgr, true, -1, -1);
dbh = new DBHandle(bfile);
assertEquals(dbId, dbh.getDatabaseId());
}
@Test
public void testCreateDatabase() {
assertTrue(!dbh.canUpdate());
assertTrue(!dbh.canRedo());
assertTrue(!dbh.canUndo());
try {
dbh.checkTransaction();
Assert.fail();
}
catch (NoTransactionException e) {
// expected
}
assertEquals(dbh.getTableCount(), 0);
}
@Test
public void testCreateExistingDatabase() throws IOException {
saveAsAndReopen(dbName);
try {
saveAsAndReopen(dbName);
Assert.fail();
}
catch (DuplicateFileException e) {
// good; expected
}
}
@Test
public void testCreateDatabaseCopies() throws IOException {
long dbId = dbh.getDatabaseId();
saveAsAndReopen(dbName);
assertTrue(dbId == dbh.getDatabaseId());// First SaveAs should preserve ID
try {
saveAsAndReopen(dbName2);
assertTrue(dbId != dbh.getDatabaseId());// Second SaveAs should change ID to avoid duplication
}
catch (DuplicateFileException e) {
Assert.fail();
}
}
@Test
public void testOpenForReadOnlyDuringUpdate() throws IOException {
saveAsAndReopen(dbName);
BufferFile bf2 = new LocalManagedBufferFile(fileMgr, false, -1, -1);
DBHandle dbh2 = null;
try {
dbh2 = new DBHandle(bf2);
createNonIndexedTables(false, DBTestUtils.MAX_SCHEMA_TYPE + 1);
saveAndReopen();
assertEquals(DBTestUtils.MAX_SCHEMA_TYPE + 1, dbh.getTableCount());
assertEquals(0, dbh2.getTableCount());
}
finally {
if (dbh2 != null) {
dbh2.close();
}
bf2.close();
}
}
@Test
public void testCreateLongKeyTable() throws IOException {
long txId = dbh.startTransaction();
Table table =
DBTestUtils.createLongKeyTable(dbh, "TABLE1", DBTestUtils.ALL_TYPES, false, false);
dbh.endTransaction(txId, true);
String[] names = table.getSchema().getFieldNames();
assertTrue(Arrays.equals(DBTestUtils.getFieldNames(DBTestUtils.ALL_TYPES), names));
}
@Test
public void testCreateVarKeyTable() throws IOException {
long txId = dbh.startTransaction();
Table table = DBTestUtils.createBinaryKeyTable(dbh, "TABLE1", DBTestUtils.ALL_TYPES, false);
dbh.endTransaction(txId, true);
String[] names = table.getSchema().getFieldNames();
assertTrue(Arrays.equals(DBTestUtils.getFieldNames(DBTestUtils.ALL_TYPES), names));
}
@Test
public void testStoredCreateLongKeyTable() throws IOException {
long txId = dbh.startTransaction();
DBTestUtils.createLongKeyTable(dbh, "TABLE1", DBTestUtils.ALL_TYPES, false, false);
dbh.endTransaction(txId, true);
saveAsAndReopen(dbName);
Table table = dbh.getTable("TABLE1");
assertTrue(table.useLongKeys());
String[] names = table.getSchema().getFieldNames();
assertTrue(Arrays.equals(DBTestUtils.getFieldNames(DBTestUtils.ALL_TYPES), names));
}
@Test
public void testStoredCreateVarKeyTable() throws IOException {
long txId = dbh.startTransaction();
DBTestUtils.createBinaryKeyTable(dbh, "TABLE1", DBTestUtils.ALL_TYPES, false);
dbh.endTransaction(txId, true);
saveAsAndReopen(dbName);
Table table = dbh.getTable("TABLE1");
assertTrue(!table.useLongKeys());
assertTrue(table.getSchema().getKeyFieldType().getClass().equals(BinaryField.class));
String[] names = table.getSchema().getFieldNames();
assertTrue(Arrays.equals(DBTestUtils.getFieldNames(DBTestUtils.ALL_TYPES), names));
}
private void createNonIndexedTables(boolean testStoredDB, int cnt) throws IOException {
// Create table for each schema defined by DBTestUtils
long txId = dbh.startTransaction();
for (int i = 0; i < cnt; i++) {
DBTestUtils.createLongKeyTable(dbh, "TABLE" + i, i % (DBTestUtils.MAX_SCHEMA_TYPE + 1),
false, false);
}
dbh.endTransaction(txId, true);
assertEquals(cnt, dbh.getTableCount());
if (testStoredDB) {
saveAsAndReopen(dbName);
assertEquals(cnt, dbh.getTableCount());
}
// Check Master Table entries
TableRecord[] tableRecords = dbh.getMasterTable().getTableRecords();
for (int i = 0; i < tableRecords.length; i++) {
String name = "TABLE" + i;
assertEquals(name, tableRecords[i].getName());
assertEquals(-1, tableRecords[i].getIndexedColumn());
assertEquals(Long.MIN_VALUE, tableRecords[i].getMaxKey());
assertEquals(0, tableRecords[i].getRecordCount());
assertEquals(-1, tableRecords[i].getRootBufferId());
}
}
@Test
public void testNonIndexedTables() throws IOException {
createNonIndexedTables(false, DBTestUtils.MAX_SCHEMA_TYPE + 1);
}
@Test
public void testStoredNonIndexedTables() throws IOException {
createNonIndexedTables(true, DBTestUtils.MAX_SCHEMA_TYPE + 1);
}
private void createIndexedTables(boolean testStoredDB) throws IOException {
// Create table for each schema defined by DBTestUtils
// All schema fields are indexed
long txId = dbh.startTransaction();
for (int i = 0; i <= DBTestUtils.MAX_SCHEMA_TYPE; i++) {
DBTestUtils.createLongKeyTable(dbh, "TABLE" + i, i, true, false);
}
assertEquals(DBTestUtils.MAX_SCHEMA_TYPE + 1, dbh.getTableCount());
dbh.endTransaction(txId, true);
if (testStoredDB) {
saveAsAndReopen(dbName);
assertEquals(DBTestUtils.MAX_SCHEMA_TYPE + 1, dbh.getTableCount());
}
// Check Master Table entries
TableRecord[] tableRecords = dbh.getMasterTable().getTableRecords();
Table lastTable = null;
int tableCnt = 0;
int indexCnt = 0;
for (TableRecord tableRecord : tableRecords) {
if (tableRecord.getIndexedColumn() < 0) {
if (tableCnt > 0) {
assertEquals(DBTestUtils.getIndexedColumnCount(tableCnt - 1), indexCnt);
}
String name = "TABLE" + tableCnt;
lastTable = dbh.getTable(name);
assertEquals(name, tableRecord.getName());
assertEquals(Long.MIN_VALUE, tableRecord.getMaxKey());
assertEquals(0, tableRecord.getRecordCount());
assertEquals(-1, tableRecord.getRootBufferId());
++tableCnt;
indexCnt = 0;
}
else {
if (lastTable == null) {
Assert.fail();
}
int[] indexedColumns = DBTestUtils.getIndexedColumns(tableCnt - 1);
assertTrue(indexCnt < indexedColumns.length);
assertEquals(indexedColumns[indexCnt], tableRecord.getIndexedColumn());
assertEquals(lastTable.getName(), tableRecord.getName());
assertEquals(Long.MIN_VALUE, tableRecord.getMaxKey());
assertEquals(0, tableRecord.getRecordCount());
assertEquals(-1, tableRecord.getRootBufferId());
++indexCnt;
}
}
assertEquals(DBTestUtils.getIndexedColumnCount(tableCnt - 1), indexCnt);
assertEquals(DBTestUtils.MAX_SCHEMA_TYPE + 1, tableCnt);
}
@Test
public void testIndexedTables() throws IOException {
createIndexedTables(false);
}
@Test
public void testStoredIndexedTables() throws IOException {
createIndexedTables(true);
}
private void removeNonIndexedTables(boolean testStoredDB) throws IOException {
// Create table for each schema defined by DBTestUtils
createNonIndexedTables(testStoredDB, DBTestUtils.MAX_SCHEMA_TYPE + 1);
// Delete odd numbered tables
long txId = dbh.startTransaction();
int totalTableCnt = 0;
for (int i = 0; i <= DBTestUtils.MAX_SCHEMA_TYPE; i++) {
if ((i % 2) == 1) {
dbh.deleteTable("TABLE" + i);
}
else {
++totalTableCnt;
}
}
dbh.endTransaction(txId, true);
assertEquals(totalTableCnt, dbh.getTableCount());
if (testStoredDB) {
saveAndReopen();
assertEquals(totalTableCnt, dbh.getTableCount());
}
// Check Master Table entries
TableRecord[] tableRecords = dbh.getMasterTable().getTableRecords();
for (int i = 0; i < tableRecords.length; i++) {
String name = "TABLE" + (2 * i);
assertEquals(name, tableRecords[i].getName());
assertEquals(-1, tableRecords[i].getIndexedColumn());
assertEquals(Long.MIN_VALUE, tableRecords[i].getMaxKey());
assertEquals(0, tableRecords[i].getRecordCount());
assertEquals(-1, tableRecords[i].getRootBufferId());
}
assertEquals(totalTableCnt, tableRecords.length);
}
@Test
public void testRemoveNonIndexedTables() throws IOException {
removeNonIndexedTables(false);
}
@Test
public void testStoredRemoveNonIndexedTables() throws IOException {
removeNonIndexedTables(true);
}
private void removeIndexedTables(boolean testStoredDB) throws IOException {
// Create table for each schema defined by DBTestUtils
// All schema fields are indexed
createIndexedTables(testStoredDB);
// Delete odd numbered tables
long txId = dbh.startTransaction();
int totalTableCnt = 0;
for (int i = 0; i <= DBTestUtils.MAX_SCHEMA_TYPE; i++) {
if ((i % 2) == 1) {
dbh.deleteTable("TABLE" + i);
}
else {
++totalTableCnt;
}
}
dbh.endTransaction(txId, true);
assertEquals(totalTableCnt, dbh.getTableCount());
if (testStoredDB) {
saveAndReopen();
assertEquals(totalTableCnt, dbh.getTableCount());
}
// Check Master Table entries
TableRecord[] tableRecords = dbh.getMasterTable().getTableRecords();
Table lastTable = null;
int tableCnt = 0;
int indexCnt = 0;
for (TableRecord tableRecord : tableRecords) {
if (tableRecord.getIndexedColumn() < 0) {
if (tableCnt > 0) {
assertEquals(DBTestUtils.getIndexedColumnCount(2 * (tableCnt - 1)), indexCnt);
}
String name = "TABLE" + (2 * tableCnt);
lastTable = dbh.getTable(name);
assertEquals(name, tableRecord.getName());
assertEquals(Long.MIN_VALUE, tableRecord.getMaxKey());
assertEquals(0, tableRecord.getRecordCount());
assertEquals(-1, tableRecord.getRootBufferId());
++tableCnt;
indexCnt = 0;
}
else {
if (lastTable == null) {
Assert.fail();
}
int[] indexedColumns = DBTestUtils.getIndexedColumns(2 * (tableCnt - 1));
assertTrue(indexCnt < indexedColumns.length);
assertEquals(indexedColumns[indexCnt], tableRecord.getIndexedColumn());
assertEquals(lastTable.getName(), tableRecord.getName());
assertEquals(Long.MIN_VALUE, tableRecord.getMaxKey());
assertEquals(0, tableRecord.getRecordCount());
assertEquals(-1, tableRecord.getRootBufferId());
++indexCnt;
}
}
Schema schema = lastTable.getSchema();
assertEquals(schema.getFields().length - 2, indexCnt); // ByteField and BooleanField do not support indexing
assertEquals(totalTableCnt, tableCnt);
}
@Test
public void testRemoveIndexedTables() throws IOException {
removeIndexedTables(false);
}
@Test
public void testStoredRemoveIndexedTables() throws IOException {
removeIndexedTables(true);
}
@Test
public void testLargeMasterTable() throws IOException {
createNonIndexedTables(false, 1000);
}
@Test
public void testStoredLargeMasterTable() throws IOException {
createNonIndexedTables(true, 1000);
}
@Test
public void testMasterTableUndo() throws IOException {
createIndexedTables(false);
Table[] tables = dbh.getTables();
String table1Name = tables[1].getName();
// delete a table - and rollback
long txId = dbh.startTransaction();
for (Table table : tables) {
dbh.deleteTable(table.getName());
}
dbh.endTransaction(txId, false);
assertTrue(dbh.getBufferMgr().atCheckpoint());
try {
tables[1].getName();
Assert.fail();
}
catch (Exception e) {
// Ignore
}
Schema s = tables[1].getSchema();
DBRecord rec = s.createRecord(1);
txId = dbh.startTransaction();
try {
tables[1].putRecord(rec);
Assert.fail();
}
catch (Exception e) {
// Ignore
}
assertTrue(dbh.getBufferMgr().atCheckpoint());
Table t = dbh.getTable(table1Name);
assertEquals(table1Name, t.getName());
t.putRecord(rec);
assertTrue(!dbh.getBufferMgr().atCheckpoint());
dbh.endTransaction(txId, true);
assertEquals(rec, t.getRecord(1));
// delete a table - and commit
txId = dbh.startTransaction();
dbh.deleteTable(table1Name);
dbh.endTransaction(txId, true);
assertTrue(dbh.undo());
t = dbh.getTable(table1Name);
assertEquals(table1Name, t.getName());
assertTrue(dbh.redo());
t = dbh.getTable(table1Name);
assertNull(t);
}
@Test
public void testTableWithIndexRecreateUndo() throws IOException {
createIndexedTables(false);
Table[] tables = dbh.getTables();
String table1Name = tables[1].getName();
Table t1 = dbh.getTable(table1Name);
int[] indexedColumns = t1.getIndexedColumns();
assertEquals(t1.getSchema().getFieldCount(), indexedColumns.length);
for (int i = 0; i < indexedColumns.length; i++) {
assertEquals(i, indexedColumns[i]);
}
Schema schema = t1.getSchema();
long txId = dbh.startTransaction();
try {
t1.putRecord(schema.createRecord(1));
}
finally {
dbh.endTransaction(txId, true);
}
txId = dbh.startTransaction();
try {
dbh.deleteTable(table1Name);
dbh.createTable(table1Name, schema, indexedColumns);
}
finally {
dbh.endTransaction(txId, true);
}
dbh.undo();
Table t1prime = dbh.getTable(table1Name);
indexedColumns = t1prime.getIndexedColumns();
assertEquals(t1prime.getSchema().getFieldCount(), indexedColumns.length);
for (int i = 0; i < indexedColumns.length; i++) {
assertEquals(i, indexedColumns[i]);
}
assertNotNull(t1prime.getRecord(1));
}
}