From 036f4ec53fac660b7703fe707dbcd7c5b405e020 Mon Sep 17 00:00:00 2001 From: adenzhou1350 Date: Fri, 23 Oct 2026 03:13:31 +0800 Subject: [PATCH] fix(node): preserve options when namespace is omitted (#4479) The namespace overloads allow `undefined` to select the root namespace, but overload dispatch treats it as the legacy options argument and discards the trailing options. For example, `createTable(name, data, undefined, { mode: "overwrite" })` raises an already-exists error instead of overwriting. Preserve the trailing options for `createTable`, `createEmptyTable`, `tableNames`, and `listTables` when the namespace is omitted. Legacy options-object calls and explicit namespace arrays keep their existing behavior. Adds native-database regressions for overwrite, existOk, and both listing APIs, parameterized over `undefined` and `[]`. Validation on Windows / Node 24.19.0: - Before the fix: all five omitted-namespace cases failed; all five explicit-root controls passed. - Current TypeScript source with official, integrity-verified 0.41.0-beta.0 native binaries: the entire connection test file passed, 47/47 (transpile-only). - The same fix replayed onto the matching 0.41.0-beta.0 source: TypeScript compilation, strict ts-jest connection tests (47/47), and TypeDoc passed. - Biome check/format on both changed files and diff whitespace checks passed. No Rust changes. A complete current-main native rebuild remains for CI: released declarations predate the unrelated lazy-iterator APIs, so current-main typechecking against those declarations reports six pre-existing iterator signature errors. Signed-off-by: Xucheng Zhou Co-authored-by: Xuanwo --- nodejs/__test__/connection.test.ts | 60 ++++++++++++++++++++++++++++++ nodejs/lancedb/connection.ts | 8 ++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/nodejs/__test__/connection.test.ts b/nodejs/__test__/connection.test.ts index 1167ae34a..355b86ccb 100644 --- a/nodejs/__test__/connection.test.ts +++ b/nodejs/__test__/connection.test.ts @@ -117,6 +117,66 @@ describe("given a connection", () => { await expect(tbl.countRows()).resolves.toBe(1); }); + describe.each([undefined, []])("root namespace %p", (namespacePath) => { + it("should preserve createTable options after the namespace", async () => { + await db.createTable("test", [{ id: 1 }, { id: 2 }]); + + const table = await db.createTable("test", [{ id: 3 }], namespacePath, { + mode: "overwrite", + }); + + const rows = await table.query().toArray(); + expect(rows.map((row) => row.id)).toEqual([3]); + }); + + it("should preserve createEmptyTable overwrite after the namespace", async () => { + await db.createTable("test", [{ id: 1 }]); + const schema = new Schema([new Field("id", new Float64(), true)]); + + const table = await db.createEmptyTable("test", schema, namespacePath, { + mode: "overwrite", + }); + + await expect(table.countRows()).resolves.toBe(0); + }); + + it("should preserve createEmptyTable existOk after the namespace", async () => { + await db.createTable("test", [{ id: 1 }]); + const schema = new Schema([new Field("id", new Float64(), true)]); + + const table = await db.createEmptyTable("test", schema, namespacePath, { + existOk: true, + }); + + await expect(table.countRows()).resolves.toBe(1); + }); + + it("should preserve tableNames options after the namespace", async () => { + for (const name of ["a", "b", "c"]) { + await db.createTable(name, [{ id: 1 }]); + } + + await expect( + db.tableNames(namespacePath, { limit: 1, startAfter: "a" }), + ).resolves.toEqual(["b"]); + }); + + it("should preserve listTables options after the namespace", async () => { + for (const name of ["a", "b", "c"]) { + await db.createTable(name, [{ id: 1 }]); + } + + const first = await db.listTables(namespacePath, { limit: 1 }); + expect(first.tables).toEqual(["a"]); + expect(first.pageToken).toBeDefined(); + + const second = await db.listTables(namespacePath, { + limit: 1, + pageToken: first.pageToken, + }); + expect(second.tables).toEqual(["b"]); + }); + }); it("should keep the existing table when mode is exist_ok", async () => { const created = await db.createTable("test", [{ id: 1 }, { id: 2 }], { mode: "exist_ok", diff --git a/nodejs/lancedb/connection.ts b/nodejs/lancedb/connection.ts index 4ed511b86..f866d1ecd 100644 --- a/nodejs/lancedb/connection.ts +++ b/nodejs/lancedb/connection.ts @@ -720,7 +720,7 @@ export class LocalConnection extends Connection { } else { // First argument is options object (backwards compatibility) namespacePath = undefined; - tableNamesOptions = namespacePathOrOptions; + tableNamesOptions = namespacePathOrOptions ?? options; } return this.inner.tableNames( @@ -831,7 +831,7 @@ export class LocalConnection extends Connection { : undefined; const listTablesOptions = Array.isArray(namespacePathOrOptions) ? options - : namespacePathOrOptions; + : (namespacePathOrOptions ?? options); return this.inner.listTables( namespacePath ?? [], @@ -942,7 +942,7 @@ export class LocalConnection extends Connection { } else { // Third argument is options object (backwards compatibility) namespacePath = undefined; - createOptions = namespacePathOrOptions; + createOptions = namespacePathOrOptions ?? options; } return this._createTableImpl(name, data, namespacePath, createOptions); @@ -989,7 +989,7 @@ export class LocalConnection extends Connection { } else { // Third argument is options object (backwards compatibility) namespacePath = undefined; - createOptions = namespacePathOrOptions; + createOptions = namespacePathOrOptions ?? options; } let mode: string = createOptions?.mode ?? "create";