diff --git a/jitpack.yml b/jitpack.yml
new file mode 100644
index 0000000..cc0dcea
--- /dev/null
+++ b/jitpack.yml
@@ -0,0 +1,4 @@
+jdk:
+ - openjdk11
+install:
+ - mvn clean install -DskipTests
diff --git a/pom.xml b/pom.xml
index acc89b0..5772187 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.tidesdb
tidesdb-java
- 0.4.0
+ 0.5.0
jar
TidesDB Java
diff --git a/src/main/c/com_tidesdb_TidesDB.c b/src/main/c/com_tidesdb_TidesDB.c
index d81df22..d572035 100644
--- a/src/main/c/com_tidesdb_TidesDB.c
+++ b/src/main/c/com_tidesdb_TidesDB.c
@@ -398,6 +398,38 @@ JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeRenameColumnFamily(JNIEnv
}
}
+JNIEXPORT void JNICALL Java_com_tidesdb_TidesDB_nativeCloneColumnFamily(JNIEnv *env, jclass cls,
+ jlong handle,
+ jstring sourceName,
+ jstring destName)
+{
+ tidesdb_t *db = (tidesdb_t *)(uintptr_t)handle;
+ const char *srcCfName = (*env)->GetStringUTFChars(env, sourceName, NULL);
+ if (srcCfName == NULL)
+ {
+ throwTidesDBException(env, TDB_ERR_MEMORY, "Failed to get source column family name");
+ return;
+ }
+
+ const char *dstCfName = (*env)->GetStringUTFChars(env, destName, NULL);
+ if (dstCfName == NULL)
+ {
+ (*env)->ReleaseStringUTFChars(env, sourceName, srcCfName);
+ throwTidesDBException(env, TDB_ERR_MEMORY, "Failed to get destination column family name");
+ return;
+ }
+
+ int result = tidesdb_clone_column_family(db, srcCfName, dstCfName);
+
+ (*env)->ReleaseStringUTFChars(env, sourceName, srcCfName);
+ (*env)->ReleaseStringUTFChars(env, destName, dstCfName);
+
+ if (result != TDB_SUCCESS)
+ {
+ throwTidesDBException(env, result, getErrorMessage(result));
+ }
+}
+
JNIEXPORT jobject JNICALL Java_com_tidesdb_ColumnFamily_nativeGetStats(JNIEnv *env, jclass cls,
jlong handle)
{
diff --git a/src/main/java/com/tidesdb/TidesDB.java b/src/main/java/com/tidesdb/TidesDB.java
index 6b2797b..4281140 100644
--- a/src/main/java/com/tidesdb/TidesDB.java
+++ b/src/main/java/com/tidesdb/TidesDB.java
@@ -247,6 +247,26 @@ public void renameColumnFamily(String oldName, String newName) throws TidesDBExc
nativeRenameColumnFamily(nativeHandle, oldName, newName);
}
+ /**
+ * Creates a complete copy of an existing column family with a new name.
+ * The clone contains all the data from the source at the time of cloning.
+ * The clone is completely independent - modifications to one do not affect the other.
+ *
+ * @param sourceName the source column family name
+ * @param destName the destination column family name
+ * @throws TidesDBException if the clone fails
+ */
+ public void cloneColumnFamily(String sourceName, String destName) throws TidesDBException {
+ checkNotClosed();
+ if (sourceName == null || sourceName.isEmpty()) {
+ throw new IllegalArgumentException("Source column family name cannot be null or empty");
+ }
+ if (destName == null || destName.isEmpty()) {
+ throw new IllegalArgumentException("Destination column family name cannot be null or empty");
+ }
+ nativeCloneColumnFamily(nativeHandle, sourceName, destName);
+ }
+
private void checkNotClosed() {
if (closed) {
throw new IllegalStateException("TidesDB instance is closed");
@@ -288,4 +308,6 @@ private static native void nativeCreateColumnFamily(long handle, String name,
private static native void nativeBackup(long handle, String dir) throws TidesDBException;
private static native void nativeRenameColumnFamily(long handle, String oldName, String newName) throws TidesDBException;
+
+ private static native void nativeCloneColumnFamily(long handle, String sourceName, String destName) throws TidesDBException;
}
diff --git a/src/test/java/com/tidesdb/TidesDBTest.java b/src/test/java/com/tidesdb/TidesDBTest.java
index 6953304..e2fa35f 100644
--- a/src/test/java/com/tidesdb/TidesDBTest.java
+++ b/src/test/java/com/tidesdb/TidesDBTest.java
@@ -556,6 +556,64 @@ void testBtreeIterator() throws TidesDBException {
@Test
@Order(15)
+ void testCloneColumnFamily() throws TidesDBException {
+ Config config = Config.builder(tempDir.resolve("testdb15").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("source_cf", cfConfig);
+
+ ColumnFamily sourceCf = db.getColumnFamily("source_cf");
+
+ // Insert data into source
+ try (Transaction txn = db.beginTransaction()) {
+ for (int i = 0; i < 10; i++) {
+ txn.put(sourceCf, ("key" + i).getBytes(), ("value" + i).getBytes());
+ }
+ txn.commit();
+ }
+
+ // Clone the column family
+ db.cloneColumnFamily("source_cf", "cloned_cf");
+
+ // Verify clone exists
+ ColumnFamily clonedCf = db.getColumnFamily("cloned_cf");
+ assertNotNull(clonedCf);
+ assertEquals("cloned_cf", clonedCf.getName());
+
+ // Verify both column families are listed
+ String[] families = db.listColumnFamilies();
+ assertTrue(families.length >= 2);
+
+ // Verify data exists in clone
+ try (Transaction txn = db.beginTransaction()) {
+ for (int i = 0; i < 10; i++) {
+ byte[] result = txn.get(clonedCf, ("key" + i).getBytes());
+ assertNotNull(result);
+ assertArrayEquals(("value" + i).getBytes(), result);
+ }
+ }
+
+ // Verify independence: insert into clone, should not appear in source
+ try (Transaction txn = db.beginTransaction()) {
+ txn.put(clonedCf, "clone_only_key".getBytes(), "clone_only_value".getBytes());
+ txn.commit();
+ }
+
+ try (Transaction txn = db.beginTransaction()) {
+ assertThrows(TidesDBException.class, () -> txn.get(sourceCf, "clone_only_key".getBytes()));
+ }
+ }
+ }
+
+ @Test
+ @Order(16)
void testTransactionPutGetDeleteBadKey() throws TidesDBException {
Config config = Config.builder(tempDir.resolve("testdb3").toString())
.numFlushThreads(2)