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