@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.
- package/out/{chunk-W7REZ3GY.js → chunk-CV5PHAED.js} +6 -6
- package/out/{chunk-W7REZ3GY.js.map → chunk-CV5PHAED.js.map} +2 -2
- package/out/{chunk-2QVFOPGX.js → chunk-FIIHDLSZ.js} +1 -1
- package/out/chunk-FIIHDLSZ.js.map +7 -0
- package/out/{chunk-I5KZ62HL.js → chunk-T42I73ZN.js} +2 -2
- package/out/{inspector-T4TQPT2K.js → inspector-RKVBTGL6.js} +3 -3
- package/out/react.js +1 -1
- package/out/shared/src/arrays.d.ts +1 -0
- package/out/shared/src/arrays.d.ts.map +1 -1
- package/out/shared/src/arrays.js +6 -0
- package/out/shared/src/arrays.js.map +1 -1
- package/out/solid.js +3 -3
- package/out/z2s/src/compiler.d.ts +45 -50
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +310 -300
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +118 -2
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +21 -1
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +1 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.js +1 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/runner/config.d.ts +115 -17
- package/out/zero-cache/src/server/runner/config.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/config.js +2 -2
- package/out/zero-cache/src/server/runner/config.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts +2 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -2
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/dispatcher/dispatcher.d.ts +2 -1
- package/out/zero-cache/src/services/dispatcher/dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/services/dispatcher/dispatcher.js +2 -2
- package/out/zero-cache/src/services/dispatcher/dispatcher.js.map +1 -1
- package/out/zero-cache/src/services/http-service.d.ts +2 -1
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +6 -1
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/statz.d.ts +4 -0
- package/out/zero-cache/src/services/statz.d.ts.map +1 -0
- package/out/{analyze-query/src/bin-cache-stats.js → zero-cache/src/services/statz.js} +100 -103
- package/out/zero-cache/src/services/statz.js.map +1 -0
- package/out/zero-cache/src/types/error-for-client.d.ts +1 -1
- package/out/zero-client/src/client/server-option.d.ts.map +1 -1
- package/out/zero-pg/src/query.js +1 -1
- package/out/zero-pg/src/query.js.map +1 -1
- package/out/zero.js +3 -3
- package/package.json +5 -4
- package/out/analyze-query/src/bin-cache-stats.d.ts +0 -2
- package/out/analyze-query/src/bin-cache-stats.d.ts.map +0 -1
- package/out/analyze-query/src/bin-cache-stats.js.map +0 -1
- package/out/chunk-2QVFOPGX.js.map +0 -7
- package/out/zero/src/cache-stats.d.ts +0 -3
- package/out/zero/src/cache-stats.d.ts.map +0 -1
- package/out/zero/src/cache-stats.js +0 -3
- package/out/zero/src/cache-stats.js.map +0 -1
- /package/out/{chunk-I5KZ62HL.js.map → chunk-T42I73ZN.js.map} +0 -0
- /package/out/{inspector-T4TQPT2K.js.map → inspector-RKVBTGL6.js.map} +0 -0
package/out/z2s/src/compiler.js
CHANGED
|
@@ -1,13 +1,318 @@
|
|
|
1
|
-
import {
|
|
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
|