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)