mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
fix(node): normalize record batches before IPC serialization (#4871)
Passing a record batch created by an ESM `apache-arrow` import to `RRFReranker.rerankHybrid` throws an IPC footer parse error. The SDK's CommonJS Arrow writer receives data types from another module instance. Normalize single-record-batch input with the existing table sanitizer before writing IPC. Extend the Arrow 15–21 round-trip test with caller-created batches and nulls, and add a native ESM reranker regression covering 64-bit row IDs, scores, and an empty FTS side. Validation: - Reproduced with published `0.42.0-beta.3`; CommonJS and explicitly normalized ESM controls pass. - Same native module and inputs: all 8 targeted cases fail with the parent serializer and pass with this change. - Local native build, TypeScript build, lint, and formatting pass. - Current-head CI passes on Linux Node 22/24/26, macOS, and Arrow 19/20. The macOS run passes 1,643 tests; Linux Node 24 also passes docs and example checks.
This commit is contained in:
@@ -1567,11 +1567,17 @@ describe.each(arrowVersions)("Arrow %#", (arrow: ApacheArrow) => {
|
||||
const records = [
|
||||
{ text: "dog", vector: [0.1, 0.2] },
|
||||
{ text: "cat", vector: [0.3, 0.4] },
|
||||
{ text: null, vector: null },
|
||||
];
|
||||
const table = await convertToTable(records);
|
||||
const table = arrow.tableFromArrays({
|
||||
text: records.map((row) => row.text),
|
||||
vector: records.map((row) => row.vector),
|
||||
});
|
||||
const batch = table.batches[0];
|
||||
|
||||
const buffer = await fromRecordBatchToBuffer(batch);
|
||||
const buffer = await fromRecordBatchToBuffer(
|
||||
batch as Parameters<typeof fromRecordBatchToBuffer>[0],
|
||||
);
|
||||
const result = await fromBufferToRecordBatch(buffer);
|
||||
|
||||
expect(JSON.stringify(batch.toArray())).toEqual(
|
||||
|
||||
@@ -1,13 +1,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import * as path from "node:path";
|
||||
import { RecordBatch } from "apache-arrow";
|
||||
import * as tmp from "tmp";
|
||||
import { Connection, Index, Table, connect, makeArrowTable } from "../lancedb";
|
||||
import { RRFReranker } from "../lancedb/rerankers";
|
||||
|
||||
it("reranks record batches created by an ESM Arrow import", () => {
|
||||
const script = `
|
||||
import assert from "node:assert/strict";
|
||||
import { tableFromArrays } from "apache-arrow";
|
||||
import { rerankers } from "./dist/index.js";
|
||||
|
||||
const ids = [1n, 9007199254740993n];
|
||||
const batch = tableFromArrays({ _rowid: new BigUint64Array(ids) }).batches[0];
|
||||
const reranker = await rerankers.RRFReranker.create();
|
||||
const result = await reranker.rerankHybrid("", batch, batch);
|
||||
|
||||
assert.equal(result.numRows, 2);
|
||||
assert.deepEqual(Array.from(result.getChild("_rowid").toArray()), ids);
|
||||
const scores = result.getChild("_relevance_score").toArray();
|
||||
assert.ok(Math.abs(scores[0] - 2 / 61) < 1e-8);
|
||||
assert.ok(Math.abs(scores[1] - 2 / 62) < 1e-8);
|
||||
|
||||
const oneSide = await reranker.rerankHybrid("", batch, batch.slice(0, 0));
|
||||
assert.deepEqual(Array.from(oneSide.getChild("_rowid").toArray()), ids);
|
||||
const oneSideScores = oneSide.getChild("_relevance_score").toArray();
|
||||
assert.ok(Math.abs(oneSideScores[0] - 1 / 61) < 1e-8);
|
||||
assert.ok(Math.abs(oneSideScores[1] - 1 / 62) < 1e-8);
|
||||
`;
|
||||
execFileSync(process.execPath, ["--input-type=module", "-e", script], {
|
||||
cwd: path.resolve(__dirname, ".."),
|
||||
timeout: 10_000,
|
||||
});
|
||||
});
|
||||
|
||||
describe("rerankers", function () {
|
||||
let tmpDir: tmp.DirResult;
|
||||
let conn: Connection;
|
||||
|
||||
@@ -1386,7 +1386,9 @@ export async function fromBufferToRecordBatch(
|
||||
export async function fromRecordBatchToBuffer(
|
||||
batch: RecordBatch,
|
||||
): Promise<Buffer> {
|
||||
const writer = new RecordBatchFileWriter().writeAll([batch]);
|
||||
// Arrow writers require data types from their own library instance.
|
||||
const table = sanitizeTable({ schema: batch.schema, batches: [batch] });
|
||||
const writer = new RecordBatchFileWriter().writeAll(table);
|
||||
return Buffer.from(await writer.toUint8Array());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user