package com.github.geophile.erdo;
import com.github.geophile.erdo.apiimpl.DatabaseImpl;
import com.github.geophile.erdo.apiimpl.DatabaseOnDisk;
import com.github.geophile.erdo.util.FileUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
// Tests handling of deleted records, through consolidations that must preserve them, and consolidations that must
// drop them.
public class DeletedRecordTest
{
@BeforeClass
public static void beforeClass() throws IOException
{
DB_DIRECTORY = new File(FileUtil.tempDirectory(), DB_NAME);
}
@Before
public void before()
{
FileUtil.deleteDirectory(DB_DIRECTORY);
}
@After
public void after()
{
((TestFactory)db.factory()).reset();
}
@Test
public void noPersistentDeletedRecords()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
// Turn off background consolidation
Configuration configuration = Configuration.defaultConfiguration();
configuration.consolidationMinMapsToConsolidate(Integer.MAX_VALUE);
configuration.consolidationMinSizeBytes(Integer.MAX_VALUE);
configuration.consolidationThreads(0);
db = DatabaseOnDisk.createDatabase(DB_DIRECTORY, configuration, TestFactory.class);
map = db.createMap("test", RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
final int N = 10;
{
// Transaction 0: Load a private map with keys 0..N-1 and delete N/2
for (int key = 0; key < N; key++) {
insert(key, "a");
}
delete(N/2);
db.commitTransaction();
}
{
// Transaction 1: Load a private map with keys N..2N-1, reinsert N/2, and delete N + N/2
for (int key = N; key < 2 * N; key++) {
insert(key, "b");
}
insert(N / 2, "c");
delete(N + N/2);
db.commitTransaction();
}
{
// Force consolidation
((DatabaseOnDisk)db).consolidateAll();
}
checkContents(2 * N);
db.close();
}
@Test
public void testWithPersistentDeletedRecords()
throws IOException, InterruptedException, DeadlockException, TransactionRolledBackException
{
FileUtil.deleteDirectory(DB_DIRECTORY);
// Turn off background consolidation
Configuration configuration = Configuration.defaultConfiguration();
configuration.consolidationMinMapsToConsolidate(2);
configuration.consolidationMinSizeBytes(10);
configuration.consolidationThreads(1);
db = DatabaseOnDisk.createDatabase(DB_DIRECTORY, configuration, TestFactory.class);
map = db.createMap("test", RecordFactory.simpleRecordFactory(TestKey.class, TestRecord.class));
final int MAX_KEY = 20;
final int OPERATIONS = 1000;
final int OPERATIONS_PER_TXN = 10;
final double DELETE_PROBABILITY = 0.3;
final Random RANDOM = new Random();
String transaction = null;
for (int i = 0; i < OPERATIONS; i++) {
if (i % OPERATIONS_PER_TXN == 0) {
db.commitTransaction();
transaction = String.format("txn(%s)", i / OPERATIONS_PER_TXN);
}
int key = RANDOM.nextInt(MAX_KEY);
if (RANDOM.nextDouble() < DELETE_PROBABILITY) {
delete(key);
} else {
insert(key, transaction);
}
}
db.commitTransaction();
checkContents(MAX_KEY);
db.close();
assertTrue(db.factory().testObserver().writeDeletedKeyCount() > 0);
assertTrue(db.factory().testObserver().readDeletedKeyCount() > 0);
}
private void insert(int key, String value)
throws InterruptedException, DeadlockException, TransactionRolledBackException, IOException
{
map.put(TestRecord.createRecord(key, value));
expected.put(key, value);
}
private void delete(int key)
throws InterruptedException, DeadlockException, TransactionRolledBackException, IOException
{
map.delete(new TestKey(key));
expected.put(key, null);
}
private void checkContents(int n) throws IOException, InterruptedException
{
Cursor cursor = map.first();
TestRecord record;
for (int key = 0; key < n; key++) {
String expectedValue = expected.get(key);
if (expectedValue != null) {
record = (TestRecord) cursor.next();
assertEquals(key, record.key().key());
assertEquals(expectedValue, record.stringValue());
}
}
}
private static final String DB_NAME = "erdo";
private static File DB_DIRECTORY;
private DatabaseImpl db;
private OrderedMap map;
private Map<Integer, String> expected = new HashMap<>();
}