From 6dae41714e360194e68270c6c6bca2661ec349a1 Mon Sep 17 00:00:00 2001 From: Alex Gaetano Padula Date: Wed, 11 Feb 2026 11:00:23 -0500 Subject: [PATCH] extend api with transaction reset capabilities, bump minor, extend tests --- pom.xml | 2 +- src/main/c/com_tidesdb_TidesDB.c | 12 +++ src/main/java/com/tidesdb/Transaction.java | 16 +++ src/test/java/com/tidesdb/TidesDBTest.java | 118 +++++++++++++++++++++ 4 files changed, 147 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5772187..e4b3640 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.tidesdb tidesdb-java - 0.5.0 + 0.6.0 jar TidesDB Java diff --git a/src/main/c/com_tidesdb_TidesDB.c b/src/main/c/com_tidesdb_TidesDB.c index d572035..7072b56 100644 --- a/src/main/c/com_tidesdb_TidesDB.c +++ b/src/main/c/com_tidesdb_TidesDB.c @@ -724,6 +724,18 @@ JNIEXPORT jlong JNICALL Java_com_tidesdb_Transaction_nativeNewIterator(JNIEnv *e return (jlong)(uintptr_t)iter; } +JNIEXPORT void JNICALL Java_com_tidesdb_Transaction_nativeReset(JNIEnv *env, jclass cls, + jlong handle, jint isolationLevel) +{ + tidesdb_txn_t *txn = (tidesdb_txn_t *)(uintptr_t)handle; + int result = tidesdb_txn_reset(txn, (tidesdb_isolation_level_t)isolationLevel); + + if (result != TDB_SUCCESS) + { + throwTidesDBException(env, result, getErrorMessage(result)); + } +} + JNIEXPORT void JNICALL Java_com_tidesdb_Transaction_nativeFree(JNIEnv *env, jclass cls, jlong handle) { diff --git a/src/main/java/com/tidesdb/Transaction.java b/src/main/java/com/tidesdb/Transaction.java index ef6c1e3..a9fbd8f 100644 --- a/src/main/java/com/tidesdb/Transaction.java +++ b/src/main/java/com/tidesdb/Transaction.java @@ -187,6 +187,21 @@ public TidesDBIterator newIterator(ColumnFamily cf) throws TidesDBException { return new TidesDBIterator(iterHandle); } + /** + * Resets a committed or aborted transaction for reuse with a new isolation level. + * This avoids the overhead of freeing and reallocating transaction resources in hot loops. + * + * @param isolation the new isolation level for the reset transaction + * @throws TidesDBException if the reset fails (e.g., transaction is still active) + */ + public void reset(IsolationLevel isolation) throws TidesDBException { + checkNotFreed(); + if (isolation == null) { + throw new IllegalArgumentException("Isolation level cannot be null"); + } + nativeReset(nativeHandle, isolation.getValue()); + } + /** * Frees the transaction resources. */ @@ -225,5 +240,6 @@ long getNativeHandle() { private static native void nativeRollbackToSavepoint(long handle, String name) throws TidesDBException; private static native void nativeReleaseSavepoint(long handle, String name) throws TidesDBException; private static native long nativeNewIterator(long handle, long cfHandle) throws TidesDBException; + private static native void nativeReset(long handle, int isolationLevel) throws TidesDBException; private static native void nativeFree(long handle); } diff --git a/src/test/java/com/tidesdb/TidesDBTest.java b/src/test/java/com/tidesdb/TidesDBTest.java index e2fa35f..5af56de 100644 --- a/src/test/java/com/tidesdb/TidesDBTest.java +++ b/src/test/java/com/tidesdb/TidesDBTest.java @@ -654,4 +654,122 @@ void testTransactionPutGetDeleteBadKey() throws TidesDBException { }); } } + + @Test + @Order(17) + void testTransactionReset() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb17").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"); + + // Begin transaction and do first batch of work + Transaction txn = db.beginTransaction(); + txn.put(cf, "key1".getBytes(), "value1".getBytes()); + txn.commit(); + + // Reset instead of free + begin + txn.reset(IsolationLevel.READ_COMMITTED); + + // Second batch of work using the same transaction + txn.put(cf, "key2".getBytes(), "value2".getBytes()); + txn.commit(); + + // Free once when done + txn.free(); + + // Verify both keys exist + try (Transaction readTxn = db.beginTransaction()) { + byte[] result1 = readTxn.get(cf, "key1".getBytes()); + assertNotNull(result1); + assertArrayEquals("value1".getBytes(), result1); + + byte[] result2 = readTxn.get(cf, "key2".getBytes()); + assertNotNull(result2); + assertArrayEquals("value2".getBytes(), result2); + } + } + } + + @Test + @Order(18) + void testTransactionResetWithDifferentIsolation() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb18").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"); + + // Begin with READ_COMMITTED + Transaction txn = db.beginTransaction(IsolationLevel.READ_COMMITTED); + txn.put(cf, "key1".getBytes(), "value1".getBytes()); + txn.commit(); + + // Reset with different isolation level (REPEATABLE_READ) + txn.reset(IsolationLevel.REPEATABLE_READ); + txn.put(cf, "key2".getBytes(), "value2".getBytes()); + txn.commit(); + + // Reset again with SERIALIZABLE + txn.reset(IsolationLevel.SERIALIZABLE); + txn.put(cf, "key3".getBytes(), "value3".getBytes()); + txn.commit(); + + txn.free(); + + // Verify all keys exist + try (Transaction readTxn = db.beginTransaction()) { + for (int i = 1; i <= 3; i++) { + byte[] result = readTxn.get(cf, ("key" + i).getBytes()); + assertNotNull(result); + assertArrayEquals(("value" + i).getBytes(), result); + } + } + } + } + + @Test + @Order(19) + void testTransactionResetNullIsolation() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb19").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"); + + Transaction txn = db.beginTransaction(); + txn.put(cf, "key1".getBytes(), "value1".getBytes()); + txn.commit(); + + // Null isolation level should throw IllegalArgumentException + assertThrows(IllegalArgumentException.class, () -> txn.reset(null)); + + txn.free(); + } + } }