@rocicorp/zero 1.3.0-canary.3 → 1.4.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/out/analyze-query/src/analyze-cli.d.ts +24 -0
  2. package/out/analyze-query/src/analyze-cli.d.ts.map +1 -0
  3. package/out/analyze-query/src/analyze-cli.js +279 -0
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -0
  5. package/out/analyze-query/src/bin-analyze.js +6 -6
  6. package/out/analyze-query/src/bin-transform.js +2 -2
  7. package/out/ast-to-zql/src/bin.js +1 -1
  8. package/out/z2s/src/compiler.d.ts.map +1 -1
  9. package/out/z2s/src/compiler.js +4 -1
  10. package/out/z2s/src/compiler.js.map +1 -1
  11. package/out/z2s/src/sql.d.ts.map +1 -1
  12. package/out/z2s/src/sql.js +1 -0
  13. package/out/z2s/src/sql.js.map +1 -1
  14. package/out/zero/package.js +5 -1
  15. package/out/zero/package.js.map +1 -1
  16. package/out/zero/src/analyze.d.ts +2 -0
  17. package/out/zero/src/analyze.d.ts.map +1 -0
  18. package/out/zero/src/analyze.js +2 -0
  19. package/out/zero-cache/src/server/anonymous-otel-start.js +1 -1
  20. package/out/zero-cache/src/server/change-streamer.js +1 -1
  21. package/out/zero-cache/src/services/analyze.js +1 -1
  22. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  23. package/out/zero-cache/src/services/change-source/pg/change-source.js +17 -21
  24. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +2 -2
  26. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +24 -15
  28. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  29. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +35 -58
  30. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  31. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  32. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts +1 -2
  35. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/schema/published.js +15 -18
  37. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  38. package/out/zero-cache/src/services/change-source/protocol/current/data.js +1 -1
  39. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  40. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  41. package/out/zero-cache/src/services/litestream/commands.js +12 -6
  42. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  43. package/out/zero-cache/src/services/mutagen/mutagen.js +2 -2
  44. package/out/zero-cache/src/services/statz.js +1 -1
  45. package/out/zero-cache/src/services/view-syncer/cvr-store.js +1 -1
  46. package/out/zero-cache/src/services/view-syncer/cvr.js +1 -1
  47. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  48. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +1 -1
  49. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +1 -1
  50. package/out/zero-cache/src/types/websocket-handoff.js +1 -1
  51. package/out/zero-cache/src/workers/syncer.js +1 -1
  52. package/out/zero-client/src/client/inspector/inspector.d.ts +24 -0
  53. package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
  54. package/out/zero-client/src/client/inspector/inspector.js +28 -0
  55. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  56. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +9 -0
  57. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
  58. package/out/zero-client/src/client/inspector/lazy-inspector.js +28 -1
  59. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  60. package/out/zero-client/src/client/version.js +1 -1
  61. package/package.json +5 -1
