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
832 changes: 575 additions & 257 deletions src/main/c/com_tidesdb_TidesDB.c

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions src/main/java/com/tidesdb/ColumnFamily.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,25 @@ public class ColumnFamily {
private final long nativeHandle;
private final String name;
private long commitHookCtxHandle = 0;
private final TidesDB owner;

ColumnFamily(long nativeHandle, String name) {
this(nativeHandle, name, null);
}

ColumnFamily(long nativeHandle, String name, TidesDB owner) {
this.nativeHandle = nativeHandle;
this.name = name;
this.owner = owner;
}

/**
* Checks that the owning database is open. Throws if the owner is closed.
*/
void checkOwnerOpen() {
if (owner != null && owner.isClosed()) {
throw new IllegalStateException("TidesDB instance is closed");
}
}

/**
Expand All @@ -53,6 +68,7 @@ public String getName() {
* @throws TidesDBException if the stats cannot be retrieved
*/
public Stats getStats() throws TidesDBException {
checkOwnerOpen();
return nativeGetStats(nativeHandle);
}

Expand All @@ -62,6 +78,7 @@ public Stats getStats() throws TidesDBException {
* @throws TidesDBException if compaction fails
*/
public void compact() throws TidesDBException {
checkOwnerOpen();
nativeCompact(nativeHandle);
}

Expand All @@ -80,6 +97,7 @@ public void compact() throws TidesDBException {
* or the merge fails
*/
public void compactRange(byte[] startKey, byte[] endKey) throws TidesDBException {
checkOwnerOpen();
nativeCompactRange(nativeHandle, startKey, endKey);
}

Expand All @@ -89,6 +107,7 @@ public void compactRange(byte[] startKey, byte[] endKey) throws TidesDBException
* @throws TidesDBException if flush fails
*/
public void flushMemtable() throws TidesDBException {
checkOwnerOpen();
nativeFlushMemtable(nativeHandle);
}

Expand All @@ -98,6 +117,7 @@ public void flushMemtable() throws TidesDBException {
* @return true if flushing is in progress
*/
public boolean isFlushing() {
checkOwnerOpen();
return nativeIsFlushing(nativeHandle);
}

Expand All @@ -107,6 +127,7 @@ public boolean isFlushing() {
* @return true if compaction is in progress
*/
public boolean isCompacting() {
checkOwnerOpen();
return nativeIsCompacting(nativeHandle);
}

Expand All @@ -130,6 +151,7 @@ public boolean isCompacting() {
* @throws TidesDBException if the update fails
*/
public void updateRuntimeConfig(ColumnFamilyConfig config, boolean persistToDisk) throws TidesDBException {
checkOwnerOpen();
if (config == null) {
throw new IllegalArgumentException("Config cannot be null");
}
Expand All @@ -152,6 +174,7 @@ public void updateRuntimeConfig(ColumnFamilyConfig config, boolean persistToDisk
* @throws TidesDBException if the purge fails
*/
public void purge() throws TidesDBException {
checkOwnerOpen();
nativePurge(nativeHandle);
}

Expand All @@ -162,6 +185,7 @@ public void purge() throws TidesDBException {
* @throws TidesDBException if the WAL sync fails
*/
public void syncWal() throws TidesDBException {
checkOwnerOpen();
nativeSyncWal(nativeHandle);
}

Expand All @@ -177,6 +201,7 @@ public void syncWal() throws TidesDBException {
* @throws TidesDBException if the estimation fails
*/
public double rangeCost(byte[] keyA, byte[] keyB) throws TidesDBException {
checkOwnerOpen();
if (keyA == null || keyA.length == 0) {
throw new IllegalArgumentException("keyA cannot be null or empty");
}
Expand All @@ -199,10 +224,15 @@ public double rangeCost(byte[] keyA, byte[] keyB) throws TidesDBException {
* @throws TidesDBException if the hook cannot be set
*/
public void setCommitHook(CommitHook hook) throws TidesDBException {
checkOwnerOpen();
if (hook == null) {
throw new IllegalArgumentException("Hook cannot be null, use clearCommitHook() instead");
}
boolean firstInstall = (commitHookCtxHandle == 0);
commitHookCtxHandle = nativeSetCommitHook(nativeHandle, hook, commitHookCtxHandle);
if (firstInstall && owner != null) {
owner.registerHookColumnFamily(this);
}
}

/**
Expand All @@ -212,7 +242,26 @@ public void setCommitHook(CommitHook hook) throws TidesDBException {
* @throws TidesDBException if the hook cannot be cleared
*/
public void clearCommitHook() throws TidesDBException {
checkOwnerOpen();
commitHookCtxHandle = nativeSetCommitHook(nativeHandle, null, commitHookCtxHandle);
if (owner != null) {
owner.unregisterHookColumnFamily(this);
}
}

/**
* Best-effort hook detach during database close. The hook is removed from
* the engine without caller interaction. Errors are swallowed.
*/
void clearHookOnClose() {
if (commitHookCtxHandle != 0) {
try {
nativeSetCommitHook(nativeHandle, null, commitHookCtxHandle);
} catch (TidesDBException ignored) {
// Best-effort during shutdown.
}
commitHookCtxHandle = 0;
}
}

long getNativeHandle() {
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/com/tidesdb/ColumnFamilyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,75 @@ public Builder objectPrefetchCompaction(boolean objectPrefetchCompaction) {
}

public ColumnFamilyConfig build() {
validate();
return new ColumnFamilyConfig(this);
}

private void validate() {
// Nullable-enum checks (do first, before field-level checks)
if (compressionAlgorithm == null) {
throw new IllegalArgumentException("compressionAlgorithm must not be null");
}
if (syncMode == null) {
throw new IllegalArgumentException("syncMode must not be null");
}
if (defaultIsolationLevel == null) {
throw new IllegalArgumentException("defaultIsolationLevel must not be null");
}

// Non-negative (zero sentinel OK)
if (klogValueThreshold < 0) {
throw new IllegalArgumentException("klogValueThreshold must not be negative, was: " + klogValueThreshold);
}
if (syncIntervalUs < 0) {
throw new IllegalArgumentException("syncIntervalUs must not be negative, was: " + syncIntervalUs);
}
if (minDiskSpace < 0) {
throw new IllegalArgumentException("minDiskSpace must not be negative, was: " + minDiskSpace);
}
if (dividingLevelOffset < 0) {
throw new IllegalArgumentException("dividingLevelOffset must not be negative, was: " + dividingLevelOffset);
}
if (blockIndexPrefixLen < 0) {
throw new IllegalArgumentException("blockIndexPrefixLen must not be negative, was: " + blockIndexPrefixLen);
}
if (skipListMaxLevel < 0) {
throw new IllegalArgumentException("skipListMaxLevel must not be negative, was: " + skipListMaxLevel);
}

// Positive-required (zero rejected)
if (writeBufferSize <= 0) {
throw new IllegalArgumentException("writeBufferSize must be positive, was: " + writeBufferSize);
}
if (levelSizeRatio <= 0) {
throw new IllegalArgumentException("levelSizeRatio must be positive, was: " + levelSizeRatio);
}
if (minLevels <= 0) {
throw new IllegalArgumentException("minLevels must be positive, was: " + minLevels);
}
if (indexSampleRatio <= 0) {
throw new IllegalArgumentException("indexSampleRatio must be positive, was: " + indexSampleRatio);
}
if (l1FileCountTrigger <= 0) {
throw new IllegalArgumentException("l1FileCountTrigger must be positive, was: " + l1FileCountTrigger);
}
if (l0QueueStallThreshold <= 0) {
throw new IllegalArgumentException("l0QueueStallThreshold must be positive, was: " + l0QueueStallThreshold);
}
if (tombstoneDensityMinEntries <= 0) {
throw new IllegalArgumentException("tombstoneDensityMinEntries must be positive, was: " + tombstoneDensityMinEntries);
}

// Float/double range and finiteness
if (Double.isNaN(bloomFPR) || Double.isInfinite(bloomFPR) || bloomFPR < 0.0 || bloomFPR > 1.0) {
throw new IllegalArgumentException("bloomFPR must be finite and in [0.0, 1.0], was: " + bloomFPR);
}
if (Float.isNaN(skipListProbability) || Float.isInfinite(skipListProbability) || skipListProbability < 0.0f || skipListProbability > 1.0f) {
throw new IllegalArgumentException("skipListProbability must be finite and in [0.0, 1.0], was: " + skipListProbability);
}
if (Double.isNaN(tombstoneDensityTrigger) || Double.isInfinite(tombstoneDensityTrigger) || tombstoneDensityTrigger < 0.0 || tombstoneDensityTrigger > 1.0) {
throw new IllegalArgumentException("tombstoneDensityTrigger must be finite and in [0.0, 1.0], was: " + tombstoneDensityTrigger);
}
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/tidesdb/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,31 @@ private void validate() throws IllegalArgumentException {
if (maxOpenSSTables <= 0) {
throw new IllegalArgumentException("Max open SSTables must be positive");
}
if (logTruncationAt < 0) {
throw new IllegalArgumentException("logTruncationAt must not be negative, was: " + logTruncationAt);
}
if (maxMemoryUsage < 0) {
throw new IllegalArgumentException("maxMemoryUsage must not be negative, was: " + maxMemoryUsage);
}
if (unifiedMemtableWriteBufferSize < 0) {
throw new IllegalArgumentException("unifiedMemtableWriteBufferSize must not be negative, was: " + unifiedMemtableWriteBufferSize);
}
if (unifiedMemtableSyncIntervalUs < 0) {
throw new IllegalArgumentException("unifiedMemtableSyncIntervalUs must not be negative, was: " + unifiedMemtableSyncIntervalUs);
}
if (unifiedMemtableSkipListMaxLevel < 0) {
throw new IllegalArgumentException("unifiedMemtableSkipListMaxLevel must not be negative, was: " + unifiedMemtableSkipListMaxLevel);
}
if (Float.isNaN(unifiedMemtableSkipListProbability) || Float.isInfinite(unifiedMemtableSkipListProbability)
|| unifiedMemtableSkipListProbability < 0.0f || unifiedMemtableSkipListProbability > 1.0f) {
throw new IllegalArgumentException("unifiedMemtableSkipListProbability must be finite and in [0.0, 1.0], was: " + unifiedMemtableSkipListProbability);
}
if (unifiedMemtableSyncMode < 0) {
throw new IllegalArgumentException("unifiedMemtableSyncMode must not be negative, was: " + unifiedMemtableSyncMode);
}
if (maxConcurrentFlushes < 0) {
throw new IllegalArgumentException("maxConcurrentFlushes must not be negative, was: " + maxConcurrentFlushes);
}
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/tidesdb/ObjectStoreConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,32 @@ public Builder replicaReplayWal(boolean replicaReplayWal) {
}

public ObjectStoreConfig build() {
validate();
return new ObjectStoreConfig(this);
}

private void validate() {
if (localCacheMaxBytes < 0) {
throw new IllegalArgumentException("localCacheMaxBytes must not be negative, was: " + localCacheMaxBytes);
}
if (maxConcurrentUploads <= 0) {
throw new IllegalArgumentException("maxConcurrentUploads must be positive, was: " + maxConcurrentUploads);
}
if (maxConcurrentDownloads <= 0) {
throw new IllegalArgumentException("maxConcurrentDownloads must be positive, was: " + maxConcurrentDownloads);
}
if (multipartThreshold < 0) {
throw new IllegalArgumentException("multipartThreshold must not be negative, was: " + multipartThreshold);
}
if (multipartPartSize < 0) {
throw new IllegalArgumentException("multipartPartSize must not be negative, was: " + multipartPartSize);
}
if (walSyncThresholdBytes < 0) {
throw new IllegalArgumentException("walSyncThresholdBytes must not be negative, was: " + walSyncThresholdBytes);
}
if (replicaSyncIntervalUs < 0) {
throw new IllegalArgumentException("replicaSyncIntervalUs must not be negative, was: " + replicaSyncIntervalUs);
}
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/tidesdb/S3Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ public S3Config build() {
if (secretKey == null || secretKey.isEmpty()) {
throw new IllegalArgumentException("S3 secret key is required");
}
if (multipartThreshold < 0) {
throw new IllegalArgumentException("multipartThreshold must not be negative, was: " + multipartThreshold);
}
if (multipartPartSize < 0) {
throw new IllegalArgumentException("multipartPartSize must not be negative, was: " + multipartPartSize);
}
return new S3Config(this);
}
}
Expand Down
51 changes: 49 additions & 2 deletions src/main/java/com/tidesdb/TidesDB.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
package com.tidesdb;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* TidesDB is the main database class providing access to TidesDB functionality.
Expand All @@ -32,6 +36,7 @@ public class TidesDB implements Closeable {

private long nativeHandle;
private boolean closed = false;
private final Set<ColumnFamily> hookBearingCFs = new HashSet<>();

private TidesDB(long nativeHandle) {
this.nativeHandle = nativeHandle;
Expand Down Expand Up @@ -134,9 +139,26 @@ public static boolean isS3Available() {
@Override
public void close() {
if (!closed && nativeHandle != 0) {
closed = true;

// Drain all registered hooks. Loop because a concurrent setCommitHook
// may have passed checkOwnerOpen before closed=true but hasn't
// registered yet; its registration is rejected (closed is true), but a
// retry loop guarantees the set is empty before calling nativeClose.
while (true) {
List<ColumnFamily> batch;
synchronized (hookBearingCFs) {
if (hookBearingCFs.isEmpty()) break;
batch = new ArrayList<>(hookBearingCFs);
hookBearingCFs.clear();
}
for (ColumnFamily cf : batch) {
cf.clearHookOnClose();
}
}

nativeClose(nativeHandle);
nativeHandle = 0;
closed = true;
}
}

Expand Down Expand Up @@ -212,7 +234,7 @@ public ColumnFamily getColumnFamily(String name) throws TidesDBException {
throw new IllegalArgumentException("Column family name cannot be null or empty");
}
long cfHandle = nativeGetColumnFamily(nativeHandle, name);
return new ColumnFamily(cfHandle, name);
return new ColumnFamily(cfHandle, name, this);
}

/**
Expand Down Expand Up @@ -436,6 +458,31 @@ private void checkNotClosed() {
}
}

/**
* Reports whether this database instance has been closed.
*/
boolean isClosed() {
return closed;
}

/**
* Registers a column family that has an installed commit hook.
*/
void registerHookColumnFamily(ColumnFamily cf) {
synchronized (hookBearingCFs) {
if (!closed) hookBearingCFs.add(cf);
}
}

/**
* Unregisters a column family whose commit hook has been cleared.
*/
void unregisterHookColumnFamily(ColumnFamily cf) {
synchronized (hookBearingCFs) {
hookBearingCFs.remove(cf);
}
}

long getNativeHandle() {
return nativeHandle;
}
Expand Down
Loading
Loading