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.6.2</version>
<version>0.6.3</version>
<packaging>jar</packaging>

<name>TidesDB Java</name>
Expand Down
29 changes: 29 additions & 0 deletions src/main/c/com_tidesdb_TidesDB.c
Original file line number Diff line number Diff line change
Expand Up @@ -912,3 +912,32 @@ JNIEXPORT void JNICALL Java_com_tidesdb_TidesDBIterator_nativeFree(JNIEnv *env,
tidesdb_iter_free(iter);
}
}

JNIEXPORT jdouble JNICALL Java_com_tidesdb_ColumnFamily_nativeRangeCost(JNIEnv *env, jclass cls,
jlong handle,
jbyteArray keyA,
jbyteArray keyB)
{
tidesdb_column_family_t *cf = (tidesdb_column_family_t *)(uintptr_t)handle;

jsize keyALen = (*env)->GetArrayLength(env, keyA);
jsize keyBLen = (*env)->GetArrayLength(env, keyB);

jbyte *keyABytes = (*env)->GetByteArrayElements(env, keyA, NULL);
jbyte *keyBBytes = (*env)->GetByteArrayElements(env, keyB, NULL);

double cost = 0.0;
int result = tidesdb_range_cost(cf, (uint8_t *)keyABytes, keyALen, (uint8_t *)keyBBytes,
keyBLen, &cost);

(*env)->ReleaseByteArrayElements(env, keyA, keyABytes, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, keyB, keyBBytes, JNI_ABORT);

if (result != TDB_SUCCESS)
{
throwTidesDBException(env, result, getErrorMessage(result));
return 0.0;
}

return (jdouble)cost;
}
22 changes: 22 additions & 0 deletions src/main/java/com/tidesdb/ColumnFamily.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,27 @@ public void updateRuntimeConfig(ColumnFamilyConfig config, boolean persistToDisk
persistToDisk);
}

/**
* Estimates the computational cost of iterating between two keys in this column family.
* The returned value is an opaque double — meaningful only for comparison with other
* values from the same method. Uses only in-memory metadata and performs no disk I/O.
* Key order does not matter — the method normalizes the range internally.
*
* @param keyA first key (bound of range)
* @param keyB second key (bound of range)
* @return estimated traversal cost (higher = more expensive), 0.0 if no overlapping data
* @throws TidesDBException if the estimation fails
*/
public double rangeCost(byte[] keyA, byte[] keyB) throws TidesDBException {
if (keyA == null || keyA.length == 0) {
throw new IllegalArgumentException("keyA cannot be null or empty");
}
if (keyB == null || keyB.length == 0) {
throw new IllegalArgumentException("keyB cannot be null or empty");
}
return nativeRangeCost(nativeHandle, keyA, keyB);
}

long getNativeHandle() {
return nativeHandle;
}
Expand All @@ -137,4 +158,5 @@ long getNativeHandle() {
private static native void nativeUpdateRuntimeConfig(long handle, long writeBufferSize,
int skipListMaxLevel, float skipListProbability, double bloomFPR, int indexSampleRatio,
int syncMode, long syncIntervalUs, boolean persistToDisk) throws TidesDBException;
private static native double nativeRangeCost(long handle, byte[] keyA, byte[] keyB) throws TidesDBException;
}
94 changes: 94 additions & 0 deletions src/test/java/com/tidesdb/TidesDBTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,100 @@ void testTransactionResetWithDifferentIsolation() throws TidesDBException {
}
}

@Test
@Order(22)
void testRangeCost() throws TidesDBException {
Config config = Config.builder(tempDir.resolve("testdb20").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 data
try (Transaction txn = db.beginTransaction()) {
for (int i = 0; i < 100; i++) {
String key = String.format("key%04d", i);
txn.put(cf, key.getBytes(), ("value" + i).getBytes());
}
txn.commit();
}

// Estimate cost for a range
double cost = cf.rangeCost("key0000".getBytes(), "key0099".getBytes());
assertTrue(cost >= 0.0, "Range cost should be non-negative");
}
}

@Test
@Order(23)
void testRangeCostComparison() throws TidesDBException {
Config config = Config.builder(tempDir.resolve("testdb21").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 data
try (Transaction txn = db.beginTransaction()) {
for (int i = 0; i < 1000; i++) {
String key = String.format("key%04d", i);
txn.put(cf, key.getBytes(), ("value" + i).getBytes());
}
txn.commit();
}

// Both costs should be non-negative
double costSmall = cf.rangeCost("key0000".getBytes(), "key0010".getBytes());
double costLarge = cf.rangeCost("key0000".getBytes(), "key0999".getBytes());
assertTrue(costSmall >= 0.0, "Small range cost should be non-negative");
assertTrue(costLarge >= 0.0, "Large range cost should be non-negative");
}
}

@Test
@Order(24)
void testRangeCostNullKeys() throws TidesDBException {
Config config = Config.builder(tempDir.resolve("testdb22").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");

assertThrows(IllegalArgumentException.class,
() -> cf.rangeCost(null, "key".getBytes()));
assertThrows(IllegalArgumentException.class,
() -> cf.rangeCost("key".getBytes(), null));
assertThrows(IllegalArgumentException.class,
() -> cf.rangeCost(new byte[0], "key".getBytes()));
assertThrows(IllegalArgumentException.class,
() -> cf.rangeCost("key".getBytes(), new byte[0]));
}
}

@Test
@Order(21)
void testTransactionResetNullIsolation() throws TidesDBException {
Expand Down
Loading