From b799ebaa6910d1d410d33ec70c653ae2827cc1a9 Mon Sep 17 00:00:00 2001 From: kid <19265318+u70b3@users.noreply.github.com> Date: Wed, 29 Jul 2026 04:36:08 +0800 Subject: [PATCH] fix(node): reject non-string Arrow metadata (#3728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - validate Arrow metadata keys and values independently at runtime - reject malformed foreign schemas before constructing a local Arrow schema - cover valid and invalid metadata entries across Arrow 15–18 ## Testing - `node_modules/.bin/jest --runInBand __test__/arrow.test.ts -t "schema metadata"` - `node_modules/.bin/jest --runInBand __test__/arrow.test.ts` - `node node_modules/@biomejs/biome/bin/biome format --write lancedb/sanitize.ts __test__/arrow.test.ts` - `pnpm lint` - `pnpm build` - `pnpm run docs` Fixes #3729 --- nodejs/__test__/arrow.test.ts | 31 +++++++++++++++++++++++++++++++ nodejs/lancedb/sanitize.ts | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/nodejs/__test__/arrow.test.ts b/nodejs/__test__/arrow.test.ts index 2735f7f72..9e20e3c04 100644 --- a/nodejs/__test__/arrow.test.ts +++ b/nodejs/__test__/arrow.test.ts @@ -991,6 +991,37 @@ describe.each([arrow15, arrow16, arrow17, arrow18])( expectValidMapField(roundTripped.schema.fields[0]); }); + + it("preserves string schema metadata", function () { + const metadata = new Map([["source", "fixture"]]); + const schema = new Schema( + [new Field("value", new Int32(), true)], + metadata, + ); + + expect(makeEmptyTable(schema).schema.metadata.get("source")).toBe( + "fixture", + ); + }); + + it.each([ + ["non-string keys", new Map([[42, "fixture"]])], + ["non-string values", new Map([["source", 42]])], + [ + "non-string keys and values", + new Map([[42, false]]), + ], + ])("rejects schema metadata with %s", function (_, metadataLike) { + const metadata = metadataLike as unknown as Map; + const schema = new Schema( + [new Field("value", new Int32(), true)], + metadata, + ); + + expect(() => makeEmptyTable(schema)).toThrow( + "Expected metadata, if present, to be a Map but it had non-string keys or values", + ); + }); }); describe("when using two versions of arrow", function () { diff --git a/nodejs/lancedb/sanitize.ts b/nodejs/lancedb/sanitize.ts index 9f834a7f6..ae0bc0179 100644 --- a/nodejs/lancedb/sanitize.ts +++ b/nodejs/lancedb/sanitize.ts @@ -84,7 +84,7 @@ export function sanitizeMetadata( throw Error("Expected metadata, if present, to be a Map"); } for (const item of metadataLike) { - if (!(typeof item[0] === "string" || !(typeof item[1] === "string"))) { + if (typeof item[0] !== "string" || typeof item[1] !== "string") { throw Error( "Expected metadata, if present, to be a Map but it had non-string keys or values", );