diff --git a/java/lance-jni/src/namespace.rs b/java/lance-jni/src/namespace.rs index b9db171c064..148171f8c7a 100644 --- a/java/lance-jni/src/namespace.rs +++ b/java/lance-jni/src/namespace.rs @@ -993,6 +993,23 @@ pub extern "system" fn Java_org_lance_namespace_RestNamespace_declareTableNative .into_raw() } +#[no_mangle] +pub extern "system" fn Java_org_lance_namespace_RestNamespace_renameTableNative( + mut env: JNIEnv, + _obj: JObject, + handle: jlong, + request_json: JString, +) -> jstring { + ok_or_throw_with_return!( + env, + call_rest_namespace_method(&mut env, handle, request_json, |ns, req| { + RT.block_on(ns.inner.rename_table(req)) + }), + std::ptr::null_mut() + ) + .into_raw() +} + #[no_mangle] pub extern "system" fn Java_org_lance_namespace_RestNamespace_insertIntoTableNative( mut env: JNIEnv, diff --git a/java/src/main/java/org/lance/namespace/RestNamespace.java b/java/src/main/java/org/lance/namespace/RestNamespace.java index 840e9f3d690..63dfe28dea7 100644 --- a/java/src/main/java/org/lance/namespace/RestNamespace.java +++ b/java/src/main/java/org/lance/namespace/RestNamespace.java @@ -243,6 +243,14 @@ public DeclareTableResponse declareTable(DeclareTableRequest request) { return fromJson(responseJson, DeclareTableResponse.class); } + @Override + public RenameTableResponse renameTable(RenameTableRequest request) { + ensureInitialized(); + String requestJson = toJson(request); + String responseJson = renameTableNative(nativeRestNamespaceHandle, requestJson); + return fromJson(responseJson, RenameTableResponse.class); + } + @Override public InsertIntoTableResponse insertIntoTable( InsertIntoTableRequest request, byte[] requestData) { @@ -397,6 +405,8 @@ private native long createNativeWithProvider( private native String declareTableNative(long handle, String requestJson); + private native String renameTableNative(long handle, String requestJson); + private native String insertIntoTableNative(long handle, String requestJson, byte[] requestData); private native String mergeInsertIntoTableNative( diff --git a/java/src/test/java/org/lance/namespace/RestNamespaceTest.java b/java/src/test/java/org/lance/namespace/RestNamespaceTest.java index 797ef5d6785..29522e54b4b 100644 --- a/java/src/test/java/org/lance/namespace/RestNamespaceTest.java +++ b/java/src/test/java/org/lance/namespace/RestNamespaceTest.java @@ -325,4 +325,40 @@ void testCreateEmptyTable() { assertNotNull(createResp); assertNotNull(createResp.getLocation()); } + + @Test + void testRenameTable() throws Exception { + // Create parent namespace + CreateNamespaceRequest createNsReq = + new CreateNamespaceRequest().id(Arrays.asList("workspace")); + namespace.createNamespace(createNsReq); + + // Create a table + byte[] tableData = createTestTableData(); + CreateTableRequest createReq = + new CreateTableRequest().id(Arrays.asList("workspace", "test_table")); + namespace.createTable(createReq, tableData); + + // TODO: underlying dir namespace doesn't support rename yet... + + // // Rename the table + // RenameTableRequest renameReq = + // new RenameTableRequest() + // .id(Arrays.asList("workspace", "test_table")) + // .newNamespaceId(Arrays.asList("workspace")) + // .newTableName("test_table_renamed"); + + // RenameTableResponse renameRes = namespace.renameTable(renameReq); + // assertNotNull(renameRes); + + // // Verify table with old name no longer exists + // TableExistsRequest oldExistsReq = + // new TableExistsRequest().id(Arrays.asList("workspace", "test_table")); + // assertThrows(RuntimeException.class, () -> namespace.tableExists(oldExistsReq)); + + // // Verify table with new name exists + // TableExistsRequest existsReq = + // new TableExistsRequest().id(Arrays.asList("workspace", "test_table_renamed")); + // assertDoesNotThrow(() -> namespace.tableExists(existsReq)); + } } diff --git a/python/python/lance/namespace.py b/python/python/lance/namespace.py index 9df0c451173..bccb602f169 100644 --- a/python/python/lance/namespace.py +++ b/python/python/lance/namespace.py @@ -42,6 +42,8 @@ NamespaceExistsRequest, RegisterTableRequest, RegisterTableResponse, + RenameTableRequest, + RenameTableResponse, TableExistsRequest, ) @@ -536,6 +538,10 @@ def declare_table(self, request: DeclareTableRequest) -> DeclareTableResponse: response_dict = self._inner.declare_table(request.model_dump()) return DeclareTableResponse.from_dict(response_dict) + def rename_table(self, request: RenameTableRequest) -> RenameTableResponse: + response_dict = self._inner.rename_table(request.model_dump()) + return RenameTableResponse.from_dict(response_dict) + class RestAdapter: """REST adapter server that creates a namespace backend and exposes it via REST. diff --git a/python/python/tests/test_namespace_rest.py b/python/python/tests/test_namespace_rest.py index de1a57ace8d..9dcc3a35f43 100644 --- a/python/python/tests/test_namespace_rest.py +++ b/python/python/tests/test_namespace_rest.py @@ -405,6 +405,39 @@ def test_register_table_rejects_path_traversal(self, rest_namespace): rest_namespace.register_table(register_req) assert "Path traversal is not allowed" in str(exc_info.value) + def test_rename_table(self, rest_namespace): + """Test renaming a table.""" + # Create parent namespace + create_ns_req = CreateNamespaceRequest(id=["workspace"]) + rest_namespace.create_namespace(create_ns_req) + + # Create table + table_data = create_test_data() + ipc_data = table_to_ipc_bytes(table_data) + create_req = CreateTableRequest(id=["workspace", "test_table"]) + rest_namespace.create_table(create_req, ipc_data) + + # TODO: underlying dir namespace doesn't support rename yet... + + # # Rename the table + # rename_req = RenameTableRequest( + # id=["workspace", "test_table"], + # new_namespace_id=["workspace"], + # new_table_name="test_table_renamed", + # ) + + # response = rest_namespace.rename_table(rename_req) + # assert response is not None + + # # Verify table with old name no longer exists + # exists_req = TableExistsRequest(id=["workspace", "test_table"]) + # with pytest.raises(Exception): + # rest_namespace.table_exists(exists_req) + + # # Verify table with new name exists + # exists_req = TableExistsRequest(id=["workspace", "test_table_renamed"]) + # rest_namespace.table_exists(exists_req) + class TestChildNamespaceOperations: """Tests for operations in child namespaces - mirrors DirectoryNamespace tests.""" diff --git a/python/src/namespace.rs b/python/src/namespace.rs index 90bc3f8fa03..fb2769f66c2 100644 --- a/python/src/namespace.rs +++ b/python/src/namespace.rs @@ -548,6 +548,18 @@ impl PyRestNamespace { .infer_error()?; Ok(pythonize(py, &response)?.into()) } + + fn rename_table<'py>( + &self, + py: Python<'py>, + request: &Bound<'_, PyAny>, + ) -> PyResult> { + let request = depythonize(request)?; + let response = crate::rt() + .block_on(Some(py), self.inner.rename_table(request))? + .infer_error()?; + Ok(pythonize(py, &response)?.into()) + } } /// Python wrapper for REST adapter server