feat: cascade option on materialized view refresh

A view over a view could only be brought fully up to date by refreshing
each view in the lineage by hand, in order. `cascade` on the refresh
builder, Python `refresh`/`refresh_async` and Node `refresh` does that in
one call: every upstream view is refreshed furthest first, each reading
the version the one before it left, then the named view. `full` applies
to the named view only.

Locally the lineage is walked through each view's source, and each hop is
refreshed against the incarnation read in that walk. Remote tables send
`cascade` in the refresh request, only when set, so older servers that
reject unknown fields are unaffected by plain refreshes.
This commit is contained in:
Wyatt Alt
2026-09-24 19:09:12 +00:00
parent 604b1b583b
commit d96151c385
13 changed files with 297 additions and 33 deletions
+17
View File
@@ -112,6 +112,23 @@ describe("materialized views", () => {
expect((await view.refresh()).mode).toBe("no_op");
});
it("cascades a refresh through upstream views", async () => {
await db.createMaterializedView("adults", "people", {
where: "age >= 18",
withNoData: true,
});
const seniors = await db.createMaterializedView("seniors", "adults", {
where: "age >= 65",
withNoData: true,
});
expect(Number((await seniors.refresh()).rowsWritten)).toBe(0);
expect(Number((await seniors.refresh({ cascade: true })).rowsWritten)).toBe(
1,
);
expect(await (await db.openTable("adults")).countRows()).toBe(2);
});
it("lists views and rejects non-views", async () => {
await db.createMaterializedView("adults", "people", {
where: "age >= 18",