From f2fc4faf26bb491c844533fa09bbec01ad418848 Mon Sep 17 00:00:00 2001 From: Brendan Clement Date: Wed, 13 May 2026 16:19:10 -0700 Subject: [PATCH] feat(nodejs): add renameTable on Connection --- docs/src/js/classes/Connection.md | 33 ++++++++++ docs/src/js/globals.md | 1 + docs/src/js/interfaces/RenameTableOptions.md | 29 +++++++++ nodejs/__test__/connection.test.ts | 8 +++ nodejs/__test__/remote.test.ts | 64 ++++++++++++++++++++ nodejs/lancedb/connection.ts | 44 ++++++++++++++ nodejs/lancedb/index.ts | 1 + nodejs/src/connection.rs | 19 ++++++ 8 files changed, 199 insertions(+) create mode 100644 docs/src/js/interfaces/RenameTableOptions.md diff --git a/docs/src/js/classes/Connection.md b/docs/src/js/classes/Connection.md index f79eb5232..ad24644b6 100644 --- a/docs/src/js/classes/Connection.md +++ b/docs/src/js/classes/Connection.md @@ -437,6 +437,39 @@ Open a table in the database. *** +### renameTable() + +```ts +abstract renameTable( + currentName, + newName, + options?): Promise +``` + +Rename a table. + +Currently only supported by LanceDB Cloud. Local OSS connections and +namespace-backed connections (via [connectNamespace](../functions/connectNamespace.md)) reject with +a "not supported" error. + +#### Parameters + +* **currentName**: `string` + The current name of the table. + +* **newName**: `string` + The new name for the table. + +* **options?**: [`RenameTableOptions`](../interfaces/RenameTableOptions.md) + Optional namespace paths. When + `newNamespacePath` is omitted the table stays in `namespacePath`. + +#### Returns + +`Promise`<`void`> + +*** + ### tableNames() #### tableNames(options) diff --git a/docs/src/js/globals.md b/docs/src/js/globals.md index fca33544f..10015b462 100644 --- a/docs/src/js/globals.md +++ b/docs/src/js/globals.md @@ -84,6 +84,7 @@ - [OptimizeStats](interfaces/OptimizeStats.md) - [QueryExecutionOptions](interfaces/QueryExecutionOptions.md) - [RemovalStats](interfaces/RemovalStats.md) +- [RenameTableOptions](interfaces/RenameTableOptions.md) - [RestNamespaceConfig](interfaces/RestNamespaceConfig.md) - [RetryConfig](interfaces/RetryConfig.md) - [ShuffleOptions](interfaces/ShuffleOptions.md) diff --git a/docs/src/js/interfaces/RenameTableOptions.md b/docs/src/js/interfaces/RenameTableOptions.md new file mode 100644 index 000000000..f3eaaa723 --- /dev/null +++ b/docs/src/js/interfaces/RenameTableOptions.md @@ -0,0 +1,29 @@ +[**@lancedb/lancedb**](../README.md) • **Docs** + +*** + +[@lancedb/lancedb](../globals.md) / RenameTableOptions + +# Interface: RenameTableOptions + +## Properties + +### namespacePath? + +```ts +optional namespacePath: string[]; +``` + +The namespace path of the table being renamed. Defaults to the root +namespace (`[]`) when omitted. + +*** + +### newNamespacePath? + +```ts +optional newNamespacePath: string[]; +``` + +The namespace path to move the table to as part of the rename. When +omitted the table stays in `namespacePath`. diff --git a/nodejs/__test__/connection.test.ts b/nodejs/__test__/connection.test.ts index b7bafad7c..d195a0ca4 100644 --- a/nodejs/__test__/connection.test.ts +++ b/nodejs/__test__/connection.test.ts @@ -47,6 +47,14 @@ describe("given a connection", () => { await db.close(); expect(db.isOpen()).toBe(false); await expect(db.tableNames()).rejects.toThrow("Connection is closed"); + await expect(db.renameTable("a", "b")).rejects.toThrow( + "Connection is closed", + ); + }); + + it("should report renameTable as unsupported on an OSS connection", async () => { + await db.createTable("a", [{ id: 1 }]); + await expect(db.renameTable("a", "b")).rejects.toThrow(/not supported/); }); it("should be able to create a table from an object arg `createTable(options)`, or args `createTable(name, data, options)`", async () => { let tbl = await db.createTable("test", [{ id: 1 }, { id: 2 }]); diff --git a/nodejs/__test__/remote.test.ts b/nodejs/__test__/remote.test.ts index ff3826da2..cbddf9673 100644 --- a/nodejs/__test__/remote.test.ts +++ b/nodejs/__test__/remote.test.ts @@ -617,4 +617,68 @@ describe("remote connection", () => { ); }); }); + + describe("renameTable", () => { + async function captureRenameRequest( + call: (db: Connection) => Promise, + ): Promise<{ url: string; body: Record }> { + let captured: { url: string; body: Record } | undefined; + await withMockDatabase((req, res) => { + let raw = ""; + req.on("data", (chunk) => { + raw += chunk; + }); + req.on("end", () => { + captured = { + url: req.url ?? "", + body: raw ? JSON.parse(raw) : {}, + }; + res.writeHead(200, { "Content-Type": "application/json" }).end(""); + }); + }, call); + if (!captured) { + throw new Error("mock server never saw a request"); + } + return captured; + } + + it("sends rename request for a table in the root namespace", async () => { + const { url, body } = await captureRenameRequest(async (db) => { + await db.renameTable("table1", "table2"); + }); + expect(url).toBe("/v1/table/table1/rename/"); + // biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format + expect(body).toEqual({ new_table_name: "table2" }); + }); + + it("omits new_namespace when only the current namespace is supplied", async () => { + // Safe-default check: passing namespacePath alone must not send + // `new_namespace`, so the server keeps the table in its current + // namespace instead of silently moving it to root. + const { url, body } = await captureRenameRequest(async (db) => { + await db.renameTable("table1", "table2", { + namespacePath: ["ns1"], + }); + }); + expect(url).toBe("/v1/table/ns1$table1/rename/"); + // biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format + expect(body).toEqual({ new_table_name: "table2" }); + }); + + it("includes new_namespace in the body for a cross-namespace rename", async () => { + const { url, body } = await captureRenameRequest(async (db) => { + await db.renameTable("table1", "table2", { + namespacePath: ["ns1"], + newNamespacePath: ["ns2"], + }); + }); + expect(url).toBe("/v1/table/ns1$table1/rename/"); + expect(body).toEqual({ + // biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format + new_table_name: "table2", + // biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format + new_namespace: ["ns2"], + }); + }); + }); }); diff --git a/nodejs/lancedb/connection.ts b/nodejs/lancedb/connection.ts index 45513f77c..f6eacb20f 100644 --- a/nodejs/lancedb/connection.ts +++ b/nodejs/lancedb/connection.ts @@ -144,6 +144,19 @@ export interface DropNamespaceOptions { behavior?: "restrict" | "cascade"; } +export interface RenameTableOptions { + /** + * The namespace path of the table being renamed. Defaults to the root + * namespace (`[]`) when omitted. + */ + namespacePath?: string[]; + /** + * The namespace path to move the table to as part of the rename. When + * omitted the table stays in `namespacePath`. + */ + newNamespacePath?: string[]; +} + /** * A LanceDB Connection that allows you to open tables and create new ones. * @@ -391,6 +404,24 @@ export abstract class Connection { isShallow?: boolean; }, ): Promise; + + /** + * Rename a table. + * + * Currently only supported by LanceDB Cloud. Local OSS connections and + * namespace-backed connections (via {@link connectNamespace}) reject with + * a "not supported" error. + * + * @param {string} currentName - The current name of the table. + * @param {string} newName - The new name for the table. + * @param {RenameTableOptions} options - Optional namespace paths. When + * `newNamespacePath` is omitted the table stays in `namespacePath`. + */ + abstract renameTable( + currentName: string, + newName: string, + options?: RenameTableOptions, + ): Promise; } /** @hideconstructor */ @@ -651,6 +682,19 @@ export class LocalConnection extends Connection { options?.behavior, ); } + + async renameTable( + currentName: string, + newName: string, + options?: RenameTableOptions, + ): Promise { + return this.inner.renameTable( + currentName, + newName, + options?.namespacePath ?? [], + options?.newNamespacePath, + ); + } } /** diff --git a/nodejs/lancedb/index.ts b/nodejs/lancedb/index.ts index 8952cf043..3246bde0b 100644 --- a/nodejs/lancedb/index.ts +++ b/nodejs/lancedb/index.ts @@ -71,6 +71,7 @@ export { CreateNamespaceResponse, DropNamespaceResponse, DescribeNamespaceResponse, + RenameTableOptions, } from "./connection"; export { Session } from "./native.js"; diff --git a/nodejs/src/connection.rs b/nodejs/src/connection.rs index eb8bab22e..18d6644de 100644 --- a/nodejs/src/connection.rs +++ b/nodejs/src/connection.rs @@ -459,4 +459,23 @@ impl Connection { transaction_id: resp.transaction_id, }) } + + /// Rename a table. `current_namespace_path` and `new_namespace_path` default to + /// the root namespace when omitted; the caller is expected to either pass both + /// or pass neither. + #[napi(catch_unwind)] + pub async fn rename_table( + &self, + current_name: String, + new_name: String, + current_namespace_path: Option>, + new_namespace_path: Option>, + ) -> napi::Result<()> { + let cur_ns = current_namespace_path.unwrap_or_default(); + let new_ns = new_namespace_path.unwrap_or_default(); + self.get_inner()? + .rename_table(¤t_name, &new_name, &cur_ns, &new_ns) + .await + .default_error() + } }