Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.tidesdb</groupId>
<artifactId>tidesdb-java</artifactId>
<version>0.5.0</version>
<version>0.6.0</version>
<packaging>jar</packaging>

<name>TidesDB Java</name>
Expand Down
12 changes: 12 additions & 0 deletions src/main/c/com_tidesdb_TidesDB.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/tidesdb/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
}
118 changes: 118 additions & 0 deletions src/test/java/com/tidesdb/TidesDBTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
Loading