From d443b1a525edde0e8bbbfd64c8a2d1e0327d4cd3 Mon Sep 17 00:00:00 2001 From: Alex Gaetano Padula Date: Fri, 24 Apr 2026 18:42:22 -0400 Subject: [PATCH] iss #32 #33 retire unused objecttargetfilesize cf config field and add singledelete transaction binding; also update read me removing useless section on support --- README.md | 5 -- pom.xml | 2 +- src/main/c/com_tidesdb_TidesDB.c | 24 +++++- src/main/java/com/tidesdb/ColumnFamily.java | 4 +- .../java/com/tidesdb/ColumnFamilyConfig.java | 10 --- src/main/java/com/tidesdb/TidesDB.java | 3 +- src/main/java/com/tidesdb/Transaction.java | 29 ++++++- src/test/java/com/tidesdb/TidesDBTest.java | 84 +++++++++++++++++-- 8 files changed, 131 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 68eae55..86dc78e 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,3 @@ Multiple licenses apply: ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. - -## Support - -- [Discord](https://discord.gg/tWEmjR66cy) -- [GitHub Issues](https://github.com/tidesdb/tidesdb-java/issues) diff --git a/pom.xml b/pom.xml index 8d599e4..eb49773 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.tidesdb tidesdb-java - 0.6.8 + 0.7.0 jar TidesDB Java diff --git a/src/main/c/com_tidesdb_TidesDB.c b/src/main/c/com_tidesdb_TidesDB.c index 6ff9b0c..1aa8dcc 100644 --- a/src/main/c/com_tidesdb_TidesDB.c +++ b/src/main/c/com_tidesdb_TidesDB.c @@ -189,7 +189,7 @@ JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeCreateColumnFamily( jboolean enableBlockIndexes, jint indexSampleRatio, jint blockIndexPrefixLen, jint syncMode, jlong syncIntervalUs, jstring comparatorName, jint skipListMaxLevel, jfloat skipListProbability, jint defaultIsolationLevel, jlong minDiskSpace, jint l1FileCountTrigger, - jint l0QueueStallThreshold, jboolean useBtree, jlong objectTargetFileSize, + jint l0QueueStallThreshold, jboolean useBtree, jboolean objectLazyCompaction, jboolean objectPrefetchCompaction) { tidesdb_t *db = (tidesdb_t *)(uintptr_t)handle; @@ -227,7 +227,6 @@ JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeCreateColumnFamily( .l1_file_count_trigger = l1FileCountTrigger, .l0_queue_stall_threshold = l0QueueStallThreshold, .use_btree = useBtree ? 1 : 0, - .object_target_file_size = (size_t)objectTargetFileSize, .object_lazy_compaction = objectLazyCompaction ? 1 : 0, .object_prefetch_compaction = objectPrefetchCompaction ? 1 : 0}; @@ -724,6 +723,27 @@ JNIEXPORT void JNICALL Java_com_tidesdb_Transaction_nativeDelete(JNIEnv *env, jc } } +JNIEXPORT void JNICALL Java_com_tidesdb_Transaction_nativeSingleDelete(JNIEnv *env, jclass cls, + jlong handle, + jlong cfHandle, + jbyteArray key) +{ + tidesdb_txn_t *txn = (tidesdb_txn_t *)(uintptr_t)handle; + tidesdb_column_family_t *cf = (tidesdb_column_family_t *)(uintptr_t)cfHandle; + + jsize keyLen = (*env)->GetArrayLength(env, key); + jbyte *keyBytes = (*env)->GetByteArrayElements(env, key, NULL); + + int result = tidesdb_txn_single_delete(txn, cf, (uint8_t *)keyBytes, keyLen); + + (*env)->ReleaseByteArrayElements(env, key, keyBytes, JNI_ABORT); + + if (result != TDB_SUCCESS) + { + throwTidesDBException(env, result, getErrorMessage(result)); + } +} + JNIEXPORT void JNICALL Java_com_tidesdb_Transaction_nativeCommit(JNIEnv *env, jclass cls, jlong handle) { diff --git a/src/main/java/com/tidesdb/ColumnFamily.java b/src/main/java/com/tidesdb/ColumnFamily.java index 962dfa7..7a49143 100644 --- a/src/main/java/com/tidesdb/ColumnFamily.java +++ b/src/main/java/com/tidesdb/ColumnFamily.java @@ -149,9 +149,9 @@ public void syncWal() throws TidesDBException { /** * 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 + * 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. + * 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) diff --git a/src/main/java/com/tidesdb/ColumnFamilyConfig.java b/src/main/java/com/tidesdb/ColumnFamilyConfig.java index 8d421fb..fc383e6 100644 --- a/src/main/java/com/tidesdb/ColumnFamilyConfig.java +++ b/src/main/java/com/tidesdb/ColumnFamilyConfig.java @@ -44,7 +44,6 @@ public class ColumnFamilyConfig { private int l1FileCountTrigger; private int l0QueueStallThreshold; private boolean useBtree; - private long objectTargetFileSize; private boolean objectLazyCompaction; private boolean objectPrefetchCompaction; @@ -70,7 +69,6 @@ private ColumnFamilyConfig(Builder builder) { this.l1FileCountTrigger = builder.l1FileCountTrigger; this.l0QueueStallThreshold = builder.l0QueueStallThreshold; this.useBtree = builder.useBtree; - this.objectTargetFileSize = builder.objectTargetFileSize; this.objectLazyCompaction = builder.objectLazyCompaction; this.objectPrefetchCompaction = builder.objectPrefetchCompaction; } @@ -103,7 +101,6 @@ public static ColumnFamilyConfig defaultConfig() { .l1FileCountTrigger(4) .l0QueueStallThreshold(20) .useBtree(false) - .objectTargetFileSize(0) .objectLazyCompaction(false) .objectPrefetchCompaction(true) .build(); @@ -139,7 +136,6 @@ public static Builder builder() { public int getL1FileCountTrigger() { return l1FileCountTrigger; } public int getL0QueueStallThreshold() { return l0QueueStallThreshold; } public boolean isUseBtree() { return useBtree; } - public long getObjectTargetFileSize() { return objectTargetFileSize; } public boolean isObjectLazyCompaction() { return objectLazyCompaction; } public boolean isObjectPrefetchCompaction() { return objectPrefetchCompaction; } @@ -168,7 +164,6 @@ public static class Builder { private int l1FileCountTrigger = 4; private int l0QueueStallThreshold = 20; private boolean useBtree = false; - private long objectTargetFileSize = 0; private boolean objectLazyCompaction = false; private boolean objectPrefetchCompaction = true; @@ -277,11 +272,6 @@ public Builder useBtree(boolean useBtree) { return this; } - public Builder objectTargetFileSize(long objectTargetFileSize) { - this.objectTargetFileSize = objectTargetFileSize; - return this; - } - public Builder objectLazyCompaction(boolean objectLazyCompaction) { this.objectLazyCompaction = objectLazyCompaction; return this; diff --git a/src/main/java/com/tidesdb/TidesDB.java b/src/main/java/com/tidesdb/TidesDB.java index bef9caf..337b37d 100644 --- a/src/main/java/com/tidesdb/TidesDB.java +++ b/src/main/java/com/tidesdb/TidesDB.java @@ -142,7 +142,6 @@ public void createColumnFamily(String name, ColumnFamilyConfig config) throws Ti config.getL1FileCountTrigger(), config.getL0QueueStallThreshold(), config.isUseBtree(), - config.getObjectTargetFileSize(), config.isObjectLazyCompaction(), config.isObjectPrefetchCompaction() ); @@ -399,7 +398,7 @@ private static native void nativeCreateColumnFamily(long handle, String name, int syncMode, long syncIntervalUs, String comparatorName, int skipListMaxLevel, float skipListProbability, int defaultIsolationLevel, long minDiskSpace, int l1FileCountTrigger, int l0QueueStallThreshold, boolean useBtree, - long objectTargetFileSize, boolean objectLazyCompaction, + boolean objectLazyCompaction, boolean objectPrefetchCompaction) throws TidesDBException; private static native void nativeDropColumnFamily(long handle, String name) throws TidesDBException; diff --git a/src/main/java/com/tidesdb/Transaction.java b/src/main/java/com/tidesdb/Transaction.java index a9fbd8f..a6baadc 100644 --- a/src/main/java/com/tidesdb/Transaction.java +++ b/src/main/java/com/tidesdb/Transaction.java @@ -108,7 +108,33 @@ public void delete(ColumnFamily cf, byte[] key) throws TidesDBException { } nativeDelete(nativeHandle, cf.getNativeHandle(), key); } - + + /** + * Writes a single-delete tombstone for a key. Has the same read semantics as + * {@link #delete}, but lets compaction drop the put and tombstone together as + * soon as both appear in the same merge input. + * + * Caller contract: between any two single-deletes on the same key (and from + * the start of the key's history to its first single-delete) the key has been + * put at most once. The engine cannot verify this; violating it can leave + * older puts visible after the single-delete. Use only for workloads that + * insert each key once and delete it once. When in doubt, prefer {@link #delete}. + * + * @param cf the column family + * @param key the key + * @throws TidesDBException if the single-delete fails + */ + public void singleDelete(ColumnFamily cf, byte[] key) throws TidesDBException { + checkNotFreed(); + if (cf == null) { + throw new IllegalArgumentException("Column family cannot be null"); + } + if (key == null || key.length == 0) { + throw new IllegalArgumentException("Key cannot be null"); + } + nativeSingleDelete(nativeHandle, cf.getNativeHandle(), key); + } + /** * Commits the transaction. * @@ -234,6 +260,7 @@ long getNativeHandle() { private static native void nativePut(long handle, long cfHandle, byte[] key, byte[] value, long ttl) throws TidesDBException; private static native byte[] nativeGet(long handle, long cfHandle, byte[] key) throws TidesDBException; private static native void nativeDelete(long handle, long cfHandle, byte[] key) throws TidesDBException; + private static native void nativeSingleDelete(long handle, long cfHandle, byte[] key) throws TidesDBException; private static native void nativeCommit(long handle) throws TidesDBException; private static native void nativeRollback(long handle) throws TidesDBException; private static native void nativeSavepoint(long handle, String name) throws TidesDBException; diff --git a/src/test/java/com/tidesdb/TidesDBTest.java b/src/test/java/com/tidesdb/TidesDBTest.java index ce0e417..485128c 100644 --- a/src/test/java/com/tidesdb/TidesDBTest.java +++ b/src/test/java/com/tidesdb/TidesDBTest.java @@ -1024,7 +1024,7 @@ void testCommitHookClear() throws TidesDBException { return 0; }); - // First commit — hook should fire + // First commit - hook should fire try (Transaction txn = db.beginTransaction()) { txn.put(cf, "key1".getBytes(), "value1".getBytes()); txn.commit(); @@ -1034,7 +1034,7 @@ void testCommitHookClear() throws TidesDBException { // Clear the hook cf.clearCommitHook(); - // Second commit — hook should NOT fire + // Second commit - hook should NOT fire try (Transaction txn = db.beginTransaction()) { txn.put(cf, "key2".getBytes(), "value2".getBytes()); txn.commit(); @@ -1505,21 +1505,91 @@ void testTransactionResetNullIsolation() throws TidesDBException { .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(); } } + + @Test + @Order(42) + void testTransactionSingleDelete() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb_single_delete").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"); + + byte[] key = "single_key".getBytes(StandardCharsets.UTF_8); + byte[] value = "single_value".getBytes(StandardCharsets.UTF_8); + + try (Transaction txn = db.beginTransaction()) { + txn.put(cf, key, value); + txn.commit(); + } + + try (Transaction txn = db.beginTransaction()) { + byte[] result = txn.get(cf, key); + assertNotNull(result); + assertArrayEquals(value, result); + } + + try (Transaction txn = db.beginTransaction()) { + txn.singleDelete(cf, key); + txn.commit(); + } + + try (Transaction txn = db.beginTransaction()) { + assertThrows(TidesDBException.class, () -> txn.get(cf, key)); + } + } + } + + @Test + @Order(43) + void testTransactionSingleDeleteNullArgs() throws TidesDBException { + Config config = Config.builder(tempDir.resolve("testdb_single_delete_null").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"); + + try (Transaction txn = db.beginTransaction()) { + assertThrows(IllegalArgumentException.class, + () -> txn.singleDelete(null, "k".getBytes())); + assertThrows(IllegalArgumentException.class, + () -> txn.singleDelete(cf, null)); + assertThrows(IllegalArgumentException.class, + () -> txn.singleDelete(cf, new byte[0])); + } + } + } }