package org.mapdb; import org.junit.Test; import org.mapdb.volume.Volume; import java.io.File; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; public class WriteAheadLogTest { @Test public void null_record() { testRecord(11111L, null); } @Test public void zero_record() { testRecord(11111L, new byte[0]); } @Test public void ten_record() { testRecord(11111L, TT.randomByteArray(10,0)); } @Test public void large_record() { testRecord(11111L, TT.randomByteArray(1000000,0)); } void testRecord(final long recid, final byte[] data) { WriteAheadLog wal = new WriteAheadLog(null); wal.open(WriteAheadLog.NOREPLAY); wal.startNextFile(); final AtomicBoolean called = new AtomicBoolean(); final long pointer = wal.walPutRecord(recid, data, 0, data == null ? 0 : data.length); for (int i = 0; i < 1; i++) { byte[] val = wal.walGetRecord(pointer, recid); if (data == null) assertNull(val); else assertTrue(Arrays.equals(data, val)); wal.seal(); } WriteAheadLog.WALReplay r = new WriteAheadLog.WALReplay() { @Override public void beforeReplayStart() { } @Override public void afterReplayFinished() { } @Override public void writeLong(long offset, long value) { fail(); } @Override public void writeRecord(long recid2, long walId, Volume vol, long volOffset, int length) { assertFalse(called.getAndSet(true)); assertEquals(recid, recid2); if (data == null) { assertNull(vol); assertEquals(walId, 0); assertEquals(volOffset, 0); assertEquals(length, 0); } else { byte[] data = new byte[length]; vol.getData(volOffset, data, 0, data.length); assertTrue(Arrays.equals(data, data)); assertEquals(pointer, walId); } } @Override public void writeByteArray(long offset2, long walId, Volume vol, long volOffset, int length) { fail(); } @Override public void commit() { fail(); } @Override public void rollback() { fail(); } @Override public void writeTombstone(long recid) { fail(); } @Override public void writePreallocate(long recid) { fail(); } }; wal.replayWAL(r); assertTrue(called.get()); } @Test public void tombstone() { WriteAheadLog wal = new WriteAheadLog(null); wal.open(WriteAheadLog.NOREPLAY); wal.startNextFile(); wal.walPutTombstone(111111L); wal.seal(); final AtomicInteger c = new AtomicInteger(); wal.replayWAL(new WriteAheadLog.WALReplay() { @Override public void beforeReplayStart() { } @Override public void afterReplayFinished() { } @Override public void writeLong(long offset, long value) { fail(); } @Override public void writeRecord(long recid, long walId, Volume vol, long volOffset, int length) { fail(); } @Override public void writeByteArray(long offset, long walId, Volume vol, long volOffset, int length) { fail(); } @Override public void commit() { fail(); } @Override public void rollback() { fail(); } @Override public void writeTombstone(long recid) { c.incrementAndGet(); assertEquals(111111L, recid); } @Override public void writePreallocate(long recid) { fail(); } }); assertEquals(1, c.get()); } @Test public void preallocate() { WriteAheadLog wal = new WriteAheadLog(null); wal.open(WriteAheadLog.NOREPLAY); wal.startNextFile(); wal.walPutPreallocate(111111L); wal.seal(); final AtomicInteger c = new AtomicInteger(); wal.replayWAL(new WriteAheadLog.WALReplay() { @Override public void beforeReplayStart() { } @Override public void afterReplayFinished() { } @Override public void writeLong(long offset, long value) { fail(); } @Override public void writeRecord(long recid, long walId, Volume vol, long volOffset, int length) { fail(); } @Override public void writeByteArray(long offset, long walId, Volume vol, long volOffset, int length) { fail(); } @Override public void commit() { fail(); } @Override public void rollback() { fail(); } @Override public void writeTombstone(long recid) { fail(); } @Override public void writePreallocate(long recid) { c.incrementAndGet(); assertEquals(111111L, recid); } }); assertEquals(1, c.get()); } @Test public void commit() { WriteAheadLog wal = new WriteAheadLog(null); wal.open(WriteAheadLog.NOREPLAY); wal.walPutLong(111L, 1111L); wal.commit(); wal.seal(); wal.replayWAL(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeLong, 111L, 1111L}, new Object[]{WALSequence.commit} )); } @Test public void rollback() { WriteAheadLog wal = new WriteAheadLog(null); wal.open(WriteAheadLog.NOREPLAY); wal.startNextFile(); wal.walPutLong(111L, 1000); wal.rollback(); wal.seal(); wal.replayWAL(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeLong, 111L, 1000L}, new Object[]{WALSequence.rollback} )); } @Test public void commitChecksum() { WriteAheadLog wal = new WriteAheadLog(null); wal.open(WriteAheadLog.NOREPLAY); wal.startNextFile(); wal.walPutLong(111L, 1000); wal.commit(); long offset1 = wal.fileOffset - 5; int checksum1 = DataIO.longHash(wal.curVol.hash(16, offset1 - 16, 111L)); assertEquals(checksum1, wal.curVol.getInt(offset1 + 1)); wal.walPutLong(111L, 1000); wal.commit(); long offset2 = wal.fileOffset - 5; int checksum2 = checksum1 + DataIO.longHash(wal.curVol.hash(offset1 + 5, offset2 - offset1 - 5, 111L)); assertEquals(checksum2, wal.curVol.getInt(offset2 + 1)); } @Test public void test_sequence() { WALSequence s = new WALSequence( new Object[]{WALSequence.commit}, new Object[]{WALSequence.rollback} ); s.commit(); s.rollback(); assertTrue(s.seq.isEmpty()); } //******************************************* @Test public void lazy_file_create() { File f = TT.tempFile(); f.delete(); File f2 = new File(f.getPath() + ".wal.0"); WriteAheadLog wal = new WriteAheadLog(f.getPath()); wal.open(WriteAheadLog.NOREPLAY); assertTrue(!f2.exists()); wal.walPutLong(111L, 111L); assertTrue(f2.exists()); wal.close(); f2.delete(); } @Test public void overflow_byte_array() { File f = TT.tempFile(); f.delete(); File f0 = new File(f.getPath() + ".wal.0"); File f1 = new File(f.getPath() + ".wal.1"); WriteAheadLog wal = new WriteAheadLog(f.getPath()); wal.open(WriteAheadLog.NOREPLAY); long lastPos = 0; while (!f1.exists()) { lastPos = wal.fileOffset; wal.walPutByteArray(111L, new byte[100], 0, 100); assertTrue(f0.exists()); } assertTrue(WriteAheadLog.MAX_FILE_SIZE - 1000 < lastPos); assertTrue(WriteAheadLog.MAX_FILE_SIZE + 120 > lastPos); wal.destroyWalFiles(); } @Test public void overflow_record() { File f = TT.tempFile(); f.delete(); File f0 = new File(f.getPath() + ".wal.0"); File f1 = new File(f.getPath() + ".wal.1"); WriteAheadLog wal = new WriteAheadLog(f.getPath()); wal.open(WriteAheadLog.NOREPLAY); long lastPos = 0; while (!f1.exists()) { lastPos = wal.fileOffset; wal.walPutRecord(111L, new byte[100], 0, 100); assertTrue(f0.exists()); } assertTrue(WriteAheadLog.MAX_FILE_SIZE - 1000 < lastPos); assertTrue(WriteAheadLog.MAX_FILE_SIZE + 120 > lastPos); wal.destroyWalFiles(); } @Test public void open_ignores_rollback() { File f = TT.tempFile(); WriteAheadLog wal = new WriteAheadLog(f.getPath()); wal.walPutLong(1L, 11L); wal.commit(); wal.walPutLong(2L, 33L); wal.rollback(); wal.walPutLong(3L, 33L); wal.commit(); wal.seal(); wal.close(); wal = new WriteAheadLog(f.getPath()); wal.open(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeLong, 1L, 11L}, new Object[]{WALSequence.commit}, // 2L is ignored, rollback section is skipped on hard replay new Object[]{WALSequence.writeLong, 3L, 33L}, new Object[]{WALSequence.commit} )); wal.destroyWalFiles(); wal.close(); f.delete(); } @Test public void skip_rollback() { WriteAheadLog wal = new WriteAheadLog(null); wal.walPutLong(1L, 11L); wal.commit(); long o1 = wal.fileOffset; wal.walPutLong(2L, 33L); wal.rollback(); long o2 = wal.fileOffset; wal.walPutLong(3L, 33L); wal.commit(); long o3 = wal.fileOffset; wal.seal(); assertEquals(o2, wal.skipRollbacks(o1)); assertEquals(o2, wal.skipRollbacks(o2)); assertEquals(0, wal.skipRollbacks(o3)); } @Test public void skip_rollback_last_rollback() { WriteAheadLog wal = new WriteAheadLog(null); wal.walPutLong(1L, 11L); wal.commit(); long o1 = wal.fileOffset; wal.walPutLong(2L, 33L); wal.commit(); long o2 = wal.fileOffset; wal.walPutLong(3L, 33L); wal.rollback(); wal.seal(); assertEquals(o1, wal.skipRollbacks(o1)); assertEquals(0, wal.skipRollbacks(o2)); } @Test public void cut_broken_end() { String f = TT.tempFile().getPath(); WriteAheadLog wal = new WriteAheadLog(f); wal.walPutLong(1L, 11L); wal.commit(); wal.walPutLong(2L, 22L); wal.rollback(); wal.walPutLong(3L, 33L); wal.commit(); wal.walPutLong(4L, 44L); wal.curVol.sync(); wal.close(); wal = new WriteAheadLog(f); wal.open(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeLong, 1L, 11L}, new Object[]{WALSequence.commit}, new Object[]{WALSequence.writeLong, 3L, 33L}, new Object[]{WALSequence.commit} )); } @Test public void cut_broken_end_rollback() { String f = TT.tempFile().getPath(); WriteAheadLog wal = new WriteAheadLog(f); wal.walPutLong(1L, 11L); wal.commit(); wal.walPutLong(2L, 22L); wal.commit(); wal.walPutLong(3L, 33L); wal.rollback(); wal.walPutLong(4L, 44L); wal.curVol.sync(); wal.close(); wal = new WriteAheadLog(f); wal.open(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeLong, 1L, 11L}, new Object[]{WALSequence.commit}, new Object[]{WALSequence.writeLong, 2L, 22L}, new Object[]{WALSequence.commit} )); } @Test public void replay_commit_over_file_edge(){ String f = TT.tempFile().getPath(); WriteAheadLog wal = new WriteAheadLog(f); byte[] b = TT.randomByteArray(20 * 1024 * 1024,0); wal.walPutRecord(11L, b, 0, b.length); wal.walPutRecord(33L, b, 0, b.length); wal.commit(); wal.close(); wal = new WriteAheadLog(f); wal.open(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeRecord, 11L, 16L, b}, new Object[]{WALSequence.writeRecord, 33L, 4294967312L, b}, new Object[]{WALSequence.commit} )); } @Test public void empty_commit(){ String f = TT.tempFile().getPath(); WriteAheadLog wal = new WriteAheadLog(f); byte[] b = TT.randomByteArray(1024,0); wal.walPutRecord(33L, b, 0, b.length); wal.commit(); wal.commit(); wal.seal(); wal.close(); wal = new WriteAheadLog(f); wal.open(new WALSequence( new Object[]{WALSequence.beforeReplayStart}, new Object[]{WALSequence.writeRecord, 33L, 16L, b}, new Object[]{WALSequence.commit}, new Object[]{WALSequence.commit} )); } }