@rocicorp/zero 0.19.2025042901 → 0.19.2025043001

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 (67) hide show
  1. package/out/{chunk-W7REZ3GY.js → chunk-CV5PHAED.js} +6 -6
  2. package/out/{chunk-W7REZ3GY.js.map → chunk-CV5PHAED.js.map} +2 -2
  3. package/out/{chunk-2QVFOPGX.js → chunk-FIIHDLSZ.js} +1 -1
  4. package/out/chunk-FIIHDLSZ.js.map +7 -0
  5. package/out/{chunk-I5KZ62HL.js → chunk-T42I73ZN.js} +2 -2
  6. package/out/{inspector-T4TQPT2K.js → inspector-RKVBTGL6.js} +3 -3
  7. package/out/react.js +1 -1
  8. package/out/shared/src/arrays.d.ts +1 -0
  9. package/out/shared/src/arrays.d.ts.map +1 -1
  10. package/out/shared/src/arrays.js +6 -0
  11. package/out/shared/src/arrays.js.map +1 -1
  12. package/out/solid.js +3 -3
  13. package/out/z2s/src/compiler.d.ts +45 -50
  14. package/out/z2s/src/compiler.d.ts.map +1 -1
  15. package/out/z2s/src/compiler.js +310 -300
  16. package/out/z2s/src/compiler.js.map +1 -1
  17. package/out/zero-cache/src/config/zero-config.d.ts +118 -2
  18. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  19. package/out/zero-cache/src/config/zero-config.js +21 -1
  20. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  21. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  22. package/out/zero-cache/src/server/change-streamer.js +1 -1
  23. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  24. package/out/zero-cache/src/server/main.js +1 -1
  25. package/out/zero-cache/src/server/main.js.map +1 -1
  26. package/out/zero-cache/src/server/runner/config.d.ts +115 -17
  27. package/out/zero-cache/src/server/runner/config.d.ts.map +1 -1
  28. package/out/zero-cache/src/server/runner/config.js +2 -2
  29. package/out/zero-cache/src/server/runner/config.js.map +1 -1
  30. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  31. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  32. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts +2 -1
  33. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  34. package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -2
  35. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  36. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -1
  37. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
  39. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  40. package/out/zero-cache/src/services/dispatcher/dispatcher.d.ts +2 -1
  41. package/out/zero-cache/src/services/dispatcher/dispatcher.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/dispatcher/dispatcher.js +2 -2
  43. package/out/zero-cache/src/services/dispatcher/dispatcher.js.map +1 -1
  44. package/out/zero-cache/src/services/http-service.d.ts +2 -1
  45. package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
  46. package/out/zero-cache/src/services/http-service.js +6 -1
  47. package/out/zero-cache/src/services/http-service.js.map +1 -1
  48. package/out/zero-cache/src/services/statz.d.ts +4 -0
  49. package/out/zero-cache/src/services/statz.d.ts.map +1 -0
  50. package/out/{analyze-query/src/bin-cache-stats.js → zero-cache/src/services/statz.js} +100 -103
  51. package/out/zero-cache/src/services/statz.js.map +1 -0
  52. package/out/zero-cache/src/types/error-for-client.d.ts +1 -1
  53. package/out/zero-client/src/client/server-option.d.ts.map +1 -1
  54. package/out/zero-pg/src/query.js +1 -1
  55. package/out/zero-pg/src/query.js.map +1 -1
  56. package/out/zero.js +3 -3
  57. package/package.json +5 -4
  58. package/out/analyze-query/src/bin-cache-stats.d.ts +0 -2
  59. package/out/analyze-query/src/bin-cache-stats.d.ts.map +0 -1
  60. package/out/analyze-query/src/bin-cache-stats.js.map +0 -1
  61. package/out/chunk-2QVFOPGX.js.map +0 -7
  62. package/out/zero/src/cache-stats.d.ts +0 -3
  63. package/out/zero/src/cache-stats.d.ts.map +0 -1
  64. package/out/zero/src/cache-stats.js +0 -3
  65. package/out/zero/src/cache-stats.js.map +0 -1
  66. /package/out/{chunk-I5KZ62HL.js.map → chunk-T42I73ZN.js.map} +0 -0
  67. /package/out/{inspector-T4TQPT2K.js.map → inspector-RKVBTGL6.js.map} +0 -0
@@ -1,13 +1,318 @@
1
- import { zip } from "../../shared/src/arrays.js";
1
+ import { sql, sqlConvertColumnArg, sqlConvertPluralLiteralArg, sqlConvertSingularLiteralArg, Z2S_COLLATION, } from "./sql.js";
2
+ import { clientToServer, } from "../../zero-schema/src/name-mapper.js";
3
+ import { must } from "../../shared/src/must.js";
4
+ import { last, zip } from "../../shared/src/arrays.js";
2
5
  import { assert, unreachable } from "../../shared/src/asserts.js";