@@ -0,0 +1,24 @@
1
+ import '../../shared/src/dotenv.ts';
2
+ import type { Schema } from '../../zero-types/src/schema.ts';
3
+ export type AnalyzeCLIOptions = {
4
+ schema: Schema;
5
+ /** Defaults to `process.argv.slice(2)`. */
6
+ argv?: readonly string[];
7
+ };
8
+ /**
9
+ * Entry point for a user's `cli.ts`. Parses argv, connects to a remote
10
+ * zero-cache by standing up an in-process Zero client (in-memory storage,
11
+ * no subscriptions), calls the inspector's `analyze-query` RPC, and
12
+ * renders the result. Intended to be called as:
13
+ *
14
+ * ```ts
15
+ * import {schema} from './schema.ts';
16
+ * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';
17
+ * await runAnalyzeCLI({schema});
18
+ * ```
19
+ *
20
+ * @experimental This API is in progress and may change without notice.
21
+ * Exits the process with code 1 on error.
22
+ */
23
+ export declare function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void>;
24
+ //# sourceMappingURL=analyze-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-cli.d.ts","sourceRoot":"","sources":["../../../../analyze-query/src/analyze-cli.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAC;AAYpC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAK3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAiGF;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+F1E"}
@@ -0,0 +1,279 @@
1
+ import { valita_exports } from "../../shared/src/valita.js";
2
+ import { createBuilder } from "../../zql/src/query/create-builder.js";
3
+ import { Zero } from "../../zero-client/src/client/zero.js";
4
+ import "../../shared/src/dotenv.js";
5
+ import { logLevel, logOptions } from "../../otel/src/log-options.js";
6
+ import { colorConsole } from "../../shared/src/logging.js";
7
+ import { parseOptions } from "../../shared/src/options.js";
8
+ import { ZERO_ENV_VAR_PREFIX } from "../../zero-cache/src/config/zero-config.js";
9
+ import { styleText } from "node:util";
10
+ import { WebSocket as WebSocket$1 } from "ws";
11
+ //#region ../analyze-query/src/analyze-cli.ts
12
+ var options = {
13
+ zeroCacheURL: {
14
+ type: valita_exports.string().optional(),
15
+ desc: ["URL of the remote zero-cache to analyze against.", "Accepts http(s):// or ws(s):// (ws(s) is the transport actually used)."]
16
+ },
17
+ adminPassword: {
18
+ type: valita_exports.string().optional(),
19
+ desc: ["Admin password for zero-cache.", "Required when the server is configured with one; ignored in dev mode."]
20
+ },
21
+ authToken: {
22
+ type: valita_exports.string().optional(),
23
+ desc: ["Raw JWT forwarded to zero-cache.", "Used server-side to fill permission variables for the query."]
24
+ },
25
+ cookie: {
26
+ type: valita_exports.string().optional(),
27
+ desc: [
28
+ "Cookie header value sent on the WebSocket upgrade request,",
29
+ "e.g. `session=abc; foo=bar`. Use this when zero-cache is behind",
30
+ "a proxy that resolves auth via cookies. Merged with --headers-json",
31
+ "(--cookie wins on conflict)."
32
+ ]
33
+ },
34
+ headersJson: {
35
+ type: valita_exports.string().optional(),
36
+ desc: [
37
+ "JSON object of arbitrary headers to send on the WebSocket upgrade",
38
+ "request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth",
39
+ "schemes; prefer --auth-token or --cookie when possible."
40
+ ]
41
+ },
42
+ userId: {
43
+ type: valita_exports.string().optional(),
44
+ desc: ["Optional userID to report to zero-cache.", "Has no functional effect on analysis; defaults to \"analyze-cli\"."]
45
+ },
46
+ ast: {
47
+ type: valita_exports.string().optional(),
48
+ desc: ["JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.", "The AST is sent to the server verbatim — provide it in server (post-mapping) form."]
49
+ },
50
+ query: {
51
+ type: valita_exports.string().optional(),
52
+ desc: ["ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.", "Evaluated against the schema you pass to runAnalyzeCLI."]
53
+ },
54
+ queryName: {
55
+ type: valita_exports.string().optional(),
56
+ desc: ["Name of a server-registered custom (named) query.", "The server resolves the name + args via its registered query handler."]
57
+ },
58
+ queryArgs: {
59
+ type: valita_exports.string().optional(),
60
+ desc: ["JSON-encoded array of arguments for --query-name. Defaults to `[]`."]
61
+ },
62
+ outputVendedRows: {
63
+ type: valita_exports.boolean().default(false),
64
+ desc: ["Include the rows read from the replica to execute the query.", "Each row appears once per read."]
65
+ },
66
+ outputSyncedRows: {
67
+ type: valita_exports.boolean().default(false),
68
+ desc: ["Include the rows that would be synced to the client."]
69
+ },
70
+ log: {
71
+ ...logOptions,
72
+ level: logLevel.default("error")
73
+ }
74
+ };
75
+ /**
76
+ * Entry point for a user's `cli.ts`. Parses argv, connects to a remote
77
+ * zero-cache by standing up an in-process Zero client (in-memory storage,
78
+ * no subscriptions), calls the inspector's `analyze-query` RPC, and
79
+ * renders the result. Intended to be called as:
80
+ *
81
+ * ```ts
82
+ * import {schema} from './schema.ts';
83
+ * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';
84
+ * await runAnalyzeCLI({schema});
85
+ * ```
86
+ *
87
+ * @experimental This API is in progress and may change without notice.
88
+ * Exits the process with code 1 on error.
89
+ */
90
+ async function runAnalyzeCLI(opts) {
91
+ const config = parseOptions(options, {
92
+ argv: (opts.argv ?? process.argv.slice(2)).map((s) => s.replaceAll("\n", " ")),
93
+ envNamePrefix: ZERO_ENV_VAR_PREFIX,
94
+ description: [{
95
+ header: "analyze-query (remote)",
96
+ content: `Analyze a ZQL query against a remote zero-cache.
97
+
98
+ Connects to zero-cache's inspector protocol and reports the server-observed
99
+ row scans, SQLite query plans, and timings.`
100
+ }, {
101
+ header: "Examples",
102
+ content: ` tsx cli.ts --zero-cache-url=https://zero.example.com \\
103
+ --admin-password="$ZERO_ADMIN_PASSWORD" \\
104
+ --query='issue.related("comments").limit(10)'
105
+
106
+ tsx cli.ts --zero-cache-url=http://localhost:4848 \\
107
+ --ast='\\{"table": "issue", "limit": 5\\}'
108
+
109
+ tsx cli.ts --zero-cache-url=http://localhost:4848 \\
110
+ --query-name=issueList --query-args='[]'`
111
+ }]
112
+ });
113
+ if (!config.zeroCacheURL) {
114
+ colorConsole.error("--zero-cache-url is required. See --help for usage.");
115
+ process.exit(1);
116
+ }
117
+ const plan = buildQueryPlan(config);
118
+ const handshakeHeaders = resolveHandshakeHeaders(config);
119
+ if (Object.keys(handshakeHeaders).length > 0) installWebSocketHeaderShim(handshakeHeaders);
120
+ globalThis.TESTING ??= false;
121
+ const z = new Zero({
122
+ schema: opts.schema,
123
+ server: config.zeroCacheURL,
124
+ auth: config.authToken,
125
+ userID: config.userId ?? "analyze-cli",
126
+ kvStore: "mem",
127
+ logLevel: config.log.level
128
+ });
129
+ let result;
130
+ try {
131
+ if (!await z.inspector.authenticate(config.adminPassword ?? "")) throw new Error("admin password rejected (or --admin-password is required)");
132
+ const rpcOptions = {
133
+ vendedRows: config.outputVendedRows,
134
+ syncedRows: config.outputSyncedRows
135
+ };
136
+ if (plan.kind === "ast") result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);
137
+ else if (plan.kind === "named") result = await z.inspector.analyzeNamedQuery(plan.name, plan.args, rpcOptions);
138
+ else {
139
+ const built = buildZqlQuery(plan.text, createBuilder(opts.schema));
140
+ result = await z.inspector.analyzeQuery(built, rpcOptions);
141
+ }
142
+ } catch (e) {
143
+ colorConsole.error(e instanceof Error ? e.message : String(e));
144
+ await z.close().catch(() => {});
145
+ process.exit(1);
146
+ }
147
+ renderResult(result, {
148
+ outputSyncedRows: config.outputSyncedRows,
149
+ outputVendedRows: config.outputVendedRows
150
+ });
151
+ await z.close();
152
+ }
153
+ function buildQueryPlan(config) {
154
+ const selectors = [
155
+ config.ast !== void 0 && "ast",
156
+ config.query !== void 0 && "query",
157
+ config.queryName !== void 0 && "queryName"
158
+ ].filter(Boolean);
159
+ if (selectors.length === 0) {
160
+ colorConsole.error("Exactly one of --ast / --query / --query-name is required.");
161
+ process.exit(1);
162
+ }
163
+ if (selectors.length > 1) {
164
+ colorConsole.error(`Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(", ")}`);
165
+ process.exit(1);
166
+ }
167
+ if (config.ast !== void 0) return {
168
+ kind: "ast",
169
+ ast: JSON.parse(config.ast)
170
+ };
171
+ if (config.query !== void 0) return {
172
+ kind: "zql",
173
+ text: config.query
174
+ };
175
+ const args = config.queryArgs ? JSON.parse(config.queryArgs) : [];
176
+ return {
177
+ kind: "named",
178
+ name: config.queryName,
179
+ args
180
+ };
181
+ }
182
+ function buildZqlQuery(queryString, builder) {
183
+ return new Function("builder", `return builder.${queryString};`)(builder);
184
+ }
185
+ function resolveHandshakeHeaders(config) {
186
+ const headers = {};
187
+ if (config.headersJson !== void 0) {
188
+ let parsed;
189
+ try {
190
+ parsed = JSON.parse(config.headersJson);
191
+ } catch (e) {
192
+ colorConsole.error(`--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
193
+ process.exit(1);
194
+ }
195
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
196
+ colorConsole.error("--headers-json must be a JSON object.");
197
+ process.exit(1);
198
+ }
199
+ for (const [k, val] of Object.entries(parsed)) {
200
+ if (typeof val !== "string") {
201
+ colorConsole.error(`--headers-json values must be strings; got ${typeof val} for "${k}".`);
202
+ process.exit(1);
203
+ }
204
+ headers[k] = val;
205
+ }
206
+ }
207
+ if (config.cookie !== void 0) headers.cookie = config.cookie;
208
+ return headers;
209
+ }
210
+ function installWebSocketHeaderShim(headers) {
211
+ class HeaderInjectingWebSocket extends WebSocket$1 {
212
+ constructor(url, protocols) {
213
+ super(url, protocols, { headers });
214
+ }
215
+ }
216
+ globalThis.WebSocket = HeaderInjectingWebSocket;
217
+ }
218
+ function renderResult(result, opts) {
219
+ if (opts.outputSyncedRows) {
220
+ colorConsole.log(styleText(["blue", "bold"], "=== Synced Rows: ===\n"));
221
+ for (const [table, rows] of Object.entries(result.syncedRows ?? {})) colorConsole.log(styleText("bold", table + ":"), rows);
222
+ }
223
+ colorConsole.log(styleText(["blue", "bold"], "=== Query Stats: ===\n"));
224
+ colorConsole.log(styleText("bold", "total synced rows:"), result.syncedRowCount);
225
+ const readRowCountsByQuery = result.readRowCountsByQuery ?? {};
226
+ let totalRowsRead = 0;
227
+ for (const table of Object.keys(readRowCountsByQuery).sort()) {
228
+ const counts = readRowCountsByQuery[table];
229
+ for (const n of Object.values(counts)) totalRowsRead += n;
230
+ colorConsole.log(styleText("bold", `${table} vended:`), counts);
231
+ }
232
+ colorConsole.log(styleText("bold", "Rows Read (into JS):"), colorRowsConsidered(totalRowsRead));
233
+ const duration = result.elapsed ?? result.end - result.start;
234
+ colorConsole.log(styleText("bold", "time:"), colorTime(duration), "ms");
235
+ if (opts.outputVendedRows) {
236
+ colorConsole.log(styleText(["blue", "bold"], "=== JS Row Scan Values: ===\n"));
237
+ for (const [table, rows] of Object.entries(result.readRows ?? {})) colorConsole.log(styleText("bold", `${table}:`), rows);
238
+ }
239
+ colorConsole.log(styleText(["blue", "bold"], "\n=== Rows Scanned (by SQLite): ===\n"));
240
+ const dbScansByQuery = result.dbScansByQuery ?? {};
241
+ let totalNVisit = 0;
242
+ for (const [table, queries] of Object.entries(dbScansByQuery)) {
243
+ colorConsole.log(styleText("bold", `${table}:`), queries);
244
+ for (const count of Object.values(queries)) totalNVisit += count;
245
+ }
246
+ colorConsole.log(styleText("bold", "total rows scanned:"), colorRowsConsidered(totalNVisit));
247
+ colorConsole.log(styleText(["blue", "bold"], "\n\n=== Query Plans: ===\n"));
248
+ const plans = result.sqlitePlans ?? {};
249
+ for (const [query, plan] of Object.entries(plans)) {
250
+ colorConsole.log(styleText("bold", "query"), query);
251
+ colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join("\n"));
252
+ colorConsole.log("\n");
253
+ }
254
+ if (result.warnings.length > 0) {
255
+ colorConsole.log(styleText(["yellow", "bold"], "=== Warnings: ===\n"));
256
+ for (const w of result.warnings) colorConsole.log(styleText("yellow", w));
257
+ }
258
+ }
259
+ function colorTime(duration) {
260
+ if (duration < 100) return styleText("green", duration.toFixed(2) + "ms");
261
+ else if (duration < 1e3) return styleText("yellow", duration.toFixed(2) + "ms");
262
+ return styleText("red", duration.toFixed(2) + "ms");
263
+ }
264
+ function colorRowsConsidered(n) {
265
+ if (n < 1e3) return styleText("green", n.toString());
266
+ else if (n < 1e4) return styleText("yellow", n.toString());
267
+ return styleText("red", n.toString());
268
+ }
269
+ function colorPlanRow(row, i) {
270
+ if (row.includes("SCAN")) {
271
+ if (i === 0) return styleText("yellow", row);
272
+ return styleText("red", row);
273
+ }
274
+ return styleText("green", row);
275
+ }
276
+ //#endregion
277
+ export { runAnalyzeCLI };
278
+
279
+ //# sourceMappingURL=analyze-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {styleText} from 'node:util';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[];\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Has no functional effect on analysis; defaults to \"analyze-cli\".',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * @experimental This API is in progress and may change without notice.\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` tsx cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under tsx there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId ?? 'analyze-cli',\n kvStore: 'mem',\n logLevel: config.log.level,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n }\n\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n }\n\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":"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"}
@@ -6,25 +6,25 @@ import { asQueryInternals } from "../../zql/src/query/query-internals.js";
6
6
  import { newQuery } from "../../zql/src/query/query-impl.js";
7
7
  import { clientToServer } from "../../zero-schema/src/name-mapper.js";
8
8
  import { QueryDelegateBase } from "../../zql/src/query/query-delegate-base.js";
9
- import { Database } from "../../zqlite/src/db.js";
10
- import { TableSource } from "../../zqlite/src/table-source.js";
11
9
  import "../../shared/src/dotenv.js";
12
- import { astToZQL } from "../../ast-to-zql/src/ast-to-zql.js";
13
- import { formatOutput } from "../../ast-to-zql/src/format.js";
14
10
  import { logLevel, logOptions } from "../../otel/src/log-options.js";
15
- import { testLogConfig } from "../../otel/src/test-log-config.js";
16
11
  import { colorConsole, createLogContext } from "../../shared/src/logging.js";
17
12
  import { parseOptions } from "../../shared/src/options.js";
18
13
  import { Debug, runtimeDebugFlags } from "../../zql/src/builder/debug-delegate.js";
19
14
  import { getShardID, upstreamSchema } from "../../zero-cache/src/types/shards.js";
20
15
  import { ZERO_ENV_VAR_PREFIX, appOptions, shardOptions, zeroOptions } from "../../zero-cache/src/config/zero-config.js";
16
+ import { Database } from "../../zqlite/src/db.js";
17
+ import { TableSource } from "../../zqlite/src/table-source.js";
18
+ import { astToZQL } from "../../ast-to-zql/src/ast-to-zql.js";
19
+ import { formatOutput } from "../../ast-to-zql/src/format.js";
20
+ import { testLogConfig } from "../../otel/src/test-log-config.js";
21
21
  import { computeZqlSpecs, mustGetTableSpec } from "../../zero-cache/src/db/lite-tables.js";
22
22
  import { deployPermissionsOptions, loadSchemaAndPermissions } from "../../zero-cache/src/scripts/permissions.js";
23
23
  import { runAst } from "../../zero-cache/src/services/run-ast.js";
24
24
  import { pgClient } from "../../zero-cache/src/types/pg.js";
25
25
  import { explainQueries } from "./explain-queries.js";
26
- import fs from "node:fs";
27
26
  import { styleText } from "node:util";
27
+ import fs from "node:fs";
28
28
  //#region ../analyze-query/src/bin-analyze.ts
29
29
  var cfg = parseOptions({
30
30
  schema: deployPermissionsOptions.schema,
@@ -1,11 +1,11 @@
1
1
  import { valita_exports } from "../../shared/src/valita.js";
2
2
  import { must } from "../../shared/src/must.js";
3
3
  import "../../shared/src/dotenv.js";
4
- import { astToZQL } from "../../ast-to-zql/src/ast-to-zql.js";
5
- import { formatOutput } from "../../ast-to-zql/src/format.js";
6
4
  import { parseOptions } from "../../shared/src/options.js";
7
5
  import { getShardID, upstreamSchema } from "../../zero-cache/src/types/shards.js";
8
6
  import { ZERO_ENV_VAR_PREFIX, appOptions, shardOptions } from "../../zero-cache/src/config/zero-config.js";
7
+ import { astToZQL } from "../../ast-to-zql/src/ast-to-zql.js";
8
+ import { formatOutput } from "../../ast-to-zql/src/format.js";
9
9
  import { loadSchemaAndPermissions } from "../../zero-cache/src/scripts/permissions.js";
10
10
  import { transformAndHashQuery } from "../../zero-cache/src/auth/read-authorizer.js";
11
11
  import { pgClient } from "../../zero-cache/src/types/pg.js";
@@ -1,9 +1,9 @@
1
1
  import { valita_exports } from "../../shared/src/valita.js";
2
2
  import { mapAST } from "../../zero-protocol/src/ast.js";
3
3
  import { serverToClient } from "../../zero-schema/src/name-mapper.js";
4
+ import { parseOptions } from "../../shared/src/options.js";
4
5
  import { astToZQL } from "./ast-to-zql.js";
5
6
  import { formatOutput } from "./format.js";
6
- import { parseOptions } from "../../shared/src/options.js";
7
7
  import { loadSchemaAndPermissions } from "../../zero-cache/src/scripts/permissions.js";
8
8
  import process from "node:process";
9
9
  import { readFile } from "node:fs/promises";
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../../../z2s/src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAQ7C,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD,OAAO,KAAK,EACV,GAAG,EAEH,kBAAkB,EAElB,WAAW,EAEX,QAAQ,EACR,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAC3D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AAUtD,KAAK,KAAK,GAAG;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,YAAY,CAAC;IAErB,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAQF,wBAAgB,OAAO,CACrB,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,GAAG,EACR,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAgBV;AA4CD,wBAAgB,KAAK,CACnB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAC5B,QAAQ,CAWV;AAUD,wBAAgB,OAAO,CACrB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,QAAQ,GAAG,SAAS,EAC7B,KAAK,EAAE,KAAK,GACX,QAAQ,CAoBV;AA4LD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,SAAS,eAAe,EAAE,EACxC,cAAc,EAAE,SAAS,MAAM,EAAE,GAChC,CAAC,UAAU,EAAE,KAAK,KAAK,QAAQ,CAiBjC;AAED,wBAAgB,MAAM,CACpB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,QAAQ,CAgCV;AAED,wBAAgB,GAAG,CACjB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,QAAQ,CAMV;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,QAAQ,CAIV;AAmGD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,kBAAkB,GAC/B;IACD,IAAI,EAAE,QAAQ,CAAC;IACf,mBAAmB,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;CAC/D,CA2BA;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,kBAAkB,GAC/B;IACD;QACE,KAAK,EAAE,KAAK,CAAC;QACb,WAAW,EAAE,WAAW,CAAC;QACzB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3B;IACD;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC;CACpE,CAsBA;AAwED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAMhE"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../../../z2s/src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAQ7C,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD,OAAO,KAAK,EACV,GAAG,EAEH,kBAAkB,EAElB,WAAW,EAEX,QAAQ,EACR,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAC3D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AAUtD,KAAK,KAAK,GAAG;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,YAAY,CAAC;IAErB,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAQF,wBAAgB,OAAO,CACrB,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,GAAG,EACR,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAgBV;AA4CD,wBAAgB,KAAK,CACnB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAC5B,QAAQ,CAWV;AAUD,wBAAgB,OAAO,CACrB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,QAAQ,GAAG,SAAS,EAC7B,KAAK,EAAE,KAAK,GACX,QAAQ,CAoBV;AA4LD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,SAAS,eAAe,EAAE,EACxC,cAAc,EAAE,SAAS,MAAM,EAAE,GAChC,CAAC,UAAU,EAAE,KAAK,KAAK,QAAQ,CAiBjC;AAED,wBAAgB,MAAM,CACpB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,QAAQ,CAgCV;AAED,wBAAgB,GAAG,CACjB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,QAAQ,CAMV;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,QAAQ,CAIV;AAmGD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,kBAAkB,GAC/B;IACD,IAAI,EAAE,QAAQ,CAAC;IACf,mBAAmB,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;CAC/D,CA2BA;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,kBAAkB,GAC/B;IACD;QACE,KAAK,EAAE,KAAK,CAAC;QACb,WAAW,EAAE,WAAW,CAAC;QACzB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3B;IACD;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC;CACpE,CAsBA;AAyED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAMhE"}
@@ -265,7 +265,10 @@ function selectIdent(server, column) {
265
265
  case "timestamp without time zone":
266
266
  case "timestamp with time zone": {
267
267
  const toMs = (epochExpr) => needsNormalization ? sql`((${epochExpr})::bigint + 86400000) % 86400000` : epochExpr;
268
- if (serverColumnSchema.isArray) return sql`ARRAY(SELECT ${toMs(sql`EXTRACT(EPOCH FROM unnest(${colIdent(server, column)})) * 1000`)}) as ${sql.ident(column.zql)}`;
268
+ if (serverColumnSchema.isArray) {
269
+ const col = colIdent(server, column);
270
+ return sql`CASE WHEN ${col} IS NULL THEN NULL ELSE ARRAY(SELECT ${toMs(sql`EXTRACT(EPOCH FROM unnest(${col})) * 1000`)}) END as ${sql.ident(column.zql)}`;
271
+ }
269
272
  return sql`${toMs(sql`EXTRACT(EPOCH FROM ${colIdent(server, column)}) * 1000`)} as ${sql.ident(column.zql)}`;
270
273
  }
271
274
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.js","names":[],"sources":["../../../../z2s/src/compiler.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {last, zip} from '../../shared/src/arrays.ts';\nimport {assert, unreachable} from '../../shared/src/asserts.ts';\nimport {\n parse as parseBigIntJson,\n type JSONValue as BigIntJSONValue,\n} from '../../shared/src/bigint-json.ts';\nimport {hasOwn} from '../../shared/src/has-own.ts';\nimport {type JSONValue} from '../../shared/src/json.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n CorrelatedSubquery,\n CorrelatedSubqueryCondition,\n Correlation,\n LiteralReference,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport {\n clientToServer,\n type NameMapper,\n} from '../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {ServerSchema} from '../../zero-types/src/server-schema.ts';\nimport type {Format} from '../../zql/src/ivm/view.ts';\nimport {completeOrdering} from '../../zql/src/query/complete-ordering.ts';\nimport {\n sql,\n sqlConvertColumnArg,\n sqlConvertPluralLiteralArg,\n sqlConvertSingularLiteralArg,\n type PluralLiteralType,\n} from './sql.ts';\n\ntype Table = {\n zql: string;\n alias: string;\n};\n\ntype QualifiedColumn = {\n table: Table;\n zql: string;\n};\n\ntype ServerSpec = {\n schema: ServerSchema;\n // maps zql names to server names\n mapper: NameMapper;\n};\n\nexport type Spec = {\n server: ServerSpec;\n zql: Schema['tables'];\n aliasCount: number;\n};\n\nconst ZQL_RESULT_KEY = 'zql_result';\nconst ZQL_RESULT_KEY_IDENT = sql.ident(ZQL_RESULT_KEY);\n\nconst ZQL_RESULT_TABLE_KEY = 'zql_root';\nconst ZQL_RESULT_TABLE_IDENT = sql.ident(ZQL_RESULT_TABLE_KEY);\n\nexport function compile(\n serverSchema: ServerSchema,\n zqlSchema: Schema,\n ast: AST,\n format?: Format,\n): SQLQuery {\n ast = completeOrdering(\n ast,\n tableName => zqlSchema.tables[tableName].primaryKey,\n );\n const spec: Spec = {\n aliasCount: 0,\n server: {\n schema: serverSchema,\n mapper: clientToServer(zqlSchema.tables),\n },\n zql: zqlSchema.tables,\n };\n return sql`SELECT \n ${toJSON(ZQL_RESULT_TABLE_KEY, format?.singular)}::text AS ${ZQL_RESULT_KEY_IDENT}\n FROM (${select(spec, ast, format)}) ${ZQL_RESULT_TABLE_IDENT}`;\n}\n\nfunction select(\n spec: Spec,\n ast: AST,\n format: Format | undefined,\n correlate?: (childTable: Table) => SQLQuery,\n): SQLQuery {\n const table = makeTable(spec, ast.table);\n const selectionSet = related(spec, ast.related ?? [], format, table);\n const tableSchema = spec.zql[ast.table];\n const usedAliases = new Set<string>(\n ast.related?.map(r => r.subquery.alias ?? ''),\n );\n for (const column of Object.keys(tableSchema.columns)) {\n if (!usedAliases.has(column)) {\n selectionSet.push(\n selectIdent(spec.server, {\n table,\n zql: column,\n }),\n );\n }\n }\n\n let appliedWhere = false;\n function maybeWhere(test: unknown | undefined) {\n if (!test) {\n return sql``;\n }\n\n const ret = appliedWhere ? sql`AND` : sql`WHERE`;\n appliedWhere = true;\n return ret;\n }\n\n return sql`SELECT ${sql.join(selectionSet, ',')}\n FROM ${fromIdent(spec.server, table)}\n ${maybeWhere(ast.where)} ${where(spec, ast.where, table)}\n ${maybeWhere(correlate)} ${correlate ? correlate(table) : sql``}\n ${orderBy(spec, ast.orderBy, table)}\n ${limit(ast.limit, format?.singular)}`;\n}\n\nexport function limit(\n limit: number | undefined,\n singular: boolean | undefined,\n): SQLQuery {\n if (limit === 0) {\n return sql`LIMIT 0`;\n }\n if (singular) {\n return sql`LIMIT 1`;\n }\n if (limit === undefined) {\n return sql``;\n }\n return sql`LIMIT ${sqlConvertSingularLiteralArg(limit)}`;\n}\n\nfunction makeTable(spec: Spec, zql: string, alias?: string): Table {\n alias = alias ?? zql + '_' + spec.aliasCount++;\n return {\n zql,\n alias,\n };\n}\n\nexport function orderBy(\n spec: Spec,\n orderBy: Ordering | undefined,\n table: Table,\n): SQLQuery {\n if (!orderBy) {\n return sql``;\n }\n return sql`ORDER BY ${sql.join(\n orderBy.map(([col, dir]) =>\n dir === 'asc'\n ? // Oh postgres. The table must be referred to by client name but the column by server name.\n // E.g., `SELECT server_col as client_col FROM server_table as client_table ORDER BY client_Table.server_col`\n sql`${colIdent(spec.server, {\n table,\n zql: col,\n })} ASC NULLS FIRST`\n : sql`${colIdent(spec.server, {\n table,\n zql: col,\n })} DESC NULLS LAST`,\n ),\n ', ',\n )}`;\n}\n\nfunction related(\n spec: Spec,\n relationships: readonly CorrelatedSubquery[],\n format: Format | undefined,\n parentTable: Table,\n): SQLQuery[] {\n return relationships.map(relationship =>\n relationshipSubquery(\n spec,\n relationship,\n format?.relationships[must(relationship.subquery.alias)],\n parentTable,\n ),\n );\n}\n\nfunction relationshipSubquery(\n spec: Spec,\n relationship: CorrelatedSubquery,\n format: Format | undefined,\n parentTable: Table,\n): SQLQuery {\n const innerAlias = `inner_${relationship.subquery.alias}`;\n if (relationship.hidden) {\n const {join, participatingTables} = makeJunctionJoin(spec, relationship);\n const lastTable = must(last(participatingTables)).table;\n\n assert(\n relationship.subquery.related,\n 'hidden relationship must be a junction',\n );\n const nestedAst = relationship.subquery.related[0].subquery;\n const selectionSet = related(\n spec,\n nestedAst.related ?? [],\n format,\n lastTable,\n );\n const tableSchema = spec.zql[nestedAst.table];\n for (const column of Object.keys(tableSchema.columns)) {\n selectionSet.push(\n selectIdent(spec.server, {\n table: lastTable,\n zql: column,\n }),\n );\n }\n\n return sql`(\n SELECT ${toJSON(innerAlias, format?.singular)} FROM (SELECT ${sql.join(\n selectionSet,\n ',',\n )} FROM ${join} WHERE (${makeCorrelator(\n spec,\n relationship.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n relationship.correlation.childField,\n )(participatingTables[0].table)}) ${\n nestedAst.where\n ? sql`AND ${where(spec, nestedAst.where, lastTable)}`\n : sql``\n } ${orderBy(spec, nestedAst.orderBy, lastTable)} ${limit(\n last(participatingTables)?.limit,\n format?.singular,\n )} ) ${sql.ident(innerAlias)}\n ) as ${sql.ident(relationship.subquery.alias)}`;\n }\n\n return sql`(\n SELECT ${toJSON(innerAlias, format?.singular)} FROM (${select(\n spec,\n relationship.subquery,\n format,\n makeCorrelator(\n spec,\n relationship.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n relationship.correlation.childField,\n ),\n )}) ${sql.ident(innerAlias)}\n ) as ${sql.ident(relationship.subquery.alias)}`;\n}\n\nfunction where(\n spec: Spec,\n condition: Condition | undefined,\n table: Table,\n): SQLQuery {\n if (!condition) {\n return sql``;\n }\n\n switch (condition.type) {\n case 'and':\n return sql`(${sql.join(\n condition.conditions.map(c => where(spec, c, table)),\n ' AND ',\n )})`;\n case 'or':\n return sql`(${sql.join(\n condition.conditions.map(c => where(spec, c, table)),\n ' OR ',\n )})`;\n case 'correlatedSubquery':\n if (condition.scalar) {\n return scalarSubquery(spec, condition, table);\n }\n return exists(spec, condition, table);\n case 'simple':\n return simple(spec, condition, table);\n }\n}\n\nfunction exists(\n spec: Spec,\n condition: CorrelatedSubqueryCondition,\n parentTable: Table,\n): SQLQuery {\n switch (condition.op) {\n case 'EXISTS':\n return sql`EXISTS (${select(\n spec,\n condition.related.subquery,\n undefined,\n makeCorrelator(\n spec,\n condition.related.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n condition.related.correlation.childField,\n ),\n )})`;\n case 'NOT EXISTS':\n return sql`NOT EXISTS (${select(\n spec,\n condition.related.subquery,\n undefined,\n makeCorrelator(\n spec,\n condition.related.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n condition.related.correlation.childField,\n ),\n )})`;\n }\n}\n\nfunction scalarSubquery(\n spec: Spec,\n condition: CorrelatedSubqueryCondition,\n parentTable: Table,\n): SQLQuery {\n const parentField = condition.related.correlation.parentField[0];\n const childField = condition.related.correlation.childField[0];\n const subqueryAST = condition.related.subquery;\n\n const parentCol = colIdent(spec.server, {\n table: parentTable,\n zql: parentField,\n });\n\n const subqueryTable = makeTable(spec, subqueryAST.table);\n const childCol = colIdent(spec.server, {\n table: subqueryTable,\n zql: childField,\n });\n\n const op = sql.__dangerous__rawValue(\n condition.op === 'EXISTS' ? '=' : 'IS NOT',\n );\n\n const subqueryWhere = subqueryAST.where\n ? sql`WHERE ${where(spec, subqueryAST.where, subqueryTable)}`\n : sql``;\n const subqueryOrderBy = orderBy(spec, subqueryAST.orderBy, subqueryTable);\n\n return sql`${parentCol} ${op} (SELECT ${childCol} FROM ${fromIdent(spec.server, subqueryTable)} ${subqueryWhere} ${subqueryOrderBy} LIMIT 1)`;\n}\n\nexport function makeCorrelator(\n spec: Spec,\n parentFields: readonly QualifiedColumn[],\n childZqlFields: readonly string[],\n): (childTable: Table) => SQLQuery {\n return (childTable: Table) => {\n const childFields = childZqlFields.map(zqlField => ({\n table: childTable,\n zql: zqlField,\n }));\n return sql.join(\n zip(parentFields, childFields).map(\n ([parentColumn, childColumn]) =>\n sql`${colIdent(spec.server, parentColumn)} = ${colIdent(\n spec.server,\n childColumn,\n )}`,\n ),\n ' AND ',\n );\n };\n}\n\nexport function simple(\n spec: Spec,\n condition: SimpleCondition,\n table: Table,\n): SQLQuery {\n switch (condition.op) {\n case '!=':\n case '<':\n case '<=':\n case '=':\n case '>':\n case '>=':\n case 'ILIKE':\n case 'LIKE':\n case 'NOT ILIKE':\n case 'NOT LIKE':\n return sql`${valueComparison(\n spec,\n condition.left,\n table,\n condition.right,\n false,\n )} ${sql.__dangerous__rawValue(condition.op)} ${valueComparison(\n spec,\n condition.right,\n table,\n condition.left,\n false,\n )}`;\n case 'NOT IN':\n case 'IN':\n return any(spec, condition, table);\n case 'IS':\n case 'IS NOT':\n return distinctFrom(spec, condition, table);\n }\n}\n\nexport function any(\n spec: Spec,\n condition: SimpleCondition,\n table: Table,\n): SQLQuery {\n return sql`${condition.op === 'NOT IN' ? sql`NOT` : sql``}\n (\n ${valueComparison(spec, condition.left, table, condition.right, false)} = ANY \n (${valueComparison(spec, condition.right, table, condition.left, true)})\n )`;\n}\n\nexport function distinctFrom(\n spec: Spec,\n condition: SimpleCondition,\n table: Table,\n): SQLQuery {\n return sql`${valueComparison(spec, condition.left, table, condition.right, false)} ${\n condition.op === 'IS' ? sql`IS NOT DISTINCT FROM` : sql`IS DISTINCT FROM`\n } ${valueComparison(spec, condition.right, table, condition.left, false)}`;\n}\n\nfunction valueComparison(\n spec: Spec,\n valuePos: ValuePosition,\n table: Table,\n otherValuePos: ValuePosition,\n plural: boolean,\n): SQLQuery {\n const valuePosType = valuePos.type;\n switch (valuePosType) {\n case 'column': {\n const qualified: QualifiedColumn = {\n table,\n zql: valuePos.name,\n };\n return colIdent(spec.server, qualified);\n }\n case 'literal':\n return literalValueComparison(\n spec,\n valuePos,\n table,\n otherValuePos,\n plural,\n );\n case 'static':\n throw new Error(\n 'Static parameters must be bound to a value before compiling to SQL',\n );\n default:\n unreachable(valuePosType);\n break;\n }\n}\n\nfunction literalValueComparison(\n spec: Spec,\n valuePos: LiteralReference,\n table: Table,\n otherValuePos: ValuePosition,\n plural: boolean,\n): SQLQuery {\n const otherType = otherValuePos.type;\n switch (otherType) {\n case 'column':\n return sqlConvertColumnArg(\n getServerColumn(spec.server, table, otherValuePos.name),\n valuePos.value,\n plural,\n true,\n );\n case 'literal': {\n assert(\n plural === Array.isArray(valuePos.value),\n 'Expected plural flag to match whether value is an array',\n );\n if (Array.isArray(valuePos.value)) {\n if (valuePos.value.length > 0) {\n // If the array is non-empty base its type on its first\n // element\n return sqlConvertPluralLiteralArg(\n typeof valuePos.value[0] as PluralLiteralType,\n valuePos.value as PluralLiteralType[],\n );\n }\n // If the array is empty, base its type on the other value\n // position's type (as long as the other value position is non-null,\n // cannot have a null[]).\n if (otherValuePos.value !== null) {\n return sqlConvertPluralLiteralArg(\n typeof otherValuePos.value as PluralLiteralType,\n [],\n );\n }\n // If the other value position is null, it can be compared to any\n // type of empty array, chose 'string' arbitrarily.\n return sqlConvertPluralLiteralArg('string', []);\n }\n if (\n typeof valuePos.value === 'string' ||\n typeof valuePos.value === 'number' ||\n typeof valuePos.value === 'boolean'\n ) {\n return sqlConvertSingularLiteralArg(valuePos.value);\n }\n throw new Error(\n `Literal of unexpected type. ${valuePos.value} of type ${typeof valuePos.value}`,\n );\n }\n case 'static':\n throw new Error(\n 'Static parameters must be bound to a value before compiling to SQL',\n );\n default:\n unreachable(otherType);\n }\n}\n\nexport function makeJunctionJoin(\n spec: Spec,\n relationship: CorrelatedSubquery,\n): {\n join: SQLQuery;\n participatingTables: ReturnType<typeof pullTablesForJunction>;\n} {\n const participatingTables = pullTablesForJunction(spec, relationship);\n const joins: SQLQuery[] = [];\n\n for (const {table} of participatingTables) {\n if (joins.length === 0) {\n joins.push(fromIdent(spec.server, table));\n continue;\n }\n joins.push(\n sql` JOIN ${fromIdent(spec.server, table)} ON ${makeCorrelator(\n spec,\n participatingTables[joins.length].correlation.parentField.map(f => ({\n table: participatingTables[joins.length - 1].table,\n zql: f,\n })),\n participatingTables[joins.length].correlation.childField,\n )(participatingTables[joins.length].table)}`,\n );\n }\n\n return {\n join: sql`${sql.join(joins, '')}`,\n participatingTables,\n // lastTable: participatingTables[participatingTables.length - 1].table,\n // lastLimit: participatingTables[participatingTables.length - 1].limit,\n };\n}\n\nexport function pullTablesForJunction(\n spec: Spec,\n relationship: CorrelatedSubquery,\n): [\n {\n table: Table;\n correlation: Correlation;\n limit: number | undefined;\n },\n {table: Table; correlation: Correlation; limit: number | undefined},\n] {\n assert(\n relationship.subquery.related?.length === 1,\n 'Too many related tables for a junction edge',\n );\n const otherRelationship = relationship.subquery.related[0];\n assert(\n !otherRelationship.hidden,\n 'Expected junction edge relationship to not be hidden',\n );\n return [\n {\n table: makeTable(spec, relationship.subquery.table),\n correlation: relationship.correlation,\n limit: relationship.subquery.limit,\n },\n {\n table: makeTable(spec, otherRelationship.subquery.table),\n correlation: otherRelationship.correlation,\n limit: otherRelationship.subquery.limit,\n },\n ];\n}\n\nfunction toJSON(table: string, singular = false): SQLQuery {\n return sql`${\n singular ? sql`` : sql`COALESCE(json_agg`\n }(row_to_json(${sql.ident(table)}))${singular ? sql`` : sql`, '[]'::json)`}`;\n}\n\nfunction selectIdent(server: ServerSpec, column: QualifiedColumn): SQLQuery {\n const serverColumnSchema =\n server.schema[server.mapper.tableName(column.table.zql)][\n server.mapper.columnName(column.table.zql, column.zql)\n ];\n const serverType = serverColumnSchema.type;\n if (!serverColumnSchema.isEnum) {\n let needsNormalization = false;\n switch (serverType) {\n case 'timestamptz':\n // @ts-expect-error Fallthrough intended\n case 'timetz':\n needsNormalization = true;\n // fallthrough\n\n case 'date':\n case 'time':\n case 'time without time zone':\n case 'time with time zone':\n case 'timestamp':\n case 'timestamp without time zone':\n case 'timestamp with time zone': {\n // EXTRACT(EPOCH FROM timetz) can be negative when the UTC offset is\n // positive (e.g. 01:00+02 = 23:00 UTC prev day = -3600s). Wrap with\n // modular arithmetic to normalize to 0..86400000.\n const toMs = (epochExpr: SQLQuery): SQLQuery =>\n needsNormalization\n ? sql`((${epochExpr})::bigint + 86400000) % 86400000`\n : epochExpr;\n\n if (serverColumnSchema.isArray) {\n return sql`ARRAY(SELECT ${toMs(\n sql`EXTRACT(EPOCH FROM unnest(${colIdent(server, column)})) * 1000`,\n )}) as ${sql.ident(column.zql)}`;\n }\n\n return sql`${toMs(\n sql`EXTRACT(EPOCH FROM ${colIdent(server, column)}) * 1000`,\n )} as ${sql.ident(column.zql)}`;\n }\n }\n }\n\n return sql`${colIdent(server, column)} as ${sql.ident(column.zql)}`;\n}\n\nfunction colIdent(server: ServerSpec, column: QualifiedColumn) {\n return sql.ident(\n column.table.alias,\n server.mapper.columnName(column.table.zql, column.zql),\n );\n}\n\nfunction fromIdent(server: ServerSpec, table: Table) {\n return sql`${sql.ident(server.mapper.tableName(table.zql))} AS ${sql.ident(table.alias)}`;\n}\n\nfunction getServerColumn(spec: ServerSpec, table: Table, zqlColumn: string) {\n return spec.schema[spec.mapper.tableName(table.zql)][\n spec.mapper.columnName(table.zql, zqlColumn)\n ];\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nexport function extractZqlResult(pgResult: Array<any>): JSONValue {\n const bigIntJson: BigIntJSONValue = parseBigIntJson(\n pgResult[0][ZQL_RESULT_KEY],\n );\n assertJSONValue(bigIntJson);\n return bigIntJson;\n}\n\nfunction assertJSONValue(v: BigIntJSONValue): asserts v is JSONValue {\n const path = findPathToBigInt(v);\n if (path) {\n throw new Error(`Value exceeds safe Number range. ${path}`);\n }\n}\n\nfunction findPathToBigInt(v: BigIntJSONValue): string | undefined {\n const typeOfV = typeof v;\n switch (typeOfV) {\n case 'bigint':\n return ` = ${v}`;\n case 'object': {\n if (v === null) {\n return;\n }\n if (Array.isArray(v)) {\n for (let i = 0; i < v.length; i++) {\n const path = findPathToBigInt(v[i]);\n if (path) {\n return `[${i}]${path}`;\n }\n }\n return undefined;\n }\n\n const o = v as Record<string, BigIntJSONValue>;\n for (const k in o) {\n if (hasOwn(o, k)) {\n const path = findPathToBigInt(o[k]);\n if (path) {\n return `['${k}']${path}`;\n }\n }\n }\n return undefined;\n }\n case 'number':\n return undefined;\n case 'boolean':\n return undefined;\n default:\n return undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"compiler.js","names":[],"sources":["../../../../z2s/src/compiler.ts"],"sourcesContent":["import type {SQLQuery} from '@databases/sql';\nimport {last, zip} from '../../shared/src/arrays.ts';\nimport {assert, unreachable} from '../../shared/src/asserts.ts';\nimport {\n parse as parseBigIntJson,\n type JSONValue as BigIntJSONValue,\n} from '../../shared/src/bigint-json.ts';\nimport {hasOwn} from '../../shared/src/has-own.ts';\nimport {type JSONValue} from '../../shared/src/json.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n CorrelatedSubquery,\n CorrelatedSubqueryCondition,\n Correlation,\n LiteralReference,\n Ordering,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport {\n clientToServer,\n type NameMapper,\n} from '../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {ServerSchema} from '../../zero-types/src/server-schema.ts';\nimport type {Format} from '../../zql/src/ivm/view.ts';\nimport {completeOrdering} from '../../zql/src/query/complete-ordering.ts';\nimport {\n sql,\n sqlConvertColumnArg,\n sqlConvertPluralLiteralArg,\n sqlConvertSingularLiteralArg,\n type PluralLiteralType,\n} from './sql.ts';\n\ntype Table = {\n zql: string;\n alias: string;\n};\n\ntype QualifiedColumn = {\n table: Table;\n zql: string;\n};\n\ntype ServerSpec = {\n schema: ServerSchema;\n // maps zql names to server names\n mapper: NameMapper;\n};\n\nexport type Spec = {\n server: ServerSpec;\n zql: Schema['tables'];\n aliasCount: number;\n};\n\nconst ZQL_RESULT_KEY = 'zql_result';\nconst ZQL_RESULT_KEY_IDENT = sql.ident(ZQL_RESULT_KEY);\n\nconst ZQL_RESULT_TABLE_KEY = 'zql_root';\nconst ZQL_RESULT_TABLE_IDENT = sql.ident(ZQL_RESULT_TABLE_KEY);\n\nexport function compile(\n serverSchema: ServerSchema,\n zqlSchema: Schema,\n ast: AST,\n format?: Format,\n): SQLQuery {\n ast = completeOrdering(\n ast,\n tableName => zqlSchema.tables[tableName].primaryKey,\n );\n const spec: Spec = {\n aliasCount: 0,\n server: {\n schema: serverSchema,\n mapper: clientToServer(zqlSchema.tables),\n },\n zql: zqlSchema.tables,\n };\n return sql`SELECT \n ${toJSON(ZQL_RESULT_TABLE_KEY, format?.singular)}::text AS ${ZQL_RESULT_KEY_IDENT}\n FROM (${select(spec, ast, format)}) ${ZQL_RESULT_TABLE_IDENT}`;\n}\n\nfunction select(\n spec: Spec,\n ast: AST,\n format: Format | undefined,\n correlate?: (childTable: Table) => SQLQuery,\n): SQLQuery {\n const table = makeTable(spec, ast.table);\n const selectionSet = related(spec, ast.related ?? [], format, table);\n const tableSchema = spec.zql[ast.table];\n const usedAliases = new Set<string>(\n ast.related?.map(r => r.subquery.alias ?? ''),\n );\n for (const column of Object.keys(tableSchema.columns)) {\n if (!usedAliases.has(column)) {\n selectionSet.push(\n selectIdent(spec.server, {\n table,\n zql: column,\n }),\n );\n }\n }\n\n let appliedWhere = false;\n function maybeWhere(test: unknown | undefined) {\n if (!test) {\n return sql``;\n }\n\n const ret = appliedWhere ? sql`AND` : sql`WHERE`;\n appliedWhere = true;\n return ret;\n }\n\n return sql`SELECT ${sql.join(selectionSet, ',')}\n FROM ${fromIdent(spec.server, table)}\n ${maybeWhere(ast.where)} ${where(spec, ast.where, table)}\n ${maybeWhere(correlate)} ${correlate ? correlate(table) : sql``}\n ${orderBy(spec, ast.orderBy, table)}\n ${limit(ast.limit, format?.singular)}`;\n}\n\nexport function limit(\n limit: number | undefined,\n singular: boolean | undefined,\n): SQLQuery {\n if (limit === 0) {\n return sql`LIMIT 0`;\n }\n if (singular) {\n return sql`LIMIT 1`;\n }\n if (limit === undefined) {\n return sql``;\n }\n return sql`LIMIT ${sqlConvertSingularLiteralArg(limit)}`;\n}\n\nfunction makeTable(spec: Spec, zql: string, alias?: string): Table {\n alias = alias ?? zql + '_' + spec.aliasCount++;\n return {\n zql,\n alias,\n };\n}\n\nexport function orderBy(\n spec: Spec,\n orderBy: Ordering | undefined,\n table: Table,\n): SQLQuery {\n if (!orderBy) {\n return sql``;\n }\n return sql`ORDER BY ${sql.join(\n orderBy.map(([col, dir]) =>\n dir === 'asc'\n ? // Oh postgres. The table must be referred to by client name but the column by server name.\n // E.g., `SELECT server_col as client_col FROM server_table as client_table ORDER BY client_Table.server_col`\n sql`${colIdent(spec.server, {\n table,\n zql: col,\n })} ASC NULLS FIRST`\n : sql`${colIdent(spec.server, {\n table,\n zql: col,\n })} DESC NULLS LAST`,\n ),\n ', ',\n )}`;\n}\n\nfunction related(\n spec: Spec,\n relationships: readonly CorrelatedSubquery[],\n format: Format | undefined,\n parentTable: Table,\n): SQLQuery[] {\n return relationships.map(relationship =>\n relationshipSubquery(\n spec,\n relationship,\n format?.relationships[must(relationship.subquery.alias)],\n parentTable,\n ),\n );\n}\n\nfunction relationshipSubquery(\n spec: Spec,\n relationship: CorrelatedSubquery,\n format: Format | undefined,\n parentTable: Table,\n): SQLQuery {\n const innerAlias = `inner_${relationship.subquery.alias}`;\n if (relationship.hidden) {\n const {join, participatingTables} = makeJunctionJoin(spec, relationship);\n const lastTable = must(last(participatingTables)).table;\n\n assert(\n relationship.subquery.related,\n 'hidden relationship must be a junction',\n );\n const nestedAst = relationship.subquery.related[0].subquery;\n const selectionSet = related(\n spec,\n nestedAst.related ?? [],\n format,\n lastTable,\n );\n const tableSchema = spec.zql[nestedAst.table];\n for (const column of Object.keys(tableSchema.columns)) {\n selectionSet.push(\n selectIdent(spec.server, {\n table: lastTable,\n zql: column,\n }),\n );\n }\n\n return sql`(\n SELECT ${toJSON(innerAlias, format?.singular)} FROM (SELECT ${sql.join(\n selectionSet,\n ',',\n )} FROM ${join} WHERE (${makeCorrelator(\n spec,\n relationship.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n relationship.correlation.childField,\n )(participatingTables[0].table)}) ${\n nestedAst.where\n ? sql`AND ${where(spec, nestedAst.where, lastTable)}`\n : sql``\n } ${orderBy(spec, nestedAst.orderBy, lastTable)} ${limit(\n last(participatingTables)?.limit,\n format?.singular,\n )} ) ${sql.ident(innerAlias)}\n ) as ${sql.ident(relationship.subquery.alias)}`;\n }\n\n return sql`(\n SELECT ${toJSON(innerAlias, format?.singular)} FROM (${select(\n spec,\n relationship.subquery,\n format,\n makeCorrelator(\n spec,\n relationship.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n relationship.correlation.childField,\n ),\n )}) ${sql.ident(innerAlias)}\n ) as ${sql.ident(relationship.subquery.alias)}`;\n}\n\nfunction where(\n spec: Spec,\n condition: Condition | undefined,\n table: Table,\n): SQLQuery {\n if (!condition) {\n return sql``;\n }\n\n switch (condition.type) {\n case 'and':\n return sql`(${sql.join(\n condition.conditions.map(c => where(spec, c, table)),\n ' AND ',\n )})`;\n case 'or':\n return sql`(${sql.join(\n condition.conditions.map(c => where(spec, c, table)),\n ' OR ',\n )})`;\n case 'correlatedSubquery':\n if (condition.scalar) {\n return scalarSubquery(spec, condition, table);\n }\n return exists(spec, condition, table);\n case 'simple':\n return simple(spec, condition, table);\n }\n}\n\nfunction exists(\n spec: Spec,\n condition: CorrelatedSubqueryCondition,\n parentTable: Table,\n): SQLQuery {\n switch (condition.op) {\n case 'EXISTS':\n return sql`EXISTS (${select(\n spec,\n condition.related.subquery,\n undefined,\n makeCorrelator(\n spec,\n condition.related.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n condition.related.correlation.childField,\n ),\n )})`;\n case 'NOT EXISTS':\n return sql`NOT EXISTS (${select(\n spec,\n condition.related.subquery,\n undefined,\n makeCorrelator(\n spec,\n condition.related.correlation.parentField.map(f => ({\n table: parentTable,\n zql: f,\n })),\n condition.related.correlation.childField,\n ),\n )})`;\n }\n}\n\nfunction scalarSubquery(\n spec: Spec,\n condition: CorrelatedSubqueryCondition,\n parentTable: Table,\n): SQLQuery {\n const parentField = condition.related.correlation.parentField[0];\n const childField = condition.related.correlation.childField[0];\n const subqueryAST = condition.related.subquery;\n\n const parentCol = colIdent(spec.server, {\n table: parentTable,\n zql: parentField,\n });\n\n const subqueryTable = makeTable(spec, subqueryAST.table);\n const childCol = colIdent(spec.server, {\n table: subqueryTable,\n zql: childField,\n });\n\n const op = sql.__dangerous__rawValue(\n condition.op === 'EXISTS' ? '=' : 'IS NOT',\n );\n\n const subqueryWhere = subqueryAST.where\n ? sql`WHERE ${where(spec, subqueryAST.where, subqueryTable)}`\n : sql``;\n const subqueryOrderBy = orderBy(spec, subqueryAST.orderBy, subqueryTable);\n\n return sql`${parentCol} ${op} (SELECT ${childCol} FROM ${fromIdent(spec.server, subqueryTable)} ${subqueryWhere} ${subqueryOrderBy} LIMIT 1)`;\n}\n\nexport function makeCorrelator(\n spec: Spec,\n parentFields: readonly QualifiedColumn[],\n childZqlFields: readonly string[],\n): (childTable: Table) => SQLQuery {\n return (childTable: Table) => {\n const childFields = childZqlFields.map(zqlField => ({\n table: childTable,\n zql: zqlField,\n }));\n return sql.join(\n zip(parentFields, childFields).map(\n ([parentColumn, childColumn]) =>\n sql`${colIdent(spec.server, parentColumn)} = ${colIdent(\n spec.server,\n childColumn,\n )}`,\n ),\n ' AND ',\n );\n };\n}\n\nexport function simple(\n spec: Spec,\n condition: SimpleCondition,\n table: Table,\n): SQLQuery {\n switch (condition.op) {\n case '!=':\n case '<':\n case '<=':\n case '=':\n case '>':\n case '>=':\n case 'ILIKE':\n case 'LIKE':\n case 'NOT ILIKE':\n case 'NOT LIKE':\n return sql`${valueComparison(\n spec,\n condition.left,\n table,\n condition.right,\n false,\n )} ${sql.__dangerous__rawValue(condition.op)} ${valueComparison(\n spec,\n condition.right,\n table,\n condition.left,\n false,\n )}`;\n case 'NOT IN':\n case 'IN':\n return any(spec, condition, table);\n case 'IS':\n case 'IS NOT':\n return distinctFrom(spec, condition, table);\n }\n}\n\nexport function any(\n spec: Spec,\n condition: SimpleCondition,\n table: Table,\n): SQLQuery {\n return sql`${condition.op === 'NOT IN' ? sql`NOT` : sql``}\n (\n ${valueComparison(spec, condition.left, table, condition.right, false)} = ANY \n (${valueComparison(spec, condition.right, table, condition.left, true)})\n )`;\n}\n\nexport function distinctFrom(\n spec: Spec,\n condition: SimpleCondition,\n table: Table,\n): SQLQuery {\n return sql`${valueComparison(spec, condition.left, table, condition.right, false)} ${\n condition.op === 'IS' ? sql`IS NOT DISTINCT FROM` : sql`IS DISTINCT FROM`\n } ${valueComparison(spec, condition.right, table, condition.left, false)}`;\n}\n\nfunction valueComparison(\n spec: Spec,\n valuePos: ValuePosition,\n table: Table,\n otherValuePos: ValuePosition,\n plural: boolean,\n): SQLQuery {\n const valuePosType = valuePos.type;\n switch (valuePosType) {\n case 'column': {\n const qualified: QualifiedColumn = {\n table,\n zql: valuePos.name,\n };\n return colIdent(spec.server, qualified);\n }\n case 'literal':\n return literalValueComparison(\n spec,\n valuePos,\n table,\n otherValuePos,\n plural,\n );\n case 'static':\n throw new Error(\n 'Static parameters must be bound to a value before compiling to SQL',\n );\n default:\n unreachable(valuePosType);\n break;\n }\n}\n\nfunction literalValueComparison(\n spec: Spec,\n valuePos: LiteralReference,\n table: Table,\n otherValuePos: ValuePosition,\n plural: boolean,\n): SQLQuery {\n const otherType = otherValuePos.type;\n switch (otherType) {\n case 'column':\n return sqlConvertColumnArg(\n getServerColumn(spec.server, table, otherValuePos.name),\n valuePos.value,\n plural,\n true,\n );\n case 'literal': {\n assert(\n plural === Array.isArray(valuePos.value),\n 'Expected plural flag to match whether value is an array',\n );\n if (Array.isArray(valuePos.value)) {\n if (valuePos.value.length > 0) {\n // If the array is non-empty base its type on its first\n // element\n return sqlConvertPluralLiteralArg(\n typeof valuePos.value[0] as PluralLiteralType,\n valuePos.value as PluralLiteralType[],\n );\n }\n // If the array is empty, base its type on the other value\n // position's type (as long as the other value position is non-null,\n // cannot have a null[]).\n if (otherValuePos.value !== null) {\n return sqlConvertPluralLiteralArg(\n typeof otherValuePos.value as PluralLiteralType,\n [],\n );\n }\n // If the other value position is null, it can be compared to any\n // type of empty array, chose 'string' arbitrarily.\n return sqlConvertPluralLiteralArg('string', []);\n }\n if (\n typeof valuePos.value === 'string' ||\n typeof valuePos.value === 'number' ||\n typeof valuePos.value === 'boolean'\n ) {\n return sqlConvertSingularLiteralArg(valuePos.value);\n }\n throw new Error(\n `Literal of unexpected type. ${valuePos.value} of type ${typeof valuePos.value}`,\n );\n }\n case 'static':\n throw new Error(\n 'Static parameters must be bound to a value before compiling to SQL',\n );\n default:\n unreachable(otherType);\n }\n}\n\nexport function makeJunctionJoin(\n spec: Spec,\n relationship: CorrelatedSubquery,\n): {\n join: SQLQuery;\n participatingTables: ReturnType<typeof pullTablesForJunction>;\n} {\n const participatingTables = pullTablesForJunction(spec, relationship);\n const joins: SQLQuery[] = [];\n\n for (const {table} of participatingTables) {\n if (joins.length === 0) {\n joins.push(fromIdent(spec.server, table));\n continue;\n }\n joins.push(\n sql` JOIN ${fromIdent(spec.server, table)} ON ${makeCorrelator(\n spec,\n participatingTables[joins.length].correlation.parentField.map(f => ({\n table: participatingTables[joins.length - 1].table,\n zql: f,\n })),\n participatingTables[joins.length].correlation.childField,\n )(participatingTables[joins.length].table)}`,\n );\n }\n\n return {\n join: sql`${sql.join(joins, '')}`,\n participatingTables,\n // lastTable: participatingTables[participatingTables.length - 1].table,\n // lastLimit: participatingTables[participatingTables.length - 1].limit,\n };\n}\n\nexport function pullTablesForJunction(\n spec: Spec,\n relationship: CorrelatedSubquery,\n): [\n {\n table: Table;\n correlation: Correlation;\n limit: number | undefined;\n },\n {table: Table; correlation: Correlation; limit: number | undefined},\n] {\n assert(\n relationship.subquery.related?.length === 1,\n 'Too many related tables for a junction edge',\n );\n const otherRelationship = relationship.subquery.related[0];\n assert(\n !otherRelationship.hidden,\n 'Expected junction edge relationship to not be hidden',\n );\n return [\n {\n table: makeTable(spec, relationship.subquery.table),\n correlation: relationship.correlation,\n limit: relationship.subquery.limit,\n },\n {\n table: makeTable(spec, otherRelationship.subquery.table),\n correlation: otherRelationship.correlation,\n limit: otherRelationship.subquery.limit,\n },\n ];\n}\n\nfunction toJSON(table: string, singular = false): SQLQuery {\n return sql`${\n singular ? sql`` : sql`COALESCE(json_agg`\n }(row_to_json(${sql.ident(table)}))${singular ? sql`` : sql`, '[]'::json)`}`;\n}\n\nfunction selectIdent(server: ServerSpec, column: QualifiedColumn): SQLQuery {\n const serverColumnSchema =\n server.schema[server.mapper.tableName(column.table.zql)][\n server.mapper.columnName(column.table.zql, column.zql)\n ];\n const serverType = serverColumnSchema.type;\n if (!serverColumnSchema.isEnum) {\n let needsNormalization = false;\n switch (serverType) {\n case 'timestamptz':\n // @ts-expect-error Fallthrough intended\n case 'timetz':\n needsNormalization = true;\n // fallthrough\n\n case 'date':\n case 'time':\n case 'time without time zone':\n case 'time with time zone':\n case 'timestamp':\n case 'timestamp without time zone':\n case 'timestamp with time zone': {\n // EXTRACT(EPOCH FROM timetz) can be negative when the UTC offset is\n // positive (e.g. 01:00+02 = 23:00 UTC prev day = -3600s). Wrap with\n // modular arithmetic to normalize to 0..86400000.\n const toMs = (epochExpr: SQLQuery): SQLQuery =>\n needsNormalization\n ? sql`((${epochExpr})::bigint + 86400000) % 86400000`\n : epochExpr;\n\n if (serverColumnSchema.isArray) {\n const col = colIdent(server, column);\n return sql`CASE WHEN ${col} IS NULL THEN NULL ELSE ARRAY(SELECT ${toMs(\n sql`EXTRACT(EPOCH FROM unnest(${col})) * 1000`,\n )}) END as ${sql.ident(column.zql)}`;\n }\n\n return sql`${toMs(\n sql`EXTRACT(EPOCH FROM ${colIdent(server, column)}) * 1000`,\n )} as ${sql.ident(column.zql)}`;\n }\n }\n }\n\n return sql`${colIdent(server, column)} as ${sql.ident(column.zql)}`;\n}\n\nfunction colIdent(server: ServerSpec, column: QualifiedColumn) {\n return sql.ident(\n column.table.alias,\n server.mapper.columnName(column.table.zql, column.zql),\n );\n}\n\nfunction fromIdent(server: ServerSpec, table: Table) {\n return sql`${sql.ident(server.mapper.tableName(table.zql))} AS ${sql.ident(table.alias)}`;\n}\n\nfunction getServerColumn(spec: ServerSpec, table: Table, zqlColumn: string) {\n return spec.schema[spec.mapper.tableName(table.zql)][\n spec.mapper.columnName(table.zql, zqlColumn)\n ];\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nexport function extractZqlResult(pgResult: Array<any>): JSONValue {\n const bigIntJson: BigIntJSONValue = parseBigIntJson(\n pgResult[0][ZQL_RESULT_KEY],\n );\n assertJSONValue(bigIntJson);\n return bigIntJson;\n}\n\nfunction assertJSONValue(v: BigIntJSONValue): asserts v is JSONValue {\n const path = findPathToBigInt(v);\n if (path) {\n throw new Error(`Value exceeds safe Number range. ${path}`);\n }\n}\n\nfunction findPathToBigInt(v: BigIntJSONValue): string | undefined {\n const typeOfV = typeof v;\n switch (typeOfV) {\n case 'bigint':\n return ` = ${v}`;\n case 'object': {\n if (v === null) {\n return;\n }\n if (Array.isArray(v)) {\n for (let i = 0; i < v.length; i++) {\n const path = findPathToBigInt(v[i]);\n if (path) {\n return `[${i}]${path}`;\n }\n }\n return undefined;\n }\n\n const o = v as Record<string, BigIntJSONValue>;\n for (const k in o) {\n if (hasOwn(o, k)) {\n const path = findPathToBigInt(o[k]);\n if (path) {\n return `['${k}']${path}`;\n }\n }\n }\n return undefined;\n }\n case 'number':\n return undefined;\n case 'boolean':\n return undefined;\n default:\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../../z2s/src/sql.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAwB,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AACpE,OAAO,GAAkB,MAAM,gBAAgB,CAAC;AAOhD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uCAAuC,CAAC;AAE9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ;UA4Q9B,MAAM;YACJ,OAAO,EAAE;EA1QlB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,QAAQ;UAuQ7C,MAAM;YACJ,OAAO,EAAE;EArQlB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ;UAkQlC,MAAM;YACJ,OAAO,EAAE;EAhQlB;AAID,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAwB7D,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,GACtC,QAAQ,CAQV;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,iBAAiB,EAAE,GACzB,QAAQ,CAQV;AAED,wBAAgB,mBAAmB,CACjC,kBAAkB,EAAE,kBAAkB,EACtC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,OAAO,GACpB,QAAQ,CASV;AAiLD,OAAO,EAAC,GAAG,EAAC,CAAC"}
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../../z2s/src/sql.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAwB,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AACpE,OAAO,GAAkB,MAAM,gBAAgB,CAAC;AAOhD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uCAAuC,CAAC;AAE9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ;UAgR9B,MAAM;YACJ,OAAO,EAAE;EA9QlB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,QAAQ;UA2Q7C,MAAM;YACJ,OAAO,EAAE;EAzQlB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ;UAsQlC,MAAM;YACJ,OAAO,EAAE;EApQlB;AAID,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAwB7D,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,GACtC,QAAQ,CAQV;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,iBAAiB,EAAE,GACzB,QAAQ,CAQV;AAED,wBAAgB,mBAAmB,CACjC,kBAAkB,EAAE,kBAAkB,EACtC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,OAAO,GACpB,QAAQ,CASV;AAqLD,OAAO,EAAC,GAAG,EAAC,CAAC"}
@@ -97,6 +97,7 @@ function createPlaceholder(index, arg) {
97
97
  assert(!arg.plural, "Args of type 'null' must not be plural");
98
98
  return `$${index}`;
99
99
  }
100
+ if (arg.value === null && arg.plural) return `$${index}`;
100
101
  if (arg[sqlConvert] === "literal") {
101
102
  const { value } = arg;
102
103
  if (Array.isArray(value)) return formatPlural(index, `value::${pgTypeForLiteralType(arg.type)}`);