From 7deeeb24be83f847a22267b1ca1e7a3db4f17859 Mon Sep 17 00:00:00 2001 From: Alex Gaetano Padula Date: Thu, 12 Feb 2026 13:50:02 -0500 Subject: [PATCH] checkpoint api feature support to align with tdb v8.3.2+ --- pom.xml | 2 +- src/main/c/com_tidesdb_TidesDB.c | 21 ++++++ src/main/java/com/tidesdb/TidesDB.java | 17 +++++ src/test/java/com/tidesdb/TidesDBTest.java | 76 +++++++++++++++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index e4b3640..7aa58ba 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.tidesdb tidesdb-java - 0.6.0 + 0.6.1 jar TidesDB Java diff --git a/src/main/c/com_tidesdb_TidesDB.c b/src/main/c/com_tidesdb_TidesDB.c index 7072b56..cef7a40 100644 --- a/src/main/c/com_tidesdb_TidesDB.c +++ b/src/main/c/com_tidesdb_TidesDB.c @@ -366,6 +366,27 @@ JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeBackup(JNIEnv *env, jclass } } +JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeCheckpoint(JNIEnv *env, jclass cls, + jlong handle, jstring dir) +{ + tidesdb_t *db = (tidesdb_t *)(uintptr_t)handle; + const char *checkpointDir = (*env)->GetStringUTFChars(env, dir, NULL); + if (checkpointDir == NULL) + { + throwTidesDBException(env, TDB_ERR_MEMORY, "Failed to get checkpoint directory"); + return; + } + + int result = tidesdb_checkpoint(db, checkpointDir); + + (*env)->ReleaseStringUTFChars(env, dir, checkpointDir); + + if (result != TDB_SUCCESS) + { + throwTidesDBException(env, result, getErrorMessage(result)); + } +} + JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeRenameColumnFamily(JNIEnv *env, jclass cls, jlong handle, jstring oldName, diff --git a/src/main/java/com/tidesdb/TidesDB.java b/src/main/java/com/tidesdb/TidesDB.java index 4281140..5c0b7ed 100644 --- a/src/main/java/com/tidesdb/TidesDB.java +++ b/src/main/java/com/tidesdb/TidesDB.java @@ -228,6 +228,21 @@ public void backup(String dir) throws TidesDBException { nativeBackup(nativeHandle, dir); } + /** + * Creates a lightweight, near-instant snapshot of an open database using hard links + * instead of copying SSTable data. + * + * @param dir the checkpoint directory (must be non-existent or empty) + * @throws TidesDBException if the checkpoint fails + */ + public void checkpoint(String dir) throws TidesDBException { + checkNotClosed(); + if (dir == null || dir.isEmpty()) { + throw new IllegalArgumentException("Checkpoint directory cannot be null or empty"); + } + nativeCheckpoint(nativeHandle, dir); + } + /** * Atomically renames a column family and its underlying directory. * The operation waits for any in-progress flush or compaction to complete before renaming. @@ -307,6 +322,8 @@ private static native void nativeCreateColumnFamily(long handle, String name, private static native void nativeBackup(long handle, String dir) throws TidesDBException; + private static native void nativeCheckpoint(long handle, String dir) throws TidesDBException; + private static native void nativeRenameColumnFamily(long handle, String oldName, String newName) throws TidesDBException; private static native void nativeCloneColumnFamily(long handle, String sourceName, String destName) throws TidesDBException; diff --git a/src/test/java/com/tidesdb/TidesDBTest.java b/src/test/java/com/tidesdb/TidesDBTest.java index 5af56de..8606c62 100644 --- a/src/test/java/com/tidesdb/TidesDBTest.java +++ b/src/test/java/com/tidesdb/TidesDBTest.java @@ -614,6 +614,76 @@ void testCloneColumnFamily() throws TidesDBException { @Test @Order(16) + void testCheckpoint() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb16").toString()) + .numFlushThreads(2) + .numCompactionThreads(2) + .logLevel(LogLevel.INFO) + .blockCacheSize(64 * 1024 * 1024) + .maxOpenSSTables(256) + .build(); + + try (TidesDB db = TidesDB.open(config)) { + ColumnFamilyConfig cfConfig = ColumnFamilyConfig.defaultConfig(); + db.createColumnFamily("test_cf", cfConfig); + + ColumnFamily cf = db.getColumnFamily("test_cf"); + + // Insert some data + try (Transaction txn = db.beginTransaction()) { + for (int i = 0; i < 10; i++) { + txn.put(cf, ("key" + i).getBytes(), ("value" + i).getBytes()); + } + txn.commit(); + } + + // Create checkpoint + String checkpointDir = tempDir.resolve("testdb16_checkpoint").toString(); + db.checkpoint(checkpointDir); + + // Open the checkpoint as a separate database and verify data + Config checkpointConfig = Config.builder(checkpointDir) + .numFlushThreads(2) + .numCompactionThreads(2) + .logLevel(LogLevel.INFO) + .blockCacheSize(64 * 1024 * 1024) + .maxOpenSSTables(256) + .build(); + + try (TidesDB checkpointDb = TidesDB.open(checkpointConfig)) { + ColumnFamily checkpointCf = checkpointDb.getColumnFamily("test_cf"); + assertNotNull(checkpointCf); + + try (Transaction txn = checkpointDb.beginTransaction()) { + for (int i = 0; i < 10; i++) { + byte[] result = txn.get(checkpointCf, ("key" + i).getBytes()); + assertNotNull(result); + assertArrayEquals(("value" + i).getBytes(), result); + } + } + } + } + } + + @Test + @Order(17) + void testCheckpointNullDir() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb16b").toString()) + .numFlushThreads(2) + .numCompactionThreads(2) + .logLevel(LogLevel.INFO) + .blockCacheSize(64 * 1024 * 1024) + .maxOpenSSTables(256) + .build(); + + try (TidesDB db = TidesDB.open(config)) { + assertThrows(IllegalArgumentException.class, () -> db.checkpoint(null)); + assertThrows(IllegalArgumentException.class, () -> db.checkpoint("")); + } + } + + @Test + @Order(18) void testTransactionPutGetDeleteBadKey() throws TidesDBException { Config config = Config.builder(tempDir.resolve("testdb3").toString()) .numFlushThreads(2) @@ -656,7 +726,7 @@ void testTransactionPutGetDeleteBadKey() throws TidesDBException { } @Test - @Order(17) + @Order(19) void testTransactionReset() throws TidesDBException { Config config = Config.builder(tempDir.resolve("testdb17").toString()) .numFlushThreads(2) @@ -701,7 +771,7 @@ void testTransactionReset() throws TidesDBException { } @Test - @Order(18) + @Order(20) void testTransactionResetWithDifferentIsolation() throws TidesDBException { Config config = Config.builder(tempDir.resolve("testdb18").toString()) .numFlushThreads(2) @@ -746,7 +816,7 @@ void testTransactionResetWithDifferentIsolation() throws TidesDBException { } @Test - @Order(19) + @Order(21) void testTransactionResetNullIsolation() throws TidesDBException { Config config = Config.builder(tempDir.resolve("testdb19").toString()) .numFlushThreads(2)