3
6
  import {} from "../../shared/src/json.js";
4
- import { must } from "../../shared/src/must.js";
5
- import {} from "../../zero-protocol/src/ast.js";
6
- import { clientToServer, NameMapper } from "../../zero-schema/src/name-mapper.js";
7
- import { sql, sqlConvertColumnArg, sqlConvertSingularLiteralArg, sqlConvertPluralLiteralArg, Z2S_COLLATION, } from "./sql.js";
8
7
  import { parse as parseBigIntJson, } from "../../zero-cache/src/types/bigint-json.js";
9
8
  import { hasOwn } from "../../shared/src/has-own.js";
10
9
  const ZQL_RESULT_KEY = 'zql_result';
10
+ const ZQL_RESULT_KEY_IDENT = sql.ident(ZQL_RESULT_KEY);
11
+ export function compile(serverSchema, zqlSchema, ast, format) {
12
+ const spec = {
13
+ aliasCount: 0,
14
+ server: {
15
+ schema: serverSchema,
16
+ mapper: clientToServer(zqlSchema.tables),
17
+ },
18
+ zql: zqlSchema.tables,
19
+ };
20
+ return sql `SELECT
21
+ ${toJSON('root', format?.singular)}::text AS ${ZQL_RESULT_KEY_IDENT}
22
+ FROM (${select(spec, ast, format)})${sql.ident('root')}`;
23
+ }
24
+ function select(spec, ast, format, correlate) {
25
+ const table = makeTable(spec, ast.table);
26
+ const selectionSet = related(spec, ast.related ?? [], format, table);
27
+ const tableSchema = spec.zql[ast.table];
28
+ const usedAliases = new Set(ast.related?.map(r => r.subquery.alias ?? ''));
29
+ for (const column of Object.keys(tableSchema.columns)) {
30
+ if (!usedAliases.has(column)) {
31
+ selectionSet.push(selectIdent(spec.server, {
32
+ table,
33
+ zql: column,
34
+ }));
35
+ }
36
+ }
37
+ let appliedWhere = false;
38
+ function maybeWhere(test) {
39
+ if (!test) {
40
+ return sql ``;
41
+ }
42
+ const ret = appliedWhere ? sql `AND` : sql `WHERE`;
43
+ appliedWhere = true;
44
+ return ret;
45
+ }
46
+ return sql `SELECT ${sql.join(selectionSet, ',')}
47
+ FROM ${fromIdent(spec.server, table)}
48
+ ${maybeWhere(ast.where)} ${where(spec, ast.where, table)}
49
+ ${maybeWhere(correlate)} ${correlate ? correlate(table) : sql ``}
50
+ ${orderBy(spec, ast.orderBy, table)}
51
+ ${format?.singular ? limit(1) : limit(ast.limit)}`;
52
+ }
53
+ export function limit(limit) {
54
+ if (!limit) {
55
+ return sql ``;
56
+ }
57
+ return sql `LIMIT ${sqlConvertSingularLiteralArg(limit)}`;
58
+ }
59
+ function makeTable(spec, zql, alias) {
60
+ alias = alias ?? zql + '_' + spec.aliasCount++;
61
+ return {
62
+ zql,
63
+ alias,
64
+ };
65
+ }
66
+ export function orderBy(spec, orderBy, table) {
67
+ if (!orderBy) {
68
+ return sql ``;
69
+ }
70
+ return sql `ORDER BY ${sql.join(orderBy.map(([col, dir]) => {
71
+ const serverColumnSchema = getServerColumn(spec.server, table, col);
72
+ return dir === 'asc'
73
+ ? // Oh postgres. The table must be referred to by client name but the column by server name.
74
+ // E.g., `SELECT server_col as client_col FROM server_table as client_table ORDER BY client_Table.server_col`
75
+ sql `${colIdent(spec.server, {
76
+ table,
77
+ zql: col,
78
+ })}${maybeCollate(serverColumnSchema)} ASC`
79
+ : sql `${colIdent(spec.server, {
80
+ table,
81
+ zql: col,
82
+ })}${maybeCollate(serverColumnSchema)} DESC`;
83
+ }), ', ')}`;
84
+ }
85
+ function maybeCollate(serverColumnSchema) {
86
+ if (serverColumnSchema.type === 'text' ||
87
+ serverColumnSchema.type === 'char' ||
88
+ serverColumnSchema.type === 'varchar') {
89
+ return sql ` COLLATE ${sql.ident(Z2S_COLLATION)}`;
90
+ }
91
+ if (serverColumnSchema.type === 'uuid' || serverColumnSchema.isEnum) {
92
+ return sql `::text COLLATE ${sql.ident(Z2S_COLLATION)}`;
93
+ }
94
+ return sql ``;
95
+ }
96
+ function related(spec, relationships, format, parentTable) {
97
+ return relationships.map(relationship => relationshipSubquery(spec, relationship, format?.relationships[must(relationship.subquery.alias)], parentTable));
98
+ }
99
+ function relationshipSubquery(spec, relationship, format, parentTable) {
100
+ const innerAlias = `inner_${relationship.subquery.alias}`;
101
+ if (relationship.hidden) {
102
+ const { join, participatingTables } = makeJunctionJoin(spec, relationship);
103
+ const lastTable = must(last(participatingTables)).table;
104
+ assert(relationship.subquery.related, 'hidden relationship must be a junction');
105
+ const nestedAst = relationship.subquery.related[0].subquery;
106
+ const selectionSet = related(spec, nestedAst.related ?? [], format, lastTable);
107
+ const tableSchema = spec.zql[nestedAst.table];
108
+ for (const column of Object.keys(tableSchema.columns)) {
109
+ selectionSet.push(selectIdent(spec.server, {
110
+ table: lastTable,
111
+ zql: column,
112
+ }));
113
+ }
114
+ return sql `(
115
+ SELECT ${toJSON(innerAlias, format?.singular)} FROM (SELECT ${sql.join(selectionSet, ',')} FROM ${join} WHERE (${makeCorrelator(spec, relationship.correlation.parentField.map(f => ({
116
+ table: parentTable,
117
+ zql: f,
118
+ })), relationship.correlation.childField)(participatingTables[0].table)}) ${relationship.subquery.where
119
+ ? sql `AND ${where(spec, relationship.subquery.where, participatingTables[0].table)}`
120
+ : sql ``} ${orderBy(spec, relationship.subquery.orderBy, participatingTables[0].table)} ${format?.singular ? limit(1) : limit(last(participatingTables)?.limit)} ) ${sql.ident(innerAlias)}
121
+ ) as ${sql.ident(relationship.subquery.alias)}`;
122
+ }
123
+ return sql `(
124
+ SELECT ${toJSON(innerAlias, format?.singular)} FROM (${select(spec, relationship.subquery, format, makeCorrelator(spec, relationship.correlation.parentField.map(f => ({
125
+ table: parentTable,
126
+ zql: f,
127
+ })), relationship.correlation.childField))}) ${sql.ident(innerAlias)}
128
+ ) as ${sql.ident(relationship.subquery.alias)}`;
129
+ }
130
+ function where(spec, condition, table) {
131
+ if (!condition) {
132
+ return sql ``;
133
+ }
134
+ switch (condition.type) {
135
+ case 'and':
136
+ return sql `(${sql.join(condition.conditions.map(c => where(spec, c, table)), ' AND ')})`;
137
+ case 'or':
138
+ return sql `(${sql.join(condition.conditions.map(c => where(spec, c, table)), ' OR ')})`;
139
+ case 'correlatedSubquery':
140
+ return exists(spec, condition, table);
141
+ case 'simple':
142
+ return simple(spec, condition, table);
143
+ }
144
+ }
145
+ function exists(spec, condition, parentTable) {
146
+ switch (condition.op) {
147
+ case 'EXISTS':
148
+ return sql `EXISTS (${select(spec, condition.related.subquery, undefined, makeCorrelator(spec, condition.related.correlation.parentField.map(f => ({
149
+ table: parentTable,
150
+ zql: f,
151
+ })), condition.related.correlation.childField))})`;
152
+ case 'NOT EXISTS':
153
+ return sql `NOT EXISTS (${select(spec, condition.related.subquery, undefined, makeCorrelator(spec, condition.related.correlation.parentField.map(f => ({
154
+ table: parentTable,
155
+ zql: f,
156
+ })), condition.related.correlation.childField))})`;
157
+ }
158
+ }
159
+ export function makeCorrelator(spec, parentFields, childZqlFields) {
160
+ return (childTable) => {
161
+ const childFields = childZqlFields.map(zqlField => ({
162
+ table: childTable,
163
+ zql: zqlField,
164
+ }));
165
+ return sql.join(zip(parentFields, childFields).map(([parentColumn, childColumn]) => sql `${colIdent(spec.server, parentColumn)} = ${colIdent(spec.server, childColumn)}`), ' AND ');
166
+ };
167
+ }
168
+ export function simple(spec, condition, table) {
169
+ switch (condition.op) {
170
+ case '!=':
171
+ case '<':
172
+ case '<=':
173
+ case '=':
174
+ case '>':
175
+ case '>=':
176
+ case 'ILIKE':
177
+ case 'LIKE':
178
+ case 'NOT ILIKE':
179
+ case 'NOT LIKE':
180
+ return sql `${valueComparison(spec, condition.left, table, condition.right, false)} ${sql.__dangerous__rawValue(condition.op)} ${valueComparison(spec, condition.right, table, condition.left, false)}`;
181
+ case 'NOT IN':
182
+ case 'IN':
183
+ return any(spec, condition, table);
184
+ case 'IS':
185
+ case 'IS NOT':
186
+ return distinctFrom(spec, condition, table);
187
+ }
188
+ }
189
+ export function any(spec, condition, table) {
190
+ return sql `${valueComparison(spec, condition.left, table, condition.right, false)} ${condition.op === 'IN' ? sql `= ANY` : sql `!= ANY`} (${valueComparison(spec, condition.right, table, condition.left, true)})`;
191
+ }
192
+ export function distinctFrom(spec, condition, table) {
193
+ return sql `${valueComparison(spec, condition.left, table, condition.right, false)} ${condition.op === 'IS' ? sql `IS NOT DISTINCT FROM` : sql `IS DISTINCT FROM`} ${valueComparison(spec, condition.right, table, condition.left, false)}`;
194
+ }
195
+ function valueComparison(spec, valuePos, table, otherValuePos, plural) {
196
+ const valuePosType = valuePos.type;
197
+ switch (valuePosType) {
198
+ case 'column': {
199
+ const serverColumnSchema = getServerColumn(spec.server, table, valuePos.name);
200
+ const qualified = {
201
+ table,
202
+ zql: valuePos.name,
203
+ };
204
+ if (serverColumnSchema.type === 'uuid' || serverColumnSchema.isEnum) {
205
+ return sql `${colIdent(spec.server, qualified)}::text`;
206
+ }
207
+ return colIdent(spec.server, qualified);
208
+ }
209
+ case 'literal':
210
+ return literalValueComparison(spec, valuePos, table, otherValuePos, plural);
211
+ case 'static':
212
+ throw new Error('Static parameters must be bound to a value before compiling to SQL');
213
+ default:
214
+ unreachable(valuePosType);
215
+ break;
216
+ }
217
+ }
218
+ function literalValueComparison(spec, valuePos, table, otherValuePos, plural) {
219
+ const otherType = otherValuePos.type;
220
+ switch (otherType) {
221
+ case 'column':
222
+ return sqlConvertColumnArg(getServerColumn(spec.server, table, otherValuePos.name), valuePos.value, plural, true);
223
+ case 'literal': {
224
+ assert(plural === Array.isArray(valuePos.value));
225
+ if (Array.isArray(valuePos.value)) {
226
+ if (valuePos.value.length > 0) {
227
+ // If the array is non-empty base its type on its first
228
+ // element
229
+ return sqlConvertPluralLiteralArg(typeof valuePos.value[0], valuePos.value);
230
+ }
231
+ // If the array is empty, base its type on the other value
232
+ // position's type (as long as the other value position is non-null,
233
+ // cannot have a null[]).
234
+ if (otherValuePos.value !== null) {
235
+ return sqlConvertPluralLiteralArg(typeof otherValuePos.value, []);
236
+ }
237
+ // If the other value position is null, it can be compared to any
238
+ // type of empty array, chose 'string' arbitrarily.
239
+ return sqlConvertPluralLiteralArg('string', []);
240
+ }
241
+ if (typeof valuePos.value === 'string' ||
242
+ typeof valuePos.value === 'number' ||
243
+ typeof valuePos.value === 'boolean') {
244
+ return sqlConvertSingularLiteralArg(valuePos.value);
245
+ }
246
+ throw new Error(`Literal of unexpected type. ${valuePos.value} of type ${typeof valuePos.value}`);
247
+ }
248
+ case 'static':
249
+ throw new Error('Static parameters must be bound to a value before compiling to SQL');
250
+ default:
251
+ unreachable(otherType);
252
+ }
253
+ }
254
+ export function makeJunctionJoin(spec, relationship) {
255
+ const participatingTables = pullTablesForJunction(spec, relationship);
256
+ const joins = [];
257
+ for (const { table } of participatingTables) {
258
+ if (joins.length === 0) {
259
+ joins.push(fromIdent(spec.server, table));
260
+ continue;
261
+ }
262
+ joins.push(sql ` JOIN ${fromIdent(spec.server, table)} ON ${makeCorrelator(spec, participatingTables[joins.length].correlation.parentField.map(f => ({
263
+ table: participatingTables[joins.length - 1].table,
264
+ zql: f,
265
+ })), participatingTables[joins.length].correlation.childField)(participatingTables[joins.length].table)}`);
266
+ }
267
+ return {
268
+ join: sql `${sql.join(joins, '')}`,
269
+ participatingTables,
270
+ // lastTable: participatingTables[participatingTables.length - 1].table,
271
+ // lastLimit: participatingTables[participatingTables.length - 1].limit,
272
+ };
273
+ }
274
+ export function pullTablesForJunction(spec, relationship) {
275
+ assert(relationship.subquery.related?.length === 1, 'Too many related tables for a junction edge');
276
+ const otherRelationship = relationship.subquery.related[0];
277
+ assert(!otherRelationship.hidden);
278
+ return [
279
+ {
280
+ table: makeTable(spec, relationship.subquery.table),
281
+ correlation: relationship.correlation,
282
+ limit: relationship.subquery.limit,
283
+ },
284
+ {
285
+ table: makeTable(spec, otherRelationship.subquery.table),
286
+ correlation: otherRelationship.correlation,
287
+ limit: otherRelationship.subquery.limit,
288
+ },
289
+ ];
290
+ }
291
+ function toJSON(table, singular = false) {
292
+ return sql `${singular ? sql `` : sql `COALESCE(json_agg`}(row_to_json(${sql.ident(table)}))${singular ? sql `` : sql `, '[]'::json)`}`;
293
+ }
294
+ function selectIdent(server, column) {
295
+ const serverColumnSchema = server.schema[server.mapper.tableName(column.table.zql)][server.mapper.columnName(column.table.zql, column.zql)];
296
+ const serverType = serverColumnSchema.type;
297
+ if (!serverColumnSchema.isEnum &&
298
+ (serverType === 'date' ||
299
+ serverType === 'timestamp' ||
300
+ serverType === 'timestamp without time zone' ||
301
+ serverType === 'timestamptz' ||
302
+ serverType === 'timestamp with time zone')) {
303
+ return sql `EXTRACT(EPOCH FROM ${colIdent(server, column)}) * 1000 as ${sql.ident(column.zql)}`;
304
+ }
305
+ return sql `${colIdent(server, column)} as ${sql.ident(column.zql)}`;
306
+ }
307
+ function colIdent(server, column) {
308
+ return sql.ident(column.table.alias, server.mapper.columnName(column.table.zql, column.zql));
309
+ }
310
+ function fromIdent(server, table) {
311
+ return sql `${sql.ident(server.mapper.tableName(table.zql))} AS ${sql.ident(table.alias)}`;
312
+ }
313
+ function getServerColumn(spec, table, zqlColumn) {
314
+ return spec.schema[spec.mapper.tableName(table.zql)][spec.mapper.columnName(table.zql, zqlColumn)];
315
+ }
11
316
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
317
  export function extractZqlResult(pgResult) {
13
318
  const bigIntJson = parseBigIntJson(pgResult[0][ZQL_RESULT_KEY]);
@@ -57,299 +362,4 @@ function findPathToBigInt(v) {
57
362
  return undefined;
58
363
  }
59
364
  }
