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();
+ }
+ }
}