fix: use SQL for all remote materialized view APIs (#4673)

Remote materialized-view APIs now use SQL for the full lifecycle. Create
and refresh already submit SQL; this change moves describe, paginated
listing, and drop onto `DESCRIBE MATERIALIZED VIEW`, `SHOW MATERIALIZED
VIEWS`, and `DROP MATERIALIZED VIEW`.

Async create, refresh, and drop return the existing Job handle with
`job_type = "query"`. The handle has one `id`: the connection-scoped SQL
query ID, also accepted by `describe_query`. Submission starts the
query, the job drains its result once, and repeated waits reuse the
completed result. Synchronous drop waits for catalog removal; physical
cleanup proceeds in the background.

Listing uses namespace-scoped keyset pagination with opaque resume
tokens. Streamed and deferred SQL responses share the same path. Adds
transport coverage and updates Python/TypeScript documentation for
query-backed drop jobs.

---------

Co-authored-by: Xuanwo <github@xuanwo.io>
This commit is contained in:
Jack Ye
2026-10-28 04:37:00 +08:00
committed by GitHub
co-authored by Xuanwo
parent f6a348ac5b
commit 45dbf5eff5
13 changed files with 420 additions and 375 deletions
+3 -25
View File
@@ -481,23 +481,6 @@ describe("remote connection", () => {
expect(insertRequests).toBe(2);
});
it("lists materialized views through the namespace route", async () => {
await withMockDatabase(
(req, res) => {
expect(req.method).toBe("GET");
expect(req.url).toBe("/v1/namespace/$/materialized_view/list");
res
.writeHead(200, { "content-type": "application/json" })
.end(JSON.stringify({ views: ["daily_sales"] }));
},
async (db) => {
expect(await collect(db.listMaterializedViews())).toEqual([
"daily_sales",
]);
},
);
});
it("creates a view and decodes the schema it resolved to", async () => {
await withMockDatabase(
(req, res) => {
@@ -1725,14 +1708,13 @@ async function collect<T>(items: AsyncIterable<T>): Promise<T[]> {
}
describe("lazy resource listings", () => {
for (const kind of ["views", "materializedViews", "jobs"] as const) {
for (const kind of ["views", "jobs"] as const) {
const listing = (
db: Connection,
options: { pageToken?: string; pageLimit?: number } = {},
) => {
if (kind === "jobs") return db.listJobs(options);
if (kind === "views") return db.listViews(["team"], options);
return db.listMaterializedViews(options);
return db.listViews(["team"], options);
};
const page = (names: string[], token?: string) => {
const items =
@@ -1807,11 +1789,7 @@ describe("lazy resource listings", () => {
limit: 2,
});
} else {
expect(url.pathname).toBe(
kind === "views"
? "/v1/namespace/team/view/list"
: "/v1/namespace/$/materialized_view/list",
);
expect(url.pathname).toBe("/v1/namespace/team/view/list");
expect(url.searchParams.get("page_token")).toBe(token);
expect(url.searchParams.get("limit")).toBe("2");
}
+5 -4
View File
@@ -308,8 +308,8 @@ export abstract class Connection {
/**
* Drop the materialized view named `name`.
*
* The view may become unavailable before physical cleanup finishes. Use
* {@link dropMaterializedViewAsync} to retain and wait for the cleanup job.
* Remote drops wait for the SQL command to remove the catalog binding.
* Physical cleanup continues in the background.
*
* Rejects a table that exists but is not a materialized view.
*/
@@ -319,8 +319,9 @@ export abstract class Connection {
): Promise<void>;
/**
* Start dropping the materialized view named `name` and return its cleanup
* job without waiting for completion.
* Start dropping the materialized view named `name` and return its job.
* Remote jobs have type `query` and use the SQL query ID as `id`. Waiting
* confirms catalog removal; physical cleanup continues in the background.
*
* Rejects a table that exists but is not a materialized view.
*/