60
- /**
61
- * Compiles to the Postgres dialect of SQL
62
- * - IS, IS NOT can only compare against `NULL`, `TRUE`, `FALSE` so use
63
- * `IS DISTINCT FROM` and `IS NOT DISTINCT FROM` instead
64
- * - IN is changed to ANY to allow binding array literals
65
- * - subqueries are aggregated using PG's `array_agg` and `row_to_json` functions
66
- * - AST must be in client name format
67
- */
68
- export function compile(ast, tables, serverSchema, format) {
69
- const compiler = new Compiler(tables, serverSchema);
70
- return compiler.compile(ast, format);
71
- }
72
- export class Compiler {
73
- #tables;
74
- #serverSchema;
75
- #nameMapper;
76
- constructor(tables, serverSchema) {
77
- this.#tables = tables;
78
- this.#serverSchema = serverSchema;
79
- this.#nameMapper = clientToServer(tables);
80
- }
81
- compile(ast, format) {
82
- return sql `SELECT ${this.#toJSON(`root`, format?.singular)}::text as ${sql.ident(ZQL_RESULT_KEY)} FROM (${this.select(ast, format, undefined)})${sql.ident(`root`)}`;
83
- }
84
- select(ast,
85
- // Is this a singular or plural query?
86
- format,
87
- // If a select is being used as a subquery, this is the correlation to the parent query
88
- correlation) {
89
- const selectionSet = this.related(ast.related ?? [], format, ast.table);
90
- const tableSchema = this.#tables[ast.table];
91
- const usedAliases = new Set(ast.related?.map(r => r.subquery.alias ?? ''));
92
- for (const column of Object.keys(tableSchema.columns)) {
93
- if (!usedAliases.has(column)) {
94
- selectionSet.push(this.#selectCol(ast.table, column));
95
- }
96
- }
97
- return sql `SELECT ${sql.join(selectionSet, ',')} FROM ${this.#mapTable(ast.table)} ${ast.where ? sql `WHERE ${this.where(ast.where, ast.table)}` : sql ``} ${correlation
98
- ? sql `${ast.where ? sql `AND` : sql `WHERE`} (${correlation})`
99
- : sql ``} ${this.orderBy(ast.orderBy, ast.table)} ${format?.singular ? this.limit(1) : this.limit(ast.limit)}`;
100
- }
101
- orderBy(orderBy, table) {
102
- if (!orderBy) {
103
- return sql ``;
104
- }
105
- return sql `ORDER BY ${sql.join(orderBy.map(([col, dir]) => {
106
- const serverColumnSchema = this.#serverSchema[this.#nameMapper.tableName(table)][this.#nameMapper.columnName(table, col)];
107
- return dir === 'asc'
108
- ? // Oh postgres. The table must be referred to be client name but the column by server name.
109
- // E.g., `SELECT server_col as client_col FROM server_table as client_table ORDER BY client_Table.server_col`
110
- sql `${sql.ident(table)}.${this.#mapColumnNoAlias(table, col)}${this.#maybeCollate(serverColumnSchema)} ASC`
111
- : sql `${sql.ident(table)}.${this.#mapColumnNoAlias(table, col)}${this.#maybeCollate(serverColumnSchema)} DESC`;
112
- }), ', ')}`;
113
- }
114
- #maybeCollate(serverColumnSchema) {
115
- if (serverColumnSchema.type === 'text' ||
116
- serverColumnSchema.type === 'char' ||
117
- serverColumnSchema.type === 'varchar') {
118
- return sql ` COLLATE ${sql.ident(Z2S_COLLATION)}`;
119
- }
120
- if (serverColumnSchema.type === 'uuid' || serverColumnSchema.isEnum) {
121
- return sql `::text COLLATE ${sql.ident(Z2S_COLLATION)}`;
122
- }
123
- return sql ``;
124
- }
125
- limit(limit) {
126
- if (!limit) {
127
- return sql ``;
128
- }
129
- return sql `LIMIT ${sqlConvertSingularLiteralArg(limit)}`;
130
- }
131
- related(relationships, format, parentTable, parentTableAlias) {
132
- return relationships.map(relationship => this.relationshipSubquery(relationship, format?.relationships[must(relationship.subquery.alias)], parentTable, parentTableAlias));
133
- }
134
- relationshipSubquery(relationship, format, parentTable, parentTableAlias) {
135
- const innerAlias = `inner_${relationship.subquery.alias}`;
136
- if (relationship.hidden) {
137
- const [join, lastAlias, lastLimit, lastTable] = this.makeJunctionJoin(relationship);
138
- assert(relationship.subquery.related, 'hidden relationship must be a junction');
139
- const nestedAst = relationship.subquery.related[0].subquery;
140
- const selectionSet = this.related(nestedAst.related ?? [], format, lastTable, lastAlias);
141
- const tableSchema = this.#tables[nestedAst.table];
142
- for (const column of Object.keys(tableSchema.columns)) {
143
- selectionSet.push(this.#selectCol(nestedAst.table, column, lastAlias));
144
- }
145
- return sql `(
146
- SELECT ${this.#toJSON(innerAlias, format?.singular)} FROM (SELECT ${sql.join(selectionSet, ',')} FROM ${join} WHERE (${this.correlate(parentTable, parentTableAlias ?? parentTable, relationship.correlation.parentField, relationship.subquery.table, relationship.subquery.table, relationship.correlation.childField)}) ${relationship.subquery.where
147
- ? sql `AND ${this.where(relationship.subquery.where, relationship.subquery.table)}`
148
- : sql ``} ${this.orderBy(relationship.subquery.orderBy, relationship.subquery.table)} ${format?.singular ? this.limit(1) : this.limit(lastLimit)} ) ${sql.ident(innerAlias)}
149
- ) as ${sql.ident(relationship.subquery.alias)}`;
150
- }
151
- return sql `(
152
- SELECT ${this.#toJSON(innerAlias, format?.singular)} FROM (${this.select(relationship.subquery, format, this.correlate(parentTable, parentTableAlias ?? parentTable, relationship.correlation.parentField, relationship.subquery.table, relationship.subquery.table, relationship.correlation.childField))}) ${sql.ident(innerAlias)}
153
- ) as ${sql.ident(relationship.subquery.alias)}`;
154
- }
155
- pullTablesForJunction(relationship) {
156
- const tables = [];
157
- tables.push([
158
- relationship.subquery.table,
159
- relationship.correlation,
160
- relationship.subquery.limit,
161
- ]);
162
- assert(relationship.subquery.related?.length === 1, 'Too many related tables for a junction edge');
163
- const otherRelationship = relationship.subquery.related[0];
164
- assert(!otherRelationship.hidden);
165
- return [
166
- [
167
- relationship.subquery.table,
168
- relationship.correlation,
169
- relationship.subquery.limit,
170
- ],
171
- [
172
- otherRelationship.subquery.table,
173
- otherRelationship.correlation,
174
- otherRelationship.subquery.limit,
175
- ],
176
- ];
177
- }
178
- makeJunctionJoin(relationship) {
179
- const participatingTables = this.pullTablesForJunction(relationship);
180
- const joins = [];
181
- function alias(index) {
182
- if (index === 0) {
183
- return participatingTables[0][0];
184
- }
185
- return `table_${index}`;
186
- }
187
- for (const [table, _correlation] of participatingTables) {
188
- if (joins.length === 0) {
189
- joins.push(this.#mapTable(table));
190
- continue;
191
- }
192
- joins.push(sql ` JOIN ${this.#mapTableNoAlias(table)} as ${sql.ident(alias(joins.length))} ON ${this.correlate(participatingTables[joins.length - 1][0], alias(joins.length - 1), participatingTables[joins.length][1].parentField, participatingTables[joins.length][0], alias(joins.length), participatingTables[joins.length][1].childField)}`);
193
- }
194
- return [
195
- sql.join(joins, ''),
196
- alias(joins.length - 1),
197
- participatingTables[participatingTables.length - 1][2],
198
- participatingTables[participatingTables.length - 1][0],
199
- ];
200
- }
201
- where(condition, table) {
202
- if (!condition) {
203
- return sql ``;
204
- }
205
- switch (condition.type) {
206
- case 'and':
207
- return sql `(${sql.join(condition.conditions.map(c => this.where(c, table)), ' AND ')})`;
208
- case 'or':
209
- return sql `(${sql.join(condition.conditions.map(c => this.where(c, table)), ' OR ')})`;
210
- case 'correlatedSubquery':
211
- return this.exists(condition, table);
212
- case 'simple':
213
- return this.simple(condition, table);
214
- }
215
- }
216
- simple(condition, table) {
217
- switch (condition.op) {
218
- case '!=':
219
- case '<':
220
- case '<=':
221
- case '=':
222
- case '>':
223
- case '>=':
224
- case 'ILIKE':
225
- case 'LIKE':
226
- case 'NOT ILIKE':
227
- case 'NOT LIKE':
228
- return sql `${this.valueComparison(condition.left, table, condition.right, false)} ${sql.__dangerous__rawValue(condition.op)} ${this.valueComparison(condition.right, table, condition.left, false)}`;
229
- case 'NOT IN':
230
- case 'IN':
231
- return this.any(condition, table);
232
- case 'IS':
233
- case 'IS NOT':
234
- return this.distinctFrom(condition, table);
235
- }
236
- }
237
- distinctFrom(condition, table) {
238
- return sql `${this.valueComparison(condition.left, table, condition.right, false)} ${condition.op === 'IS' ? sql `IS NOT DISTINCT FROM` : sql `IS DISTINCT FROM`} ${this.valueComparison(condition.right, table, condition.left, false)}`;
239
- }
240
- any(condition, table) {
241
- return sql `${this.valueComparison(condition.left, table, condition.right, false)} ${condition.op === 'IN' ? sql `= ANY` : sql `!= ANY`} (${this.valueComparison(condition.right, table, condition.left, true)})`;
242
- }
243
- valueComparison(valuePos, table, otherValuePos, plural) {
244
- const valuePosType = valuePos.type;
245
- switch (valuePosType) {
246
- case 'column': {
247
- const serverColumnSchema = this.#serverSchema[this.#nameMapper.tableName(table)][this.#nameMapper.columnName(table, valuePos.name)];
248
- if (serverColumnSchema.type === 'uuid' || serverColumnSchema.isEnum) {
249
- return sql `${this.#mapColumnNoAlias(table, valuePos.name)}::text`;
250
- }
251
- return this.#mapColumnNoAlias(table, valuePos.name);
252
- }
253
- case 'literal':
254
- return this.#literalValueComparison(valuePos, table, otherValuePos, plural);
255
- case 'static':
256
- throw new Error('Static parameters must be bound to a value before compiling to SQL');
257
- default:
258
- unreachable(valuePosType);
259
- break;
260
- }
261
- }
262
- #literalValueComparison(valuePos, table, otherValuePos, plural) {
263
- {
264
- const otherType = otherValuePos.type;
265
- switch (otherType) {
266
- case 'column':
267
- return sqlConvertColumnArg(this.#serverSchema[this.#nameMapper.tableName(table)][this.#nameMapper.columnName(table, otherValuePos.name)], valuePos.value, plural, true);
268
- case 'literal': {
269
- assert(plural === Array.isArray(valuePos.value));
270
- if (Array.isArray(valuePos.value)) {
271
- if (valuePos.value.length > 0) {
272
- // If the array is non-empty base its type on its first
273
- // element
274
- return sqlConvertPluralLiteralArg(typeof valuePos.value[0], valuePos.value);
275
- }
276
- // If the array is empty, base its type on the other value
277
- // position's type (as long as the other value position is non-null,
278
- // cannot have a null[]).
279
- if (otherValuePos.value !== null) {
280
- return sqlConvertPluralLiteralArg(typeof otherValuePos.value, []);
281
- }
282
- // If the other value position is null, it can be compared to any
283
- // type of empty array, chose 'string' arbitrarily.
284
- return sqlConvertPluralLiteralArg('string', []);
285
- }
286
- if (typeof valuePos.value === 'string' ||
287
- typeof valuePos.value === 'number' ||
288
- typeof valuePos.value === 'boolean') {
289
- return sqlConvertSingularLiteralArg(valuePos.value);
290
- }
291
- throw new Error(`Literal of unexpected type. ${valuePos.value} of type ${typeof valuePos.value}`);
292
- }
293
- case 'static':
294
- throw new Error('Static parameters must be bound to a value before compiling to SQL');
295
- default:
296
- unreachable(otherType);
297
- }
298
- }
299
- }
300
- exists(condition, parentTable) {
301
- switch (condition.op) {
302
- case 'EXISTS':
303
- return sql `EXISTS (${this.select(condition.related.subquery, undefined, this.correlate(parentTable, parentTable, condition.related.correlation.parentField, condition.related.subquery.table, condition.related.subquery.table, condition.related.correlation.childField))})`;
304
- case 'NOT EXISTS':
305
- return sql `NOT EXISTS (${this.select(condition.related.subquery, undefined, undefined)})`;
306
- }
307
- }
308
- correlate(
309
- // The table being correlated could be aliased to some other name
310
- // in the case of a junction. Hence we pass `xTableAlias`. The original
311
- // name of the table is required so we can look up the server names of the columns
312
- // to be used in the correlation.
313
- parentTable, parentTableAlias, parentColumns, childTable, childTableAlias, childColumns) {
314
- return sql.join(zip(parentColumns, childColumns).map(([parentColumn, childColumn]) => sql `${sql.ident(parentTableAlias)}.${this.#mapColumnNoAlias(parentTable, parentColumn)} = ${sql.ident(childTableAlias)}.${this.#mapColumnNoAlias(childTable, childColumn)}`), ' AND ');
315
- }
316
- #mapColumn(table, column) {
317
- const mapped = this.#nameMapper.columnName(table, column);
318
- if (mapped === column) {
319
- return sql.ident(column);
320
- }
321
- return sql `${sql.ident(mapped)} as ${sql.ident(column)}`;
322
- }
323
- #mapColumnNoAlias(table, column) {
324
- const mapped = this.#nameMapper.columnName(table, column);
325
- return sql.ident(mapped);
326
- }
327
- #mapTable(table) {
328
- const mapped = this.#nameMapper.tableName(table);
329
- if (mapped === table) {
330
- return sql.ident(table);
331
- }
332
- return sql `${sql.ident(mapped)} as ${sql.ident(table)}`;
333
- }
334
- #mapTableNoAlias(table) {
335
- const mapped = this.#nameMapper.tableName(table);
336
- return sql.ident(mapped);
337
- }
338
- #selectCol(table, column, tableAlias) {
339
- const serverColumnSchema = this.#serverSchema[this.#nameMapper.tableName(table)][this.#nameMapper.columnName(table, column)];
340
- const serverType = serverColumnSchema.type;
341
- if (!serverColumnSchema.isEnum &&
342
- (serverType === 'date' ||
343
- serverType === 'timestamp' ||
344
- serverType === 'timestamp without time zone' ||
345
- serverType === 'timestamptz' ||
346
- serverType === 'timestamp with time zone')) {
347
- return sql `EXTRACT(EPOCH FROM ${sql.ident(tableAlias ?? table)}.${this.#mapColumnNoAlias(table, column)}) * 1000 as ${sql.ident(column)}`;
348
- }
349
- return sql `${sql.ident(tableAlias ?? table)}.${this.#mapColumn(table, column)}`;
350
- }
351
- #toJSON(table, singular = false) {
352
- return sql `${singular ? sql `` : sql `COALESCE(json_agg`}(row_to_json(${sql.ident(table)}))${singular ? sql `` : sql `, '[]'::json)`}`;
353
- }
354
- }
355
365
  //# sourceMappingURL=compiler.js.map