feat: expose the remaining refresh options on the SDKs (#4890)

The server's backfill request takes a row filter, failure limits,
per-call timeouts and worker sizing, and SQL exposes all of them through
`REFRESH COLUMN ... WHERE ... WITH (...)`, but the typed SDKs could send
only batch_size, GPU placement and the retry and error policies. Anyone
needing a filtered refresh or a bounded skip policy had to drop to SQL.

This adds `where`, `max_failed_fraction`, `max_failed_rows`,
`row_timeout_seconds`, `init_timeout_seconds`, `num_workers` and
`max_executor_processes_per_worker` to Python, Rust and Node, passed
through unchanged. `RefreshColumnOptions` now serializes straight into
the request body, so the three SDKs share one producer. Node gets a
`RefreshColumnOptions` object, which also carries the options it lacked
before. A LanceDB Embedded refresh rejects every option other than
batch_size and GPU placement, naming the option.
This commit is contained in:
Wyatt Alt
2026-11-06 09:19:27 -08:00
committed by GitHub
parent d379bbb722
commit 2af85bea89
18 changed files with 974 additions and 53 deletions
+74
View File
@@ -647,6 +647,80 @@ describe("remote connection", () => {
]);
});
it("forwards refresh options to the backfill request", async () => {
const bodies: unknown[] = [];
await withMockDatabase(
(req, res) => {
const path = req.url ?? "";
if (path.endsWith("/describe/")) {
res.writeHead(200, { "Content-Type": "application/json" }).end(
JSON.stringify({
name: "docs",
version: 1,
schema: { fields: [] },
}),
);
return;
}
if (path === "/v1/table/docs/backfill_column") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
bodies.push(JSON.parse(body));
res
.writeHead(202, { "Content-Type": "application/json" })
.end('{"job_id": "j-9"}');
});
return;
}
res.writeHead(404).end();
},
async (db) => {
const table = await db.openTable("docs");
const job = await table.refreshColumnAsync("embedding", {
where: "id % 2 = 0",
batchSize: 16,
gpuClass: "a10g",
gpuCount: 2,
errorPolicy: "skip",
maxFailedFraction: 0.01,
maxFailedRows: 50,
rowTimeoutSeconds: 30,
initTimeoutSeconds: 120,
maxExecutorProcessesPerWorker: 2,
});
expect(job.id).toBe("j-9");
await table.refreshColumnAsync("embedding");
// Invalid options reject before any request is sent.
for (const [options, message] of [
[{ maxFailedRows: -1 }, "maxFailedRows"],
[{ errorPolicy: "retry" }, "errorPolicy"],
[{ where: " " }, "where"],
[{ maxFailedFraction: 2 }, "max_failed_fraction"],
[{ rowTimeoutSeconds: 0 }, "row_timeout_seconds"],
[{ numWorkers: 0 }, "num_workers"],
[{ gpuCount: 0 }, "gpu_count"],
] as const) {
await expect(
table.refreshColumnAsync("embedding", options),
).rejects.toThrow(message);
}
},
);
expect(bodies).toEqual([
JSON.parse(
`{"column": "embedding", "where": "id % 2 = 0", "batch_size": 16,
"gpu_class": "a10g", "gpu_count": 2, "error_policy": "skip",
"max_failed_fraction": 0.01, "max_failed_rows": 50,
"row_timeout_seconds": 30, "init_timeout_seconds": 120,
"max_executor_processes_per_worker": 2}`,
),
{ column: "embedding" },
]);
});
it("surfaces JSON server errors from remote table operations", async () => {
await withMockDatabase(
(req, res) => {
+11
View File
@@ -5590,6 +5590,17 @@ describe("computed columns", () => {
expect(rows.map((r) => r.doubled).sort()).toEqual([2, 4]);
});
it("rejects Enterprise refresh options on local tables", async () => {
const db = await connect(tmpDir.name);
const table = await db.createTable("refresh_options_local", [{ x: 1 }]);
await expect(table.refreshColumn("x", { where: "x > 0" })).rejects.toThrow(
"where requires a LanceDB Enterprise refresh",
);
await expect(
table.refreshColumnAsync("x", { errorPolicy: "skip" }),
).rejects.toThrow("error_policy requires a LanceDB Enterprise refresh");
});
it("records Function errors only on remote tables", async () => {
const db = await connect(tmpDir.name);
const table = await db.createTable("errors_local", [{ x: 1 }]);