This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 0995d4014 feat(binding/java): add `rename` support (#3238)
0995d4014 is described below
commit 0995d4014c3f1fa016fce22f03126a86e781938c
Author: G-XD <[email protected]>
AuthorDate: Mon Oct 9 02:08:55 2023 +0800
feat(binding/java): add `rename` support (#3238)
* test(binding/java): add rename test
* test(binding/java): remove copy capability dependency in rename test
---
bindings/java/src/blocking_operator.rs | 28 ++
.../java/org/apache/opendal/BlockingOperator.java | 6 +
.../src/main/java/org/apache/opendal/Operator.java | 7 +
bindings/java/src/operator.rs | 41 +++
.../test/behavior/AbstractBehaviorTest.java | 303 +++++++++++++++++++++
5 files changed, 385 insertions(+)
diff --git a/bindings/java/src/blocking_operator.rs
b/bindings/java/src/blocking_operator.rs
index 1f4e9240d..fd7fffa23 100644
--- a/bindings/java/src/blocking_operator.rs
+++ b/bindings/java/src/blocking_operator.rs
@@ -178,3 +178,31 @@ fn intern_copy(
Ok(op.copy(&source_path, &target_path)?)
}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_rename(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut BlockingOperator,
+ source_path: JString,
+ target_path: JString,
+) {
+ intern_rename(&mut env, &mut *op, source_path,
target_path).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ })
+}
+
+fn intern_rename(
+ env: &mut JNIEnv,
+ op: &mut BlockingOperator,
+ source_path: JString,
+ target_path: JString,
+) -> Result<()> {
+ let source_path = jstring_to_string(env, &source_path)?;
+ let target_path = jstring_to_string(env, &target_path)?;
+
+ Ok(op.rename(&source_path, &target_path)?)
+}
diff --git
a/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
index 811f3d6b9..03e8ba8b7 100644
--- a/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
@@ -78,6 +78,10 @@ public class BlockingOperator extends NativeObject {
copy(nativeHandle, sourcePath, targetPath);
}
+ public void rename(String sourcePath, String targetPath) {
+ rename(nativeHandle, sourcePath, targetPath);
+ }
+
@Override
protected native void disposeInternal(long handle);
@@ -92,4 +96,6 @@ public class BlockingOperator extends NativeObject {
private static native long createDir(long nativeHandle, String path);
private static native long copy(long nativeHandle, String sourcePath,
String targetPath);
+
+ private static native long rename(long nativeHandle, String sourcePath,
String targetPath);
}
diff --git a/bindings/java/src/main/java/org/apache/opendal/Operator.java
b/bindings/java/src/main/java/org/apache/opendal/Operator.java
index 8c791aedd..7ca7bccd2 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -187,6 +187,11 @@ public class Operator extends NativeObject {
return AsyncRegistry.take(requestId);
}
+ public CompletableFuture<Void> rename(String sourcePath, String
targetPath) {
+ final long requestId = rename(nativeHandle, sourcePath, targetPath);
+ return AsyncRegistry.take(requestId);
+ }
+
@Override
protected native void disposeInternal(long handle);
@@ -215,4 +220,6 @@ public class Operator extends NativeObject {
private static native long createDir(long nativeHandle, String path);
private static native long copy(long nativeHandle, String sourcePath,
String targetPath);
+
+ private static native long rename(long nativeHandle, String sourcePath,
String targetPath);
}
diff --git a/bindings/java/src/operator.rs b/bindings/java/src/operator.rs
index 74e0b9c30..97fbaaa9a 100644
--- a/bindings/java/src/operator.rs
+++ b/bindings/java/src/operator.rs
@@ -372,6 +372,47 @@ async fn do_copy(op: &mut Operator, source_path: String,
target_path: String) ->
Ok(op.copy(&source_path, &target_path).await?)
}
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_rename(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut Operator,
+ source_path: JString,
+ target_path: JString,
+) -> jlong {
+ intern_rename(&mut env, op, source_path, target_path).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ 0
+ })
+}
+
+fn intern_rename(
+ env: &mut JNIEnv,
+ op: *mut Operator,
+ source_path: JString,
+ target_path: JString,
+) -> Result<jlong> {
+ let op = unsafe { &mut *op };
+ let id = request_id(env)?;
+
+ let source_path = jstring_to_string(env, &source_path)?;
+ let target_path = jstring_to_string(env, &target_path)?;
+
+ unsafe { get_global_runtime() }.spawn(async move {
+ let result = do_rename(op, source_path, target_path).await;
+ complete_future(id, result.map(|_| JValueOwned::Void))
+ });
+
+ Ok(id)
+}
+
+async fn do_rename(op: &mut Operator, source_path: String, target_path:
String) -> Result<()> {
+ Ok(op.rename(&source_path, &target_path).await?)
+}
+
/// # Safety
///
/// This function should not be called before the Operator are ready.
diff --git
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
index ee7c5721b..b88290f65 100644
---
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
+++
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
@@ -375,6 +375,158 @@ public abstract class AbstractBehaviorTest {
}
}
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @Nested
+ class AsyncRenameTest {
+ @BeforeAll
+ public void precondition() {
+ final Capability capability = operator.info.fullCapability;
+ assumeTrue(capability.read && capability.write &&
capability.rename);
+ }
+
+ /**
+ * Rename a file and test with stat.
+ */
+ @Test
+ public void testRenameFile() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] content = generateBytes();
+
+ operator.write(sourcePath, content).join();
+
+ final String targetPath = UUID.randomUUID().toString();
+
+ operator.rename(sourcePath, targetPath).join();
+
+ assertThatThrownBy(() -> operator.stat(sourcePath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+ assertThat(operator.read(targetPath).join()).isEqualTo(content);
+
+ operator.delete(sourcePath).join();
+ operator.delete(targetPath).join();
+ }
+
+ /**
+ * Rename a nonexistent source should return an error.
+ */
+ @Test
+ public void testRenameNonExistingSource() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final String targetPath = UUID.randomUUID().toString();
+
+ assertThatThrownBy(() -> operator.rename(sourcePath,
targetPath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+ }
+
+ /**
+ * Rename a dir as source should return an error.
+ */
+ @Test
+ public void testRenameSourceDir() {
+ final String sourcePath = String.format("%s/",
UUID.randomUUID().toString());
+ final String targetPath = UUID.randomUUID().toString();
+
+ operator.createDir(sourcePath).join();
+
+ assertThatThrownBy(() -> operator.rename(sourcePath,
targetPath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+
+ operator.delete(sourcePath).join();
+ }
+
+ /**
+ * Rename to a dir should return an error.
+ */
+ @Test
+ public void testRenameTargetDir() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] content = generateBytes();
+
+ operator.write(sourcePath, content).join();
+
+ final String targetPath = String.format("%s/",
UUID.randomUUID().toString());
+
+ operator.createDir(targetPath).join();
+
+ assertThatThrownBy(() -> operator.rename(sourcePath,
targetPath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+
+ operator.delete(sourcePath).join();
+ operator.delete(targetPath).join();
+ }
+
+ /**
+ * Rename a file to self should return an error.
+ */
+ @Test
+ public void testRenameSelf() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] content = generateBytes();
+
+ operator.write(sourcePath, content).join();
+
+ assertThatThrownBy(() -> operator.rename(sourcePath,
sourcePath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsSameFile));
+
+ operator.delete(sourcePath).join();
+ }
+
+ /**
+ * Rename to a nested path, parent path should be created successfully.
+ */
+ @Test
+ public void testRenameNested() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] content = generateBytes();
+
+ operator.write(sourcePath, content).join();
+
+ final String targetPath = String.format(
+ "%s/%s/%s",
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString());
+
+ operator.rename(sourcePath, targetPath).join();
+
+ assertThatThrownBy(() -> operator.stat(sourcePath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+ assertThat(operator.read(targetPath).join()).isEqualTo(content);
+
+ operator.delete(sourcePath).join();
+ operator.delete(targetPath).join();
+ }
+
+ /**
+ * Rename to a exist path should overwrite successfully.
+ */
+ @Test
+ public void testRenameOverwrite() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] sourceContent = generateBytes();
+
+ operator.write(sourcePath, sourceContent).join();
+
+ final String targetPath = UUID.randomUUID().toString();
+ final byte[] targetContent = generateBytes();
+ assertNotEquals(sourceContent, targetContent);
+
+ operator.write(targetPath, targetContent).join();
+
+ operator.rename(sourcePath, targetPath).join();
+
+ assertThatThrownBy(() -> operator.stat(sourcePath).join())
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+
assertThat(operator.read(targetPath).join()).isEqualTo(sourceContent);
+
+ operator.delete(sourcePath).join();
+ operator.delete(targetPath).join();
+ }
+ }
+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Nested
class BlockingWriteTest {
@@ -605,6 +757,157 @@ public abstract class AbstractBehaviorTest {
}
}
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @Nested
+ class BlockingRenameTest {
+ @BeforeAll
+ public void precondition() {
+ final Capability capability = blockingOperator.info.fullCapability;
+ assumeTrue(capability.read && capability.write &&
capability.blocking && capability.rename);
+ }
+
+ /**
+ * Rename a file and test with stat.
+ */
+ @Test
+ public void testBlockingRenameFile() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] sourceContent = generateBytes();
+
+ blockingOperator.write(sourcePath, sourceContent);
+
+ final String targetPath = UUID.randomUUID().toString();
+
+ blockingOperator.rename(sourcePath, targetPath);
+
+ assertThatThrownBy(() -> blockingOperator.stat(sourcePath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+
assertThat(blockingOperator.stat(targetPath).getContentLength()).isEqualTo(sourceContent.length);
+
+ blockingOperator.delete(sourcePath);
+ blockingOperator.delete(targetPath);
+ }
+
+ /**
+ * Rename a nonexistent source should return an error.
+ */
+ @Test
+ public void testBlockingRenameNonExistingSource() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final String targetPath = UUID.randomUUID().toString();
+
+ assertThatThrownBy(() -> blockingOperator.rename(sourcePath,
targetPath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+ }
+
+ /**
+ * Rename a dir as source should return an error.
+ */
+ @Test
+ public void testBlockingRenameSourceDir() {
+ final String sourcePath = String.format("%s/",
UUID.randomUUID().toString());
+ final String targetPath = UUID.randomUUID().toString();
+
+ blockingOperator.createDir(sourcePath);
+
+ assertThatThrownBy(() -> blockingOperator.rename(sourcePath,
targetPath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+ }
+
+ /**
+ * Rename to a dir should return an error.
+ */
+ @Test
+ public void testBlockingRenameTargetDir() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] sourceContent = generateBytes();
+
+ blockingOperator.write(sourcePath, sourceContent);
+
+ final String targetPath = String.format("%s/",
UUID.randomUUID().toString());
+
+ blockingOperator.createDir(targetPath);
+
+ assertThatThrownBy(() -> blockingOperator.rename(sourcePath,
targetPath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+
+ blockingOperator.delete(sourcePath);
+ blockingOperator.delete(targetPath);
+ }
+
+ /**
+ * Rename a file to self should return an error.
+ */
+ @Test
+ public void testBlockingRenameSelf() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] sourceContent = generateBytes();
+
+ blockingOperator.write(sourcePath, sourceContent);
+
+ assertThatThrownBy(() -> blockingOperator.rename(sourcePath,
sourcePath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsSameFile));
+
+ blockingOperator.delete(sourcePath);
+ }
+
+ /**
+ * Rename to a nested path, parent path should be created successfully.
+ */
+ @Test
+ public void testBlockingRenameNested() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] sourceContent = generateBytes();
+
+ blockingOperator.write(sourcePath, sourceContent);
+
+ final String targetPath = String.format(
+ "%s/%s/%s",
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString());
+
+ blockingOperator.rename(sourcePath, targetPath);
+
+ assertThatThrownBy(() -> blockingOperator.stat(sourcePath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+
assertThat(blockingOperator.read(targetPath)).isEqualTo(sourceContent);
+
+ blockingOperator.delete(sourcePath);
+ blockingOperator.delete(targetPath);
+ }
+
+ /**
+ * Rename to a exist path should overwrite successfully.
+ */
+ @Test
+ public void testBlockingRenameOverwrite() {
+ final String sourcePath = UUID.randomUUID().toString();
+ final byte[] sourceContent = generateBytes();
+
+ blockingOperator.write(sourcePath, sourceContent);
+
+ final String targetPath = UUID.randomUUID().toString();
+ final byte[] targetContent = generateBytes();
+
+ assertNotEquals(sourceContent, targetContent);
+
+ blockingOperator.write(targetPath, targetContent);
+
+ blockingOperator.rename(sourcePath, targetPath);
+
+ assertThatThrownBy(() -> blockingOperator.stat(sourcePath))
+
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+
assertThat(blockingOperator.read(targetPath)).isEqualTo(sourceContent);
+
+ blockingOperator.delete(sourcePath);
+ blockingOperator.delete(targetPath);
+ }
+ }
+
/**
* Generates a byte array of random content.
*/