@dbsp/adapter-pgsql 1.1.0 → 1.1.1
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/dist/index.js +2381 -2224
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -94,1854 +94,2309 @@ var init_naming_plugin = __esm({
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
// src/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
stmt.returningList = returning;
|
|
97
|
+
// src/validate.ts
|
|
98
|
+
function validateIdentifier(value, type) {
|
|
99
|
+
if (!value || value.length === 0) {
|
|
100
|
+
throw new InvalidIdentifierError(value, type, "cannot be empty");
|
|
102
101
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
if (value.length > 63) {
|
|
103
|
+
throw new InvalidIdentifierError(
|
|
104
|
+
value,
|
|
105
|
+
type,
|
|
106
|
+
`exceeds maximum length of 63 characters (got ${value.length})`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const validIdentifierPattern = /^[a-zA-Z_][a-zA-Z0-9_$]*$/;
|
|
110
|
+
if (!validIdentifierPattern.test(value)) {
|
|
111
|
+
if (/[\x00-\x1f\x7f]/.test(value)) {
|
|
112
|
+
throw new InvalidIdentifierError(
|
|
113
|
+
value,
|
|
114
|
+
type,
|
|
115
|
+
"contains control characters"
|
|
116
|
+
);
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
if (/^[0-9]/.test(value)) {
|
|
119
|
+
throw new InvalidIdentifierError(
|
|
120
|
+
value,
|
|
121
|
+
type,
|
|
122
|
+
"cannot start with a digit"
|
|
123
|
+
);
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
if (/[^\w$]/.test(value)) {
|
|
126
|
+
throw new InvalidIdentifierError(
|
|
127
|
+
value,
|
|
128
|
+
type,
|
|
129
|
+
"contains invalid characters (only letters, digits, underscore, and $ allowed)"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
throw new InvalidIdentifierError(
|
|
133
|
+
value,
|
|
134
|
+
type,
|
|
135
|
+
"does not match valid identifier pattern"
|
|
136
|
+
);
|
|
131
137
|
}
|
|
132
|
-
if (
|
|
133
|
-
|
|
138
|
+
if (value.includes("\0")) {
|
|
139
|
+
throw new InvalidIdentifierError(value, type, "contains null byte");
|
|
134
140
|
}
|
|
135
|
-
fields.push(stringNode(naming.toDatabase(column)));
|
|
136
|
-
return { ColumnRef: { fields } };
|
|
137
141
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
if (table) {
|
|
141
|
-
fields.push(stringNode(naming.toDatabase(table)));
|
|
142
|
-
}
|
|
143
|
-
fields.push({ A_Star: {} });
|
|
144
|
-
return { ColumnRef: { fields } };
|
|
142
|
+
function isReservedKeyword(value) {
|
|
143
|
+
return SQL_RESERVED_KEYWORDS.has(value.toLowerCase());
|
|
145
144
|
}
|
|
146
|
-
function
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
};
|
|
152
|
-
if (schema) {
|
|
153
|
-
rv.schemaname = naming.toDatabase(schema);
|
|
154
|
-
}
|
|
155
|
-
if (alias) {
|
|
156
|
-
rv.alias = { aliasname: naming.toDatabase(alias) };
|
|
145
|
+
function validateQualifiedIdentifier(schemaTable) {
|
|
146
|
+
const parts = schemaTable.split(".");
|
|
147
|
+
if (parts.length === 1) {
|
|
148
|
+
validateIdentifier(parts[0], "table");
|
|
149
|
+
return { table: parts[0] };
|
|
157
150
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
if (parts.length === 2) {
|
|
152
|
+
const schema = parts[0];
|
|
153
|
+
const table = parts[1];
|
|
154
|
+
validateIdentifier(schema, "schema");
|
|
155
|
+
validateIdentifier(table, "table");
|
|
156
|
+
return { schema, table };
|
|
164
157
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
function starTarget(table, naming = identityNaming) {
|
|
171
|
-
return resTarget(columnRefStar(table, naming));
|
|
172
|
-
}
|
|
173
|
-
function binaryExpr(operator, left, right, kind = "AEXPR_OP") {
|
|
174
|
-
const expr = {
|
|
175
|
-
kind,
|
|
176
|
-
name: [stringNode(operator)],
|
|
177
|
-
lexpr: left,
|
|
178
|
-
rexpr: right
|
|
179
|
-
};
|
|
180
|
-
return { A_Expr: expr };
|
|
181
|
-
}
|
|
182
|
-
function eqExpr(left, right) {
|
|
183
|
-
return binaryExpr("=", left, right);
|
|
184
|
-
}
|
|
185
|
-
function fkCorrelation(col1, alias1, col2, alias2, naming) {
|
|
186
|
-
return eqExpr(
|
|
187
|
-
columnRef(col1, alias1, void 0, naming),
|
|
188
|
-
columnRef(col2, alias2, void 0, naming)
|
|
158
|
+
throw new InvalidIdentifierError(
|
|
159
|
+
schemaTable,
|
|
160
|
+
"table",
|
|
161
|
+
"too many dots in qualified name (expected schema.table or table)"
|
|
189
162
|
);
|
|
190
163
|
}
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
function lteExpr(left, right) {
|
|
198
|
-
return binaryExpr("<=", left, right);
|
|
199
|
-
}
|
|
200
|
-
function gtExpr(left, right) {
|
|
201
|
-
return binaryExpr(">", left, right);
|
|
202
|
-
}
|
|
203
|
-
function gteExpr(left, right) {
|
|
204
|
-
return binaryExpr(">=", left, right);
|
|
205
|
-
}
|
|
206
|
-
function likeExpr(left, right) {
|
|
207
|
-
return binaryExpr("~~", left, right, "AEXPR_LIKE");
|
|
208
|
-
}
|
|
209
|
-
function ilikeExpr(left, right) {
|
|
210
|
-
return binaryExpr("~~*", left, right, "AEXPR_ILIKE");
|
|
211
|
-
}
|
|
212
|
-
function boolExpr(type, args) {
|
|
213
|
-
const expr = {
|
|
214
|
-
boolop: type,
|
|
215
|
-
args
|
|
216
|
-
};
|
|
217
|
-
return { BoolExpr: expr };
|
|
218
|
-
}
|
|
219
|
-
function andExpr(...args) {
|
|
220
|
-
return boolExpr("AND_EXPR", args);
|
|
221
|
-
}
|
|
222
|
-
function orExpr(...args) {
|
|
223
|
-
return boolExpr("OR_EXPR", args);
|
|
164
|
+
function validateIdentifiers(identifiers) {
|
|
165
|
+
for (const [value, type] of Object.entries(identifiers)) {
|
|
166
|
+
if (value) {
|
|
167
|
+
validateIdentifier(value, type);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
224
170
|
}
|
|
225
|
-
function
|
|
226
|
-
return
|
|
171
|
+
function sanitizeForDisplay(value) {
|
|
172
|
+
return value.replace(/[\x00-\x1f\x7f]/g, "?").slice(0, 100);
|
|
227
173
|
}
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
typemod: -1
|
|
232
|
-
};
|
|
233
|
-
if (isArray) {
|
|
234
|
-
tn.arrayBounds = [integerNode(-1)];
|
|
174
|
+
function validateExtensionName(name, context = "extension") {
|
|
175
|
+
if (!name || name.length === 0) {
|
|
176
|
+
throw new InvalidIdentifierError(name, context, "cannot be empty");
|
|
235
177
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
function funcCall(name, args = [], options = {}) {
|
|
243
|
-
const names = Array.isArray(name) ? name.map(stringNode) : [stringNode(name)];
|
|
244
|
-
const fc = {
|
|
245
|
-
funcname: names
|
|
246
|
-
};
|
|
247
|
-
if (options.star) {
|
|
248
|
-
fc.agg_star = true;
|
|
249
|
-
} else if (args.length > 0) {
|
|
250
|
-
fc.args = args;
|
|
178
|
+
if (name.length > 63) {
|
|
179
|
+
throw new InvalidIdentifierError(
|
|
180
|
+
name,
|
|
181
|
+
context,
|
|
182
|
+
`exceeds maximum length of 63 characters (got ${name.length})`
|
|
183
|
+
);
|
|
251
184
|
}
|
|
252
|
-
if (
|
|
253
|
-
|
|
185
|
+
if (/[\x00-\x1f\x7f]/.test(name)) {
|
|
186
|
+
throw new InvalidIdentifierError(
|
|
187
|
+
name,
|
|
188
|
+
context,
|
|
189
|
+
"contains control characters"
|
|
190
|
+
);
|
|
254
191
|
}
|
|
255
|
-
if (
|
|
256
|
-
|
|
192
|
+
if (/[\\]/.test(name)) {
|
|
193
|
+
throw new InvalidIdentifierError(
|
|
194
|
+
name,
|
|
195
|
+
context,
|
|
196
|
+
"contains backslash (forbidden in extension names)"
|
|
197
|
+
);
|
|
257
198
|
}
|
|
258
|
-
if (
|
|
259
|
-
|
|
199
|
+
if (/"/.test(name)) {
|
|
200
|
+
throw new InvalidIdentifierError(
|
|
201
|
+
name,
|
|
202
|
+
context,
|
|
203
|
+
"contains double-quote (identifier injection risk)"
|
|
204
|
+
);
|
|
260
205
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return funcCall("coalesce", args);
|
|
268
|
-
}
|
|
269
|
-
function sortBy(expr, direction = "DEFAULT", nulls = "DEFAULT") {
|
|
270
|
-
const sb = {
|
|
271
|
-
node: expr,
|
|
272
|
-
sortby_dir: direction === "ASC" ? "SORTBY_ASC" : direction === "DESC" ? "SORTBY_DESC" : "SORTBY_DEFAULT",
|
|
273
|
-
sortby_nulls: nulls === "FIRST" ? "SORTBY_NULLS_FIRST" : nulls === "LAST" ? "SORTBY_NULLS_LAST" : "SORTBY_NULLS_DEFAULT"
|
|
274
|
-
};
|
|
275
|
-
return { SortBy: sb };
|
|
276
|
-
}
|
|
277
|
-
function joinExpr(joinType, left, right, quals, alias) {
|
|
278
|
-
const je = {
|
|
279
|
-
jointype: joinType,
|
|
280
|
-
larg: left,
|
|
281
|
-
rarg: right
|
|
282
|
-
};
|
|
283
|
-
if (quals) {
|
|
284
|
-
je.quals = quals;
|
|
206
|
+
if (/'/.test(name)) {
|
|
207
|
+
throw new InvalidIdentifierError(
|
|
208
|
+
name,
|
|
209
|
+
context,
|
|
210
|
+
"contains single-quote (string injection risk)"
|
|
211
|
+
);
|
|
285
212
|
}
|
|
286
|
-
if (
|
|
287
|
-
|
|
213
|
+
if (/;/.test(name)) {
|
|
214
|
+
throw new InvalidIdentifierError(
|
|
215
|
+
name,
|
|
216
|
+
context,
|
|
217
|
+
"contains semicolon (statement injection risk)"
|
|
218
|
+
);
|
|
288
219
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return joinExpr("JOIN_LEFT", left, right, on, alias);
|
|
296
|
-
}
|
|
297
|
-
function selectStmt(options) {
|
|
298
|
-
const stmt = {
|
|
299
|
-
targetList: options.targetList
|
|
300
|
-
};
|
|
301
|
-
if (options.from && options.from.length > 0) {
|
|
302
|
-
stmt.fromClause = options.from;
|
|
220
|
+
if (/--/.test(name)) {
|
|
221
|
+
throw new InvalidIdentifierError(
|
|
222
|
+
name,
|
|
223
|
+
context,
|
|
224
|
+
"contains line-comment marker (--)"
|
|
225
|
+
);
|
|
303
226
|
}
|
|
304
|
-
if (
|
|
305
|
-
|
|
227
|
+
if (/\/\*/.test(name)) {
|
|
228
|
+
throw new InvalidIdentifierError(
|
|
229
|
+
name,
|
|
230
|
+
context,
|
|
231
|
+
"contains block-comment opener (/*)"
|
|
232
|
+
);
|
|
306
233
|
}
|
|
307
|
-
if (
|
|
308
|
-
|
|
234
|
+
if (/\*\//.test(name)) {
|
|
235
|
+
throw new InvalidIdentifierError(
|
|
236
|
+
name,
|
|
237
|
+
context,
|
|
238
|
+
"contains block-comment closer (*/)"
|
|
239
|
+
);
|
|
309
240
|
}
|
|
310
|
-
if (
|
|
311
|
-
|
|
241
|
+
if (/\$\$/.test(name)) {
|
|
242
|
+
throw new InvalidIdentifierError(
|
|
243
|
+
name,
|
|
244
|
+
context,
|
|
245
|
+
"contains dollar-quoting ($$)"
|
|
246
|
+
);
|
|
312
247
|
}
|
|
313
|
-
if (
|
|
314
|
-
|
|
248
|
+
if (/\s/.test(name)) {
|
|
249
|
+
throw new InvalidIdentifierError(name, context, "contains whitespace");
|
|
315
250
|
}
|
|
316
|
-
if (
|
|
317
|
-
|
|
251
|
+
if (!/^[a-zA-Z0-9_\-.]+$/.test(name)) {
|
|
252
|
+
throw new InvalidIdentifierError(
|
|
253
|
+
name,
|
|
254
|
+
context,
|
|
255
|
+
"contains characters not allowed in extension names (only letters, digits, underscore, hyphen, and dot allowed)"
|
|
256
|
+
);
|
|
318
257
|
}
|
|
319
|
-
|
|
320
|
-
|
|
258
|
+
}
|
|
259
|
+
function validateCollationName(name, context = "collation") {
|
|
260
|
+
if (!name || name.length === 0) {
|
|
261
|
+
throw new InvalidIdentifierError(name, context, "cannot be empty");
|
|
321
262
|
}
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
263
|
+
if (Buffer.byteLength(name, "utf8") > 63) {
|
|
264
|
+
throw new InvalidIdentifierError(
|
|
265
|
+
name,
|
|
266
|
+
context,
|
|
267
|
+
`exceeds maximum length of 63 bytes (got ${Buffer.byteLength(name, "utf8")})`
|
|
268
|
+
);
|
|
326
269
|
}
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
270
|
+
if (/\x00/.test(name)) {
|
|
271
|
+
throw new InvalidIdentifierError(
|
|
272
|
+
name,
|
|
273
|
+
context,
|
|
274
|
+
"contains NUL byte (\\x00) which would be silently truncated by PostgreSQL"
|
|
275
|
+
);
|
|
332
276
|
}
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
clause.lockedRels = options.lockingClause.lockedRels;
|
|
340
|
-
}
|
|
341
|
-
stmt.lockingClause = [{ LockingClause: clause }];
|
|
277
|
+
if (/[\x01-\x1f\x7f]/.test(name)) {
|
|
278
|
+
throw new InvalidIdentifierError(
|
|
279
|
+
name,
|
|
280
|
+
context,
|
|
281
|
+
"contains control characters (only printable characters allowed)"
|
|
282
|
+
);
|
|
342
283
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
function insertStmt(options) {
|
|
352
|
-
const naming = options.naming ?? identityNaming;
|
|
353
|
-
const relation = {
|
|
354
|
-
relname: naming.toDatabase(options.table),
|
|
355
|
-
inh: true,
|
|
356
|
-
relpersistence: "p"
|
|
357
|
-
};
|
|
358
|
-
if (options.schema) {
|
|
359
|
-
relation.schemaname = naming.toDatabase(options.schema);
|
|
284
|
+
if (/[\\]/.test(name)) {
|
|
285
|
+
throw new InvalidIdentifierError(
|
|
286
|
+
name,
|
|
287
|
+
context,
|
|
288
|
+
"contains backslash (forbidden in collation names)"
|
|
289
|
+
);
|
|
360
290
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}));
|
|
291
|
+
if (/"/.test(name)) {
|
|
292
|
+
throw new InvalidIdentifierError(
|
|
293
|
+
name,
|
|
294
|
+
context,
|
|
295
|
+
"contains double-quote (identifier injection risk)"
|
|
296
|
+
);
|
|
368
297
|
}
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
};
|
|
298
|
+
if (/'/.test(name)) {
|
|
299
|
+
throw new InvalidIdentifierError(
|
|
300
|
+
name,
|
|
301
|
+
context,
|
|
302
|
+
"contains single-quote (string injection risk)"
|
|
303
|
+
);
|
|
377
304
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
relname: naming.toDatabase(options.table),
|
|
385
|
-
inh: true,
|
|
386
|
-
relpersistence: "p"
|
|
387
|
-
};
|
|
388
|
-
if (options.schema) {
|
|
389
|
-
relation.schemaname = naming.toDatabase(options.schema);
|
|
305
|
+
if (/;/.test(name)) {
|
|
306
|
+
throw new InvalidIdentifierError(
|
|
307
|
+
name,
|
|
308
|
+
context,
|
|
309
|
+
"contains semicolon (statement injection risk)"
|
|
310
|
+
);
|
|
390
311
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
}))
|
|
399
|
-
};
|
|
400
|
-
if (options.where) {
|
|
401
|
-
stmt.whereClause = options.where;
|
|
312
|
+
if (/--/.test(name)) {
|
|
313
|
+
throw new InvalidIdentifierError(
|
|
314
|
+
name,
|
|
315
|
+
context,
|
|
316
|
+
"contains line-comment marker (--)"
|
|
317
|
+
);
|
|
402
318
|
}
|
|
403
|
-
if (
|
|
404
|
-
|
|
319
|
+
if (/\/\*/.test(name)) {
|
|
320
|
+
throw new InvalidIdentifierError(
|
|
321
|
+
name,
|
|
322
|
+
context,
|
|
323
|
+
"contains block-comment opener (/*)"
|
|
324
|
+
);
|
|
405
325
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
relname: naming.toDatabase(options.table),
|
|
413
|
-
inh: true,
|
|
414
|
-
relpersistence: "p"
|
|
415
|
-
};
|
|
416
|
-
if (options.schema) {
|
|
417
|
-
relation.schemaname = naming.toDatabase(options.schema);
|
|
326
|
+
if (/\*\//.test(name)) {
|
|
327
|
+
throw new InvalidIdentifierError(
|
|
328
|
+
name,
|
|
329
|
+
context,
|
|
330
|
+
"contains block-comment closer (*/)"
|
|
331
|
+
);
|
|
418
332
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
333
|
+
if (/\$\$/.test(name)) {
|
|
334
|
+
throw new InvalidIdentifierError(
|
|
335
|
+
name,
|
|
336
|
+
context,
|
|
337
|
+
"contains dollar-quoting ($$)"
|
|
338
|
+
);
|
|
424
339
|
}
|
|
425
|
-
if (
|
|
426
|
-
|
|
340
|
+
if (/\s/.test(name)) {
|
|
341
|
+
throw new InvalidIdentifierError(name, context, "contains whitespace");
|
|
342
|
+
}
|
|
343
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.-]*(?:@[A-Za-z0-9-]{1,10})?$/.test(name)) {
|
|
344
|
+
throw new InvalidIdentifierError(
|
|
345
|
+
name,
|
|
346
|
+
context,
|
|
347
|
+
"contains characters not allowed in collation names (only letters, digits, underscore, hyphen, and dot allowed; optional @modifier must be 1-10 alphanumeric/hyphen characters, e.g. @euro, @latin9, @iso8859-15)"
|
|
348
|
+
);
|
|
427
349
|
}
|
|
428
|
-
applyReturningList(stmt, options.returning);
|
|
429
|
-
return { DeleteStmt: stmt };
|
|
430
350
|
}
|
|
431
|
-
function
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (hasProjection) {
|
|
437
|
-
const projArgs = [];
|
|
438
|
-
for (const col of cols) {
|
|
439
|
-
projArgs.push({ A_Const: { sval: { sval: naming.toDatabase(col) } } });
|
|
440
|
-
projArgs.push(columnRef(col, targetAlias, void 0, naming));
|
|
441
|
-
}
|
|
442
|
-
toJsonbCall = {
|
|
443
|
-
FuncCall: {
|
|
444
|
-
funcname: [stringNode("jsonb_build_object")],
|
|
445
|
-
args: projArgs
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
} else {
|
|
449
|
-
const rowRef = {
|
|
450
|
-
ColumnRef: {
|
|
451
|
-
fields: [stringNode(targetAlias)]
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
toJsonbCall = {
|
|
455
|
-
FuncCall: {
|
|
456
|
-
funcname: [stringNode("to_jsonb")],
|
|
457
|
-
args: [rowRef]
|
|
458
|
-
}
|
|
459
|
-
};
|
|
351
|
+
function validateSqlExpression(sql, context) {
|
|
352
|
+
if (/[;]|--|\/\*|\*\/|\$\$|\\/.test(sql)) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Unsafe SQL expression in ${context}: contains forbidden characters (;, --, /*, */, "$$" (dollar-quoted strings), \\). Value: "${sql}"`
|
|
355
|
+
);
|
|
460
356
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
357
|
+
}
|
|
358
|
+
function validateDbTypeName(type) {
|
|
359
|
+
if (!SAFE_TYPE_PATTERN.test(type)) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Unsafe database type name: "${type}". Must match PostgreSQL type name rules.`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
return type;
|
|
365
|
+
}
|
|
366
|
+
var SQL_RESERVED_KEYWORDS, InvalidIdentifierError, SAFE_TYPE_PATTERN;
|
|
367
|
+
var init_validate = __esm({
|
|
368
|
+
"src/validate.ts"() {
|
|
369
|
+
"use strict";
|
|
370
|
+
SQL_RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
371
|
+
"all",
|
|
372
|
+
"analyse",
|
|
373
|
+
"analyze",
|
|
374
|
+
"and",
|
|
375
|
+
"any",
|
|
376
|
+
"array",
|
|
377
|
+
"as",
|
|
378
|
+
"asc",
|
|
379
|
+
"asymmetric",
|
|
380
|
+
"authorization",
|
|
381
|
+
"binary",
|
|
382
|
+
"both",
|
|
383
|
+
"case",
|
|
384
|
+
"cast",
|
|
385
|
+
"check",
|
|
386
|
+
"collate",
|
|
387
|
+
"collation",
|
|
388
|
+
"column",
|
|
389
|
+
"concurrently",
|
|
390
|
+
"constraint",
|
|
391
|
+
"create",
|
|
392
|
+
"cross",
|
|
393
|
+
"current_catalog",
|
|
394
|
+
"current_date",
|
|
395
|
+
"current_role",
|
|
396
|
+
"current_schema",
|
|
397
|
+
"current_time",
|
|
398
|
+
"current_timestamp",
|
|
399
|
+
"current_user",
|
|
400
|
+
"default",
|
|
401
|
+
"deferrable",
|
|
402
|
+
"desc",
|
|
403
|
+
"distinct",
|
|
404
|
+
"do",
|
|
405
|
+
"else",
|
|
406
|
+
"end",
|
|
407
|
+
"except",
|
|
408
|
+
"false",
|
|
409
|
+
"fetch",
|
|
410
|
+
"for",
|
|
411
|
+
"foreign",
|
|
412
|
+
"freeze",
|
|
413
|
+
"from",
|
|
414
|
+
"full",
|
|
415
|
+
"grant",
|
|
416
|
+
"group",
|
|
417
|
+
"having",
|
|
418
|
+
"ilike",
|
|
419
|
+
"in",
|
|
420
|
+
"initially",
|
|
421
|
+
"inner",
|
|
422
|
+
"intersect",
|
|
423
|
+
"into",
|
|
424
|
+
"is",
|
|
425
|
+
"isnull",
|
|
426
|
+
"join",
|
|
427
|
+
"lateral",
|
|
428
|
+
"leading",
|
|
429
|
+
"left",
|
|
430
|
+
"like",
|
|
431
|
+
"limit",
|
|
432
|
+
"localtime",
|
|
433
|
+
"localtimestamp",
|
|
434
|
+
"natural",
|
|
435
|
+
"not",
|
|
436
|
+
"notnull",
|
|
437
|
+
"null",
|
|
438
|
+
"offset",
|
|
439
|
+
"on",
|
|
440
|
+
"only",
|
|
441
|
+
"or",
|
|
442
|
+
"order",
|
|
443
|
+
"outer",
|
|
444
|
+
"overlaps",
|
|
445
|
+
"placing",
|
|
446
|
+
"primary",
|
|
447
|
+
"references",
|
|
448
|
+
"returning",
|
|
449
|
+
"right",
|
|
450
|
+
"select",
|
|
451
|
+
"session_user",
|
|
452
|
+
"similar",
|
|
453
|
+
"some",
|
|
454
|
+
"symmetric",
|
|
455
|
+
"table",
|
|
456
|
+
"timestamp",
|
|
457
|
+
"tablesample",
|
|
458
|
+
"then",
|
|
459
|
+
"to",
|
|
460
|
+
"trailing",
|
|
461
|
+
"true",
|
|
462
|
+
"union",
|
|
463
|
+
"unique",
|
|
464
|
+
"user",
|
|
465
|
+
"using",
|
|
466
|
+
"variadic",
|
|
467
|
+
"verbose",
|
|
468
|
+
"when",
|
|
469
|
+
"where",
|
|
470
|
+
"window",
|
|
471
|
+
"with"
|
|
472
|
+
]);
|
|
473
|
+
InvalidIdentifierError = class extends Error {
|
|
474
|
+
constructor(identifier, identifierType, reason) {
|
|
475
|
+
super(`Invalid ${identifierType} identifier "${identifier}": ${reason}`);
|
|
476
|
+
this.identifier = identifier;
|
|
477
|
+
this.identifierType = identifierType;
|
|
478
|
+
this.reason = reason;
|
|
479
|
+
this.name = "InvalidIdentifierError";
|
|
479
480
|
}
|
|
481
|
+
identifier;
|
|
482
|
+
identifierType;
|
|
483
|
+
reason;
|
|
480
484
|
};
|
|
485
|
+
SAFE_TYPE_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_ ]*(\(\d+(,\s*\d+)?\))?(\[\])?$/;
|
|
481
486
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
TypeCast: {
|
|
505
|
-
arg: { A_Const: { sval: { sval: "[]" } } },
|
|
506
|
-
typeName: {
|
|
507
|
-
names: [stringNode("json")],
|
|
508
|
-
typemod: -1
|
|
509
|
-
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// src/ast-helpers.ts
|
|
490
|
+
import { normalizeSQL } from "@dbsp/core";
|
|
491
|
+
function applyReturningList(stmt, returning) {
|
|
492
|
+
if (returning && returning.length > 0) {
|
|
493
|
+
stmt.returningList = returning;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function stringNode(value) {
|
|
497
|
+
return { String: { sval: value } };
|
|
498
|
+
}
|
|
499
|
+
function integerNode(value) {
|
|
500
|
+
return { Integer: { ival: value } };
|
|
501
|
+
}
|
|
502
|
+
function floatNode(value) {
|
|
503
|
+
return { Float: { fval: value } };
|
|
504
|
+
}
|
|
505
|
+
function booleanConstNode(value) {
|
|
506
|
+
return {
|
|
507
|
+
A_Const: {
|
|
508
|
+
boolval: { boolval: value }
|
|
510
509
|
}
|
|
511
510
|
};
|
|
512
|
-
|
|
511
|
+
}
|
|
512
|
+
function nullConstNode() {
|
|
513
513
|
return {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
name: alias
|
|
514
|
+
A_Const: {
|
|
515
|
+
isnull: true
|
|
517
516
|
}
|
|
518
517
|
};
|
|
519
518
|
}
|
|
520
|
-
function
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
var STRENGTH_MAP, POLICY_MAP;
|
|
527
|
-
var init_ast_helpers = __esm({
|
|
528
|
-
"src/ast-helpers.ts"() {
|
|
529
|
-
"use strict";
|
|
530
|
-
init_naming_plugin();
|
|
531
|
-
STRENGTH_MAP = {
|
|
532
|
-
forUpdate: "LCS_FORUPDATE",
|
|
533
|
-
forNoKeyUpdate: "LCS_FORNOKEYUPDATE",
|
|
534
|
-
forShare: "LCS_FORSHARE",
|
|
535
|
-
forKeyShare: "LCS_FORKEYSHARE"
|
|
536
|
-
};
|
|
537
|
-
POLICY_MAP = {
|
|
538
|
-
block: "LockWaitBlock",
|
|
539
|
-
skipLocked: "LockWaitSkip",
|
|
540
|
-
noWait: "LockWaitError"
|
|
541
|
-
};
|
|
519
|
+
function columnRef(column, table, schema, naming = identityNaming) {
|
|
520
|
+
const fields = [];
|
|
521
|
+
if (schema) {
|
|
522
|
+
const dbSchema = naming.toDatabase(schema);
|
|
523
|
+
validateIdentifier(dbSchema, "schema");
|
|
524
|
+
fields.push(stringNode(dbSchema));
|
|
542
525
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const errors = [];
|
|
548
|
-
if (paramRef.number === void 0) {
|
|
549
|
-
errors.push("ParamRef.number is required");
|
|
550
|
-
} else if (!Number.isInteger(paramRef.number)) {
|
|
551
|
-
errors.push(`ParamRef.number must be an integer, got: ${paramRef.number}`);
|
|
552
|
-
} else if (paramRef.number < 1) {
|
|
553
|
-
errors.push(
|
|
554
|
-
`ParamRef.number must be >= 1 (1-based indexing), got: ${paramRef.number}`
|
|
555
|
-
);
|
|
556
|
-
} else if (paramRef.number > 65535) {
|
|
557
|
-
errors.push(
|
|
558
|
-
`ParamRef.number exceeds maximum (65535), got: ${paramRef.number}`
|
|
559
|
-
);
|
|
526
|
+
if (table) {
|
|
527
|
+
const dbTable = naming.toDatabase(table);
|
|
528
|
+
validateIdentifier(dbTable, "table");
|
|
529
|
+
fields.push(stringNode(dbTable));
|
|
560
530
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
531
|
+
const dbColumn = naming.toDatabase(column);
|
|
532
|
+
if (dbColumn !== "*" && !/^\d+$/.test(dbColumn)) {
|
|
533
|
+
validateIdentifier(dbColumn, "column");
|
|
534
|
+
}
|
|
535
|
+
fields.push(stringNode(dbColumn));
|
|
536
|
+
return { ColumnRef: { fields } };
|
|
565
537
|
}
|
|
566
|
-
function
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
throw new Error(`Invalid ParamRef: ${result.errors.join(", ")}`);
|
|
538
|
+
function columnRefStar(table, naming = identityNaming) {
|
|
539
|
+
const fields = [];
|
|
540
|
+
if (table) {
|
|
541
|
+
fields.push(stringNode(naming.toDatabase(table)));
|
|
571
542
|
}
|
|
572
|
-
|
|
543
|
+
fields.push({ A_Star: {} });
|
|
544
|
+
return { ColumnRef: { fields } };
|
|
573
545
|
}
|
|
574
|
-
function
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
names: [{ String: { sval: typeName } }],
|
|
582
|
-
typemod: -1
|
|
583
|
-
};
|
|
584
|
-
const typeCast2 = location !== void 0 ? {
|
|
585
|
-
arg: paramRefNode,
|
|
586
|
-
typeName: typeNameNode,
|
|
587
|
-
location
|
|
588
|
-
} : {
|
|
589
|
-
arg: paramRefNode,
|
|
590
|
-
typeName: typeNameNode
|
|
546
|
+
function rangeVar(table, alias, schema, naming = identityNaming) {
|
|
547
|
+
const dbTable = naming.toDatabase(table);
|
|
548
|
+
validateIdentifier(dbTable, "table");
|
|
549
|
+
const rv = {
|
|
550
|
+
relname: dbTable,
|
|
551
|
+
inh: true,
|
|
552
|
+
relpersistence: "p"
|
|
591
553
|
};
|
|
592
|
-
|
|
554
|
+
if (schema) {
|
|
555
|
+
const dbSchema = naming.toDatabase(schema);
|
|
556
|
+
validateIdentifier(dbSchema, "schema");
|
|
557
|
+
rv.schemaname = dbSchema;
|
|
558
|
+
}
|
|
559
|
+
if (alias) {
|
|
560
|
+
const dbAlias = naming.toDatabase(alias);
|
|
561
|
+
validateIdentifier(dbAlias, "alias");
|
|
562
|
+
rv.alias = { aliasname: dbAlias };
|
|
563
|
+
}
|
|
564
|
+
return { RangeVar: rv };
|
|
593
565
|
}
|
|
594
|
-
function
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
566
|
+
function resTarget(val, name) {
|
|
567
|
+
const rt = { val };
|
|
568
|
+
if (name) {
|
|
569
|
+
rt.name = name;
|
|
570
|
+
}
|
|
571
|
+
return { ResTarget: rt };
|
|
572
|
+
}
|
|
573
|
+
function columnTarget(column, alias, table, naming = identityNaming) {
|
|
574
|
+
return resTarget(columnRef(column, table, void 0, naming), alias);
|
|
575
|
+
}
|
|
576
|
+
function starTarget(table, naming = identityNaming) {
|
|
577
|
+
return resTarget(columnRefStar(table, naming));
|
|
578
|
+
}
|
|
579
|
+
function binaryExpr(operator, left, right, kind = "AEXPR_OP") {
|
|
580
|
+
const expr = {
|
|
604
581
|
kind,
|
|
605
|
-
name: [
|
|
606
|
-
lexpr:
|
|
607
|
-
rexpr:
|
|
582
|
+
name: [stringNode(operator)],
|
|
583
|
+
lexpr: left,
|
|
584
|
+
rexpr: right
|
|
608
585
|
};
|
|
609
586
|
return { A_Expr: expr };
|
|
610
587
|
}
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
588
|
+
function eqExpr(left, right) {
|
|
589
|
+
return binaryExpr("=", left, right);
|
|
590
|
+
}
|
|
591
|
+
function fkCorrelation(col1, alias1, col2, alias2, naming) {
|
|
592
|
+
return eqExpr(
|
|
593
|
+
columnRef(col1, alias1, void 0, naming),
|
|
594
|
+
columnRef(col2, alias2, void 0, naming)
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
function neExpr(left, right) {
|
|
598
|
+
return binaryExpr("<>", left, right);
|
|
599
|
+
}
|
|
600
|
+
function ltExpr(left, right) {
|
|
601
|
+
return binaryExpr("<", left, right);
|
|
602
|
+
}
|
|
603
|
+
function lteExpr(left, right) {
|
|
604
|
+
return binaryExpr("<=", left, right);
|
|
605
|
+
}
|
|
606
|
+
function gtExpr(left, right) {
|
|
607
|
+
return binaryExpr(">", left, right);
|
|
608
|
+
}
|
|
609
|
+
function gteExpr(left, right) {
|
|
610
|
+
return binaryExpr(">=", left, right);
|
|
611
|
+
}
|
|
612
|
+
function likeExpr(left, right) {
|
|
613
|
+
return binaryExpr("~~", left, right, "AEXPR_LIKE");
|
|
614
|
+
}
|
|
615
|
+
function ilikeExpr(left, right) {
|
|
616
|
+
return binaryExpr("~~*", left, right, "AEXPR_ILIKE");
|
|
617
|
+
}
|
|
618
|
+
function boolExpr(type, args) {
|
|
619
|
+
const expr = {
|
|
620
|
+
boolop: type,
|
|
621
|
+
args
|
|
633
622
|
};
|
|
634
|
-
return {
|
|
623
|
+
return { BoolExpr: expr };
|
|
635
624
|
}
|
|
636
|
-
function
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
});
|
|
653
|
-
} else if (typeof value === "object") {
|
|
654
|
-
traverse(value, `${path}.${key}`);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
625
|
+
function andExpr(...args) {
|
|
626
|
+
return boolExpr("AND_EXPR", args);
|
|
627
|
+
}
|
|
628
|
+
function orExpr(...args) {
|
|
629
|
+
return boolExpr("OR_EXPR", args);
|
|
630
|
+
}
|
|
631
|
+
function notExpr(arg) {
|
|
632
|
+
return boolExpr("NOT_EXPR", [arg]);
|
|
633
|
+
}
|
|
634
|
+
function typeCast(arg, typeName, isArray = false) {
|
|
635
|
+
const tn = {
|
|
636
|
+
names: [stringNode(typeName)],
|
|
637
|
+
typemod: -1
|
|
638
|
+
};
|
|
639
|
+
if (isArray) {
|
|
640
|
+
tn.arrayBounds = [integerNode(-1)];
|
|
658
641
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
642
|
+
const tc = {
|
|
643
|
+
arg,
|
|
644
|
+
typeName: tn
|
|
645
|
+
};
|
|
646
|
+
return { TypeCast: tc };
|
|
663
647
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
648
|
+
function funcCall(name, args = [], options = {}) {
|
|
649
|
+
const names = Array.isArray(name) ? name.map(stringNode) : [stringNode(name)];
|
|
650
|
+
const fc = {
|
|
651
|
+
funcname: names
|
|
652
|
+
};
|
|
653
|
+
if (options.star) {
|
|
654
|
+
fc.agg_star = true;
|
|
655
|
+
} else if (args.length > 0) {
|
|
656
|
+
fc.args = args;
|
|
667
657
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
// src/handlers/expression/case-value.ts
|
|
671
|
-
function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler) {
|
|
672
|
-
if (value === null || value === void 0) {
|
|
673
|
-
return nullConstNode();
|
|
658
|
+
if (options.distinct) {
|
|
659
|
+
fc.agg_distinct = true;
|
|
674
660
|
}
|
|
675
|
-
if (
|
|
676
|
-
|
|
661
|
+
if (options.orderBy && options.orderBy.length > 0) {
|
|
662
|
+
fc.agg_order = options.orderBy;
|
|
677
663
|
}
|
|
678
|
-
if (
|
|
679
|
-
|
|
680
|
-
state.parameters.push(value);
|
|
681
|
-
return createParamRef(idx);
|
|
664
|
+
if (options.filter) {
|
|
665
|
+
fc.agg_filter = options.filter;
|
|
682
666
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
667
|
+
return { FuncCall: fc };
|
|
668
|
+
}
|
|
669
|
+
function coalesceExpr(args) {
|
|
670
|
+
return { CoalesceExpr: { args } };
|
|
671
|
+
}
|
|
672
|
+
function coalesce(...args) {
|
|
673
|
+
return funcCall("coalesce", args);
|
|
674
|
+
}
|
|
675
|
+
function sortBy(expr, direction = "DEFAULT", nulls = "DEFAULT") {
|
|
676
|
+
const sb = {
|
|
677
|
+
node: expr,
|
|
678
|
+
sortby_dir: direction === "ASC" ? "SORTBY_ASC" : direction === "DESC" ? "SORTBY_DESC" : "SORTBY_DEFAULT",
|
|
679
|
+
sortby_nulls: nulls === "FIRST" ? "SORTBY_NULLS_FIRST" : nulls === "LAST" ? "SORTBY_NULLS_LAST" : "SORTBY_NULLS_DEFAULT"
|
|
680
|
+
};
|
|
681
|
+
return { SortBy: sb };
|
|
682
|
+
}
|
|
683
|
+
function joinExpr(joinType, left, right, quals, alias) {
|
|
684
|
+
const je = {
|
|
685
|
+
jointype: joinType,
|
|
686
|
+
larg: left,
|
|
687
|
+
rarg: right
|
|
688
|
+
};
|
|
689
|
+
if (quals) {
|
|
690
|
+
je.quals = quals;
|
|
691
|
+
}
|
|
692
|
+
if (alias) {
|
|
693
|
+
je.alias = { aliasname: alias };
|
|
694
|
+
}
|
|
695
|
+
return { JoinExpr: je };
|
|
696
|
+
}
|
|
697
|
+
function innerJoin(left, right, on, alias) {
|
|
698
|
+
return joinExpr("JOIN_INNER", left, right, on, alias);
|
|
699
|
+
}
|
|
700
|
+
function leftJoin(left, right, on, alias) {
|
|
701
|
+
return joinExpr("JOIN_LEFT", left, right, on, alias);
|
|
702
|
+
}
|
|
703
|
+
function selectStmt(options) {
|
|
704
|
+
const stmt = {
|
|
705
|
+
targetList: options.targetList
|
|
706
|
+
};
|
|
707
|
+
if (options.from && options.from.length > 0) {
|
|
708
|
+
stmt.fromClause = options.from;
|
|
709
|
+
}
|
|
710
|
+
if (options.where) {
|
|
711
|
+
stmt.whereClause = options.where;
|
|
712
|
+
}
|
|
713
|
+
if (options.groupBy && options.groupBy.length > 0) {
|
|
714
|
+
stmt.groupClause = options.groupBy;
|
|
715
|
+
}
|
|
716
|
+
if (options.having) {
|
|
717
|
+
stmt.havingClause = options.having;
|
|
718
|
+
}
|
|
719
|
+
if (options.orderBy && options.orderBy.length > 0) {
|
|
720
|
+
stmt.sortClause = options.orderBy;
|
|
721
|
+
}
|
|
722
|
+
if (options.limit) {
|
|
723
|
+
stmt.limitCount = options.limit;
|
|
724
|
+
}
|
|
725
|
+
if (options.offset) {
|
|
726
|
+
stmt.limitOffset = options.offset;
|
|
727
|
+
}
|
|
728
|
+
if (options.distinct === true) {
|
|
729
|
+
stmt.distinctClause = [];
|
|
730
|
+
} else if (Array.isArray(options.distinct) && options.distinct.length > 0) {
|
|
731
|
+
stmt.distinctClause = options.distinct;
|
|
732
|
+
}
|
|
733
|
+
if (options.withClause && options.withClause.ctes.length > 0) {
|
|
734
|
+
stmt.withClause = {
|
|
735
|
+
ctes: options.withClause.ctes,
|
|
736
|
+
recursive: options.withClause.recursive ?? false
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
if (options.lockingClause) {
|
|
740
|
+
const clause = {
|
|
741
|
+
strength: options.lockingClause.strength,
|
|
742
|
+
waitPolicy: options.lockingClause.waitPolicy ?? "LockWaitBlock"
|
|
743
|
+
};
|
|
744
|
+
if (options.lockingClause.lockedRels && options.lockingClause.lockedRels.length > 0) {
|
|
745
|
+
clause.lockedRels = options.lockingClause.lockedRels;
|
|
727
746
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
747
|
+
stmt.lockingClause = [{ LockingClause: clause }];
|
|
748
|
+
}
|
|
749
|
+
return { SelectStmt: stmt };
|
|
750
|
+
}
|
|
751
|
+
function mapLockToAst(lock) {
|
|
752
|
+
return {
|
|
753
|
+
strength: STRENGTH_MAP[lock.strength],
|
|
754
|
+
waitPolicy: POLICY_MAP[lock.waitPolicy]
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function insertStmt(options) {
|
|
758
|
+
const naming = options.naming ?? identityNaming;
|
|
759
|
+
const relation = {
|
|
760
|
+
relname: naming.toDatabase(options.table),
|
|
761
|
+
inh: true,
|
|
762
|
+
relpersistence: "p"
|
|
763
|
+
};
|
|
764
|
+
if (options.schema) {
|
|
765
|
+
relation.schemaname = naming.toDatabase(options.schema);
|
|
766
|
+
}
|
|
767
|
+
const stmt = {
|
|
768
|
+
relation
|
|
769
|
+
};
|
|
770
|
+
if (options.columns && options.columns.length > 0) {
|
|
771
|
+
stmt.cols = options.columns.map((col) => ({
|
|
772
|
+
ResTarget: { name: naming.toDatabase(col) }
|
|
773
|
+
}));
|
|
774
|
+
}
|
|
775
|
+
if (options.selectQuery) {
|
|
776
|
+
stmt.selectStmt = options.selectQuery;
|
|
777
|
+
} else if (options.values && options.values.length > 0) {
|
|
778
|
+
stmt.selectStmt = {
|
|
779
|
+
SelectStmt: {
|
|
780
|
+
valuesLists: options.values.map((row) => ({ List: { items: row } }))
|
|
732
781
|
}
|
|
733
|
-
|
|
734
|
-
default: {
|
|
735
|
-
const idx = ++state.paramIndex;
|
|
736
|
-
state.parameters.push(value);
|
|
737
|
-
return createParamRef(idx);
|
|
738
|
-
}
|
|
782
|
+
};
|
|
739
783
|
}
|
|
784
|
+
applyReturningList(stmt, options.returning);
|
|
785
|
+
return { InsertStmt: stmt };
|
|
740
786
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
787
|
+
function updateStmt(options) {
|
|
788
|
+
const naming = options.naming ?? identityNaming;
|
|
789
|
+
const relation = {
|
|
790
|
+
relname: naming.toDatabase(options.table),
|
|
791
|
+
inh: true,
|
|
792
|
+
relpersistence: "p"
|
|
793
|
+
};
|
|
794
|
+
if (options.schema) {
|
|
795
|
+
relation.schemaname = naming.toDatabase(options.schema);
|
|
746
796
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
797
|
+
const stmt = {
|
|
798
|
+
relation,
|
|
799
|
+
targetList: options.set.map(({ column, value }) => ({
|
|
800
|
+
ResTarget: {
|
|
801
|
+
name: naming.toDatabase(column),
|
|
802
|
+
val: value
|
|
803
|
+
}
|
|
804
|
+
}))
|
|
805
|
+
};
|
|
806
|
+
if (options.where) {
|
|
807
|
+
stmt.whereClause = options.where;
|
|
808
|
+
}
|
|
809
|
+
if (options.from && options.from.length > 0) {
|
|
810
|
+
stmt.fromClause = options.from;
|
|
811
|
+
}
|
|
812
|
+
applyReturningList(stmt, options.returning);
|
|
813
|
+
return { UpdateStmt: stmt };
|
|
752
814
|
}
|
|
753
|
-
function
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
815
|
+
function deleteStmt(options) {
|
|
816
|
+
const naming = options.naming ?? identityNaming;
|
|
817
|
+
const relation = {
|
|
818
|
+
relname: naming.toDatabase(options.table),
|
|
819
|
+
inh: true,
|
|
820
|
+
relpersistence: "p"
|
|
821
|
+
};
|
|
822
|
+
if (options.schema) {
|
|
823
|
+
relation.schemaname = naming.toDatabase(options.schema);
|
|
758
824
|
}
|
|
759
|
-
|
|
825
|
+
const stmt = {
|
|
826
|
+
relation
|
|
827
|
+
};
|
|
828
|
+
if (options.where) {
|
|
829
|
+
stmt.whereClause = options.where;
|
|
830
|
+
}
|
|
831
|
+
if (options.using && options.using.length > 0) {
|
|
832
|
+
stmt.usingClause = options.using;
|
|
833
|
+
}
|
|
834
|
+
applyReturningList(stmt, options.returning);
|
|
835
|
+
return { DeleteStmt: stmt };
|
|
760
836
|
}
|
|
761
|
-
function
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
name: [{ String: { sval: i.operator } }],
|
|
772
|
-
lexpr: leftNode,
|
|
773
|
-
rexpr: rightNode
|
|
774
|
-
}
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
case "customFn": {
|
|
778
|
-
const i = intent;
|
|
779
|
-
const nameParts = i.name.split(".");
|
|
780
|
-
const argNodes = i.args.map(
|
|
781
|
-
(arg) => compileExpressionIntent(arg, ctx, state)
|
|
782
|
-
);
|
|
783
|
-
const orderByNodes = i.aggOrderBy && i.aggOrderBy.length > 0 ? i.aggOrderBy.map((ob) => {
|
|
784
|
-
const colNode = columnRef(
|
|
785
|
-
ob.field,
|
|
786
|
-
void 0,
|
|
787
|
-
void 0,
|
|
788
|
-
ctx.naming
|
|
789
|
-
);
|
|
790
|
-
return sortBy(colNode, ob.direction === "desc" ? "DESC" : "ASC");
|
|
791
|
-
}) : void 0;
|
|
792
|
-
return funcCall(nameParts, argNodes, {
|
|
793
|
-
...orderByNodes ? { orderBy: orderByNodes } : {}
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
case "ref": {
|
|
797
|
-
const i = intent;
|
|
798
|
-
const dotIdx = i.column.indexOf(".");
|
|
799
|
-
if (dotIdx !== -1) {
|
|
800
|
-
const table = i.column.slice(0, dotIdx);
|
|
801
|
-
const col = i.column.slice(dotIdx + 1);
|
|
802
|
-
return columnRef(col, table, void 0, ctx.naming);
|
|
803
|
-
}
|
|
804
|
-
return columnRef(i.column, void 0, void 0, ctx.naming);
|
|
837
|
+
function jsonAggSubquery(targetTable, whereExpr, alias, schemaName, naming = identityNaming, options) {
|
|
838
|
+
const targetAlias = options?.innerAlias ?? "__t__";
|
|
839
|
+
const cols = options?.columns;
|
|
840
|
+
const hasProjection = cols && cols.length > 0 && !(cols.length === 1 && cols[0] === "*");
|
|
841
|
+
let toJsonbCall;
|
|
842
|
+
if (hasProjection) {
|
|
843
|
+
const projArgs = [];
|
|
844
|
+
for (const col of cols) {
|
|
845
|
+
projArgs.push({ A_Const: { sval: { sval: naming.toDatabase(col) } } });
|
|
846
|
+
projArgs.push(columnRef(col, targetAlias, void 0, naming));
|
|
805
847
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
return createParamRef(idx);
|
|
811
|
-
}
|
|
812
|
-
case "cast": {
|
|
813
|
-
const i = intent;
|
|
814
|
-
const argNode = compileExpressionIntent(i.expr, ctx, state);
|
|
815
|
-
return typeCast(argNode, i.typeName);
|
|
816
|
-
}
|
|
817
|
-
case "literal": {
|
|
818
|
-
const i = intent;
|
|
819
|
-
if (i.value === null || i.value === void 0) {
|
|
820
|
-
return nullConstNode();
|
|
821
|
-
}
|
|
822
|
-
if (typeof i.value === "boolean") {
|
|
823
|
-
return booleanConstNode(i.value);
|
|
848
|
+
toJsonbCall = {
|
|
849
|
+
FuncCall: {
|
|
850
|
+
funcname: [stringNode("jsonb_build_object")],
|
|
851
|
+
args: projArgs
|
|
824
852
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
853
|
+
};
|
|
854
|
+
} else {
|
|
855
|
+
const rowRef = {
|
|
856
|
+
ColumnRef: {
|
|
857
|
+
fields: [stringNode(targetAlias)]
|
|
830
858
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
859
|
+
};
|
|
860
|
+
toJsonbCall = {
|
|
861
|
+
FuncCall: {
|
|
862
|
+
funcname: [stringNode("to_jsonb")],
|
|
863
|
+
args: [rowRef]
|
|
835
864
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
return {
|
|
844
|
-
A_Expr: {
|
|
845
|
-
kind: "AEXPR_OP",
|
|
846
|
-
name: [{ String: { sval: i.operator } }],
|
|
847
|
-
rexpr: operandNode
|
|
848
|
-
}
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
case "namedArg": {
|
|
852
|
-
const nae = intent;
|
|
853
|
-
const argNode = compileExpressionIntent(nae.value, ctx, state);
|
|
854
|
-
return {
|
|
855
|
-
NamedArgExpr: {
|
|
856
|
-
arg: argNode,
|
|
857
|
-
name: nae.name,
|
|
858
|
-
argnumber: -1
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
case "star":
|
|
863
|
-
return {
|
|
864
|
-
ColumnRef: { fields: [{ A_Star: {} }] }
|
|
865
|
-
};
|
|
866
|
-
case "array": {
|
|
867
|
-
const ae = intent;
|
|
868
|
-
const elements = ae.elements.map(
|
|
869
|
-
(el) => compileExpressionIntent(el, ctx, state)
|
|
870
|
-
);
|
|
871
|
-
return { A_ArrayExpr: { elements } };
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
if (options?.childNodes && options.childNodes.length > 0) {
|
|
868
|
+
const buildObjectArgs = [];
|
|
869
|
+
for (const child of options.childNodes) {
|
|
870
|
+
buildObjectArgs.push({ A_Const: { sval: { sval: child.key } } });
|
|
871
|
+
buildObjectArgs.push(child.node);
|
|
872
872
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
"compileExpressionIntent: 'subquery' expression kind requires ctx.compileSubquery to be set. Use asExpr() only in .columns() context, not in standalone expressions."
|
|
878
|
-
);
|
|
873
|
+
const jsonbBuildObject = {
|
|
874
|
+
FuncCall: {
|
|
875
|
+
funcname: [stringNode("jsonb_build_object")],
|
|
876
|
+
args: buildObjectArgs
|
|
879
877
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
878
|
+
};
|
|
879
|
+
toJsonbCall = {
|
|
880
|
+
A_Expr: {
|
|
881
|
+
kind: "AEXPR_OP",
|
|
882
|
+
name: [stringNode("||")],
|
|
883
|
+
lexpr: toJsonbCall,
|
|
884
|
+
rexpr: jsonbBuildObject
|
|
886
885
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
};
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
const jsonAggCall = {
|
|
889
|
+
FuncCall: {
|
|
890
|
+
funcname: [stringNode("json_agg")],
|
|
891
|
+
args: [toJsonbCall]
|
|
894
892
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
893
|
+
};
|
|
894
|
+
const fromTable = rangeVar(targetTable, targetAlias, schemaName, naming);
|
|
895
|
+
const limitNode = options?.limit !== void 0 ? { A_Const: { ival: { ival: options.limit } } } : void 0;
|
|
896
|
+
const innerSelect = selectStmt({
|
|
897
|
+
targetList: [{ ResTarget: { val: jsonAggCall } }],
|
|
898
|
+
from: [fromTable],
|
|
899
|
+
where: whereExpr,
|
|
900
|
+
...limitNode && { limit: limitNode }
|
|
901
|
+
});
|
|
902
|
+
const subLink = {
|
|
903
|
+
SubLink: {
|
|
904
|
+
subLinkType: "EXPR_SUBLINK",
|
|
905
|
+
// scalar subquery
|
|
906
|
+
subselect: innerSelect
|
|
899
907
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const whenNode = dispatch(
|
|
908
|
-
branch.condition,
|
|
909
|
-
ctx,
|
|
910
|
-
state
|
|
911
|
-
);
|
|
912
|
-
const thenNode = compileExpressionIntent(branch.result, ctx, state);
|
|
913
|
-
return {
|
|
914
|
-
CaseWhen: { expr: whenNode, result: thenNode }
|
|
915
|
-
};
|
|
916
|
-
});
|
|
917
|
-
let defresult;
|
|
918
|
-
if (caseIntent.else !== void 0) {
|
|
919
|
-
defresult = compileExpressionIntent(caseIntent.else, ctx, state);
|
|
908
|
+
};
|
|
909
|
+
const emptyArrayDefault = {
|
|
910
|
+
TypeCast: {
|
|
911
|
+
arg: { A_Const: { sval: { sval: "[]" } } },
|
|
912
|
+
typeName: {
|
|
913
|
+
names: [stringNode("json")],
|
|
914
|
+
typemod: -1
|
|
920
915
|
}
|
|
921
|
-
return {
|
|
922
|
-
CaseExpr: {
|
|
923
|
-
args: caseArgs,
|
|
924
|
-
...defresult !== void 0 ? { defresult } : {}
|
|
925
|
-
}
|
|
926
|
-
};
|
|
927
916
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
917
|
+
};
|
|
918
|
+
const coalesceNode = coalesceExpr([subLink, emptyArrayDefault]);
|
|
919
|
+
return {
|
|
920
|
+
ResTarget: {
|
|
921
|
+
val: coalesceNode,
|
|
922
|
+
name: alias
|
|
932
923
|
}
|
|
933
|
-
}
|
|
924
|
+
};
|
|
934
925
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
926
|
+
function jsonAggCorrelation(parentAlias, parentColumn, targetAlias, targetColumn, naming = identityNaming) {
|
|
927
|
+
return eqExpr(
|
|
928
|
+
columnRef(targetColumn, targetAlias, void 0, naming),
|
|
929
|
+
columnRef(parentColumn, parentAlias, void 0, naming)
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
var STRENGTH_MAP, POLICY_MAP;
|
|
933
|
+
var init_ast_helpers = __esm({
|
|
934
|
+
"src/ast-helpers.ts"() {
|
|
938
935
|
"use strict";
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
compile(decision, ctx, state) {
|
|
952
|
-
const expressionIntent = decision.expressionIntent;
|
|
953
|
-
return compileExpressionIntent(expressionIntent, ctx, state);
|
|
954
|
-
}
|
|
936
|
+
init_naming_plugin();
|
|
937
|
+
init_validate();
|
|
938
|
+
STRENGTH_MAP = {
|
|
939
|
+
forUpdate: "LCS_FORUPDATE",
|
|
940
|
+
forNoKeyUpdate: "LCS_FORNOKEYUPDATE",
|
|
941
|
+
forShare: "LCS_FORSHARE",
|
|
942
|
+
forKeyShare: "LCS_FORKEYSHARE"
|
|
943
|
+
};
|
|
944
|
+
POLICY_MAP = {
|
|
945
|
+
block: "LockWaitBlock",
|
|
946
|
+
skipLocked: "LockWaitSkip",
|
|
947
|
+
noWait: "LockWaitError"
|
|
955
948
|
};
|
|
956
949
|
}
|
|
957
950
|
});
|
|
958
951
|
|
|
959
|
-
// src/
|
|
960
|
-
function
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
952
|
+
// src/param-ref.ts
|
|
953
|
+
function validateParamRef(paramRef) {
|
|
954
|
+
const errors = [];
|
|
955
|
+
if (paramRef.number === void 0) {
|
|
956
|
+
errors.push("ParamRef.number is required");
|
|
957
|
+
} else if (!Number.isInteger(paramRef.number)) {
|
|
958
|
+
errors.push(`ParamRef.number must be an integer, got: ${paramRef.number}`);
|
|
959
|
+
} else if (paramRef.number < 1) {
|
|
960
|
+
errors.push(
|
|
961
|
+
`ParamRef.number must be >= 1 (1-based indexing), got: ${paramRef.number}`
|
|
962
|
+
);
|
|
963
|
+
} else if (paramRef.number > 65535) {
|
|
964
|
+
errors.push(
|
|
965
|
+
`ParamRef.number exceeds maximum (65535), got: ${paramRef.number}`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
valid: errors.length === 0,
|
|
970
|
+
errors
|
|
965
971
|
};
|
|
966
|
-
const alias = expr.as ?? expr.alias;
|
|
967
|
-
if (alias) decision.alias = alias;
|
|
968
|
-
decisions.push(decision);
|
|
969
972
|
}
|
|
970
|
-
function
|
|
971
|
-
const
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const aggFilter = expr.filter;
|
|
976
|
-
if (aggFunc === "count" && !aggField) {
|
|
977
|
-
const decision = {
|
|
978
|
-
type: "selectFunction",
|
|
979
|
-
function: "count",
|
|
980
|
-
column: "*",
|
|
981
|
-
table: rootTable
|
|
982
|
-
};
|
|
983
|
-
if (aggAs) decision.alias = aggAs;
|
|
984
|
-
applyFilter(decision, aggFilter, rootTable);
|
|
985
|
-
decisions.push(decision);
|
|
986
|
-
} else if (aggFunc === "count" && aggDistinct && aggField) {
|
|
987
|
-
const decision = {
|
|
988
|
-
type: "selectFunction",
|
|
989
|
-
function: "countDistinct",
|
|
990
|
-
column: aggField,
|
|
991
|
-
table: rootTable
|
|
992
|
-
};
|
|
993
|
-
if (aggAs) decision.alias = aggAs;
|
|
994
|
-
applyFilter(decision, aggFilter, rootTable);
|
|
995
|
-
decisions.push(decision);
|
|
996
|
-
} else {
|
|
997
|
-
const decision = {
|
|
998
|
-
type: "selectFunction",
|
|
999
|
-
function: aggFunc,
|
|
1000
|
-
table: rootTable
|
|
1001
|
-
};
|
|
1002
|
-
if (aggField) decision.column = aggField;
|
|
1003
|
-
if (aggAs) decision.alias = aggAs;
|
|
1004
|
-
applyFilter(decision, aggFilter, rootTable);
|
|
1005
|
-
decisions.push(decision);
|
|
973
|
+
function createParamRef(number, location) {
|
|
974
|
+
const paramRef = location !== void 0 ? { number, location } : { number };
|
|
975
|
+
const result = validateParamRef(paramRef);
|
|
976
|
+
if (!result.valid) {
|
|
977
|
+
throw new Error(`Invalid ParamRef: ${result.errors.join(", ")}`);
|
|
1006
978
|
}
|
|
979
|
+
return { ParamRef: paramRef };
|
|
1007
980
|
}
|
|
1008
|
-
function
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
function handleRawExpression(expr, rootTable, decisions) {
|
|
1019
|
-
const decision = {
|
|
1020
|
-
type: "selectFunction",
|
|
1021
|
-
function: "raw",
|
|
1022
|
-
args: [expr.sql],
|
|
1023
|
-
table: rootTable
|
|
1024
|
-
};
|
|
1025
|
-
if (expr.as) decision.alias = expr.as;
|
|
1026
|
-
decisions.push(decision);
|
|
1027
|
-
}
|
|
1028
|
-
function handleWindowExpression(expr, rootTable, decisions) {
|
|
1029
|
-
const windowFunc = expr.function;
|
|
1030
|
-
const windowAlias = expr.alias;
|
|
1031
|
-
const windowField = expr.field;
|
|
1032
|
-
const over = expr.over;
|
|
1033
|
-
const decision = {
|
|
1034
|
-
type: "selectWindow",
|
|
1035
|
-
function: windowFunc,
|
|
1036
|
-
alias: windowAlias,
|
|
1037
|
-
table: rootTable
|
|
1038
|
-
};
|
|
1039
|
-
if (windowField) decision.field = windowField;
|
|
1040
|
-
if (over.partitionBy) decision.partitionBy = over.partitionBy;
|
|
1041
|
-
if (over.orderBy) decision.orderBy = over.orderBy;
|
|
1042
|
-
const windowOffset = expr.offset;
|
|
1043
|
-
const windowDefault = expr.defaultValue;
|
|
1044
|
-
if (windowOffset !== void 0) decision.args = [windowOffset];
|
|
1045
|
-
if (windowDefault !== void 0) decision.value = windowDefault;
|
|
1046
|
-
decisions.push(decision);
|
|
1047
|
-
}
|
|
1048
|
-
function handleCaseExpression(expr, rootTable, decisions, _applyFilter, convertCondition) {
|
|
1049
|
-
const whenClauses = expr.when;
|
|
1050
|
-
const conditions = whenClauses.map((wc) => ({
|
|
1051
|
-
when: convertCondition(wc.condition, rootTable),
|
|
1052
|
-
// biome-ignore lint/suspicious/noThenProperty: intentional reserved word in decision object
|
|
1053
|
-
then: wc.result
|
|
1054
|
-
}));
|
|
1055
|
-
const decision = {
|
|
1056
|
-
type: "selectExpression",
|
|
1057
|
-
expressionType: "case",
|
|
1058
|
-
table: rootTable
|
|
981
|
+
function createTypeCastParamRef(paramNumber, typeName, isArray = false, location) {
|
|
982
|
+
const paramRefNode = createParamRef(paramNumber, location);
|
|
983
|
+
const typeNameNode = isArray ? {
|
|
984
|
+
names: [{ String: { sval: typeName } }],
|
|
985
|
+
typemod: -1,
|
|
986
|
+
arrayBounds: [{ Integer: { ival: -1 } }]
|
|
987
|
+
} : {
|
|
988
|
+
names: [{ String: { sval: typeName } }],
|
|
989
|
+
typemod: -1
|
|
1059
990
|
};
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
function handleRelationColumnExpression(expr, rootTable, decisions) {
|
|
1068
|
-
const decision = {
|
|
1069
|
-
type: "selectRelationColumn",
|
|
1070
|
-
relation: expr.relation,
|
|
1071
|
-
column: expr.column ?? "*",
|
|
1072
|
-
table: rootTable
|
|
991
|
+
const typeCast2 = location !== void 0 ? {
|
|
992
|
+
arg: paramRefNode,
|
|
993
|
+
typeName: typeNameNode,
|
|
994
|
+
location
|
|
995
|
+
} : {
|
|
996
|
+
arg: paramRefNode,
|
|
997
|
+
typeName: typeNameNode
|
|
1073
998
|
};
|
|
1074
|
-
|
|
1075
|
-
decisions.push(decision);
|
|
999
|
+
return { TypeCast: typeCast2 };
|
|
1076
1000
|
}
|
|
1077
|
-
function
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1001
|
+
function createEqualityExpr(columnName, paramNumber, tableName, location) {
|
|
1002
|
+
const fields = tableName ? [{ String: { sval: tableName } }, { String: { sval: columnName } }] : [{ String: { sval: columnName } }];
|
|
1003
|
+
const kind = "AEXPR_OP";
|
|
1004
|
+
const expr = location !== void 0 ? {
|
|
1005
|
+
kind,
|
|
1006
|
+
name: [{ String: { sval: "=" } }],
|
|
1007
|
+
lexpr: { ColumnRef: { fields, location } },
|
|
1008
|
+
rexpr: createParamRef(paramNumber, location),
|
|
1009
|
+
location
|
|
1010
|
+
} : {
|
|
1011
|
+
kind,
|
|
1012
|
+
name: [{ String: { sval: "=" } }],
|
|
1013
|
+
lexpr: { ColumnRef: { fields } },
|
|
1014
|
+
rexpr: createParamRef(paramNumber)
|
|
1083
1015
|
};
|
|
1084
|
-
|
|
1085
|
-
decisions.push(decision);
|
|
1016
|
+
return { A_Expr: expr };
|
|
1086
1017
|
}
|
|
1087
|
-
function
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
args:
|
|
1093
|
-
|
|
1018
|
+
function createAnyExpr(columnName, paramNumber, tableName, location) {
|
|
1019
|
+
const fields = tableName ? [{ String: { sval: tableName } }, { String: { sval: columnName } }] : [{ String: { sval: columnName } }];
|
|
1020
|
+
const kind = "AEXPR_OP";
|
|
1021
|
+
const anyCall = location !== void 0 ? {
|
|
1022
|
+
funcname: [{ String: { sval: "any" } }],
|
|
1023
|
+
args: [createParamRef(paramNumber, location)],
|
|
1024
|
+
location
|
|
1025
|
+
} : {
|
|
1026
|
+
funcname: [{ String: { sval: "any" } }],
|
|
1027
|
+
args: [createParamRef(paramNumber)]
|
|
1094
1028
|
};
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1029
|
+
const expr = location !== void 0 ? {
|
|
1030
|
+
kind,
|
|
1031
|
+
name: [{ String: { sval: "=" } }],
|
|
1032
|
+
lexpr: { ColumnRef: { fields, location } },
|
|
1033
|
+
rexpr: { FuncCall: anyCall },
|
|
1034
|
+
location
|
|
1035
|
+
} : {
|
|
1036
|
+
kind,
|
|
1037
|
+
name: [{ String: { sval: "=" } }],
|
|
1038
|
+
lexpr: { ColumnRef: { fields } },
|
|
1039
|
+
rexpr: { FuncCall: anyCall }
|
|
1106
1040
|
};
|
|
1107
|
-
|
|
1108
|
-
if (expr.as) decision.alias = expr.as;
|
|
1109
|
-
decisions.push(decision);
|
|
1041
|
+
return { A_Expr: expr };
|
|
1110
1042
|
}
|
|
1111
|
-
function
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1043
|
+
function collectAndValidateParamRefs(node) {
|
|
1044
|
+
const paramRefs = [];
|
|
1045
|
+
function traverse(n, path) {
|
|
1046
|
+
if (n === null || n === void 0) return;
|
|
1047
|
+
if (typeof n === "object") {
|
|
1048
|
+
const obj = n;
|
|
1049
|
+
if ("ParamRef" in obj && obj.ParamRef !== void 0) {
|
|
1050
|
+
paramRefs.push({
|
|
1051
|
+
paramRef: obj.ParamRef,
|
|
1052
|
+
path
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1056
|
+
if (Array.isArray(value)) {
|
|
1057
|
+
value.forEach((item, index) => {
|
|
1058
|
+
traverse(item, `${path}.${key}[${index}]`);
|
|
1059
|
+
});
|
|
1060
|
+
} else if (typeof value === "object") {
|
|
1061
|
+
traverse(value, `${path}.${key}`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1126
1064
|
}
|
|
1127
1065
|
}
|
|
1128
|
-
|
|
1066
|
+
traverse(node, "root");
|
|
1067
|
+
const validationResults = paramRefs.map((p) => validateParamRef(p.paramRef));
|
|
1068
|
+
const allValid = validationResults.every((r) => r.valid);
|
|
1069
|
+
return { paramRefs, validationResults, allValid };
|
|
1129
1070
|
}
|
|
1130
|
-
var
|
|
1131
|
-
|
|
1132
|
-
"src/select-expression-handlers.ts"() {
|
|
1071
|
+
var init_param_ref = __esm({
|
|
1072
|
+
"src/param-ref.ts"() {
|
|
1133
1073
|
"use strict";
|
|
1134
|
-
EXPRESSION_HANDLERS = {
|
|
1135
|
-
column: (expr, rootTable, decisions) => handleColumnExpression(expr, rootTable, decisions),
|
|
1136
|
-
columnAlias: (expr, rootTable, decisions) => handleColumnExpression(expr, rootTable, decisions),
|
|
1137
|
-
aggregate: (expr, rootTable, decisions, applyFilter) => handleAggregateExpression(expr, rootTable, decisions, applyFilter),
|
|
1138
|
-
coalesce: (expr, rootTable, decisions) => handleCoalesceExpression(expr, rootTable, decisions),
|
|
1139
|
-
raw: (expr, rootTable, decisions) => handleRawExpression(expr, rootTable, decisions),
|
|
1140
|
-
window: (expr, rootTable, decisions) => handleWindowExpression(expr, rootTable, decisions),
|
|
1141
|
-
case: (expr, rootTable, decisions, applyFilter, convertCondition) => handleCaseExpression(
|
|
1142
|
-
expr,
|
|
1143
|
-
rootTable,
|
|
1144
|
-
decisions,
|
|
1145
|
-
applyFilter,
|
|
1146
|
-
convertCondition
|
|
1147
|
-
),
|
|
1148
|
-
relationColumn: (expr, rootTable, decisions) => handleRelationColumnExpression(expr, rootTable, decisions),
|
|
1149
|
-
// pseudoColumn: intentional no-op — planner hints, not SQL
|
|
1150
|
-
arithmetic: (expr, rootTable, decisions) => handleArithmeticExpression(expr, rootTable, decisions),
|
|
1151
|
-
jsonExtract: (expr, rootTable, decisions) => handleJsonExtractExpression(expr, rootTable, decisions),
|
|
1152
|
-
jsonPathExtract: (expr, rootTable, decisions) => handleJsonPathExtractExpression(expr, rootTable, decisions),
|
|
1153
|
-
// Custom expression types — produce 'selectCustomExpression' decisions
|
|
1154
|
-
literal: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1155
|
-
customOp: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1156
|
-
customFn: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1157
|
-
ref: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1158
|
-
param: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1159
|
-
cast: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1160
|
-
unary: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions)
|
|
1161
|
-
};
|
|
1162
1074
|
}
|
|
1163
1075
|
});
|
|
1164
1076
|
|
|
1165
|
-
// src/
|
|
1166
|
-
function
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
decisions.push(...convertSelect(intent.select, rootTable));
|
|
1170
|
-
} else {
|
|
1171
|
-
decisions.push({ type: "select", column: "*", table: rootTable });
|
|
1077
|
+
// src/handlers/expression/case-value.ts
|
|
1078
|
+
function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler) {
|
|
1079
|
+
if (value === null || value === void 0) {
|
|
1080
|
+
return nullConstNode();
|
|
1172
1081
|
}
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1082
|
+
if (typeof value === "string") {
|
|
1083
|
+
return columnRef(value, alias, schema, naming);
|
|
1175
1084
|
}
|
|
1176
|
-
if (
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1085
|
+
if (typeof value !== "object") {
|
|
1086
|
+
const idx = ++state.paramIndex;
|
|
1087
|
+
state.parameters.push(value);
|
|
1088
|
+
return createParamRef(idx);
|
|
1180
1089
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1090
|
+
const expr = value;
|
|
1091
|
+
switch (expr.kind) {
|
|
1092
|
+
case "literal":
|
|
1093
|
+
if (expr.value === null || expr.value === void 0)
|
|
1094
|
+
return nullConstNode();
|
|
1095
|
+
if (typeof expr.value === "boolean")
|
|
1096
|
+
return booleanConstNode(expr.value);
|
|
1097
|
+
if (typeof expr.value === "number") {
|
|
1098
|
+
if (Number.isInteger(expr.value))
|
|
1099
|
+
return integerNode(expr.value);
|
|
1100
|
+
return floatNode(String(expr.value));
|
|
1101
|
+
}
|
|
1102
|
+
{
|
|
1103
|
+
const idx = ++state.paramIndex;
|
|
1104
|
+
state.parameters.push(expr.value);
|
|
1105
|
+
return createParamRef(idx);
|
|
1106
|
+
}
|
|
1107
|
+
case "column":
|
|
1108
|
+
return columnRef(expr.column, alias, schema, naming);
|
|
1109
|
+
case "arithmetic": {
|
|
1110
|
+
const left = resolveCaseValue(
|
|
1111
|
+
expr.left,
|
|
1112
|
+
alias,
|
|
1113
|
+
schema,
|
|
1114
|
+
naming,
|
|
1115
|
+
state,
|
|
1116
|
+
nestedCaseHandler
|
|
1117
|
+
);
|
|
1118
|
+
const right = resolveCaseValue(
|
|
1119
|
+
expr.right,
|
|
1120
|
+
alias,
|
|
1121
|
+
schema,
|
|
1122
|
+
naming,
|
|
1123
|
+
state,
|
|
1124
|
+
nestedCaseHandler
|
|
1125
|
+
);
|
|
1126
|
+
return {
|
|
1127
|
+
A_Expr: {
|
|
1128
|
+
kind: "AEXPR_OP",
|
|
1129
|
+
name: [{ String: { sval: expr.operator } }],
|
|
1130
|
+
lexpr: left,
|
|
1131
|
+
rexpr: right
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1184
1134
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1135
|
+
// biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional — no nested handler → parameterize via default
|
|
1136
|
+
case "case":
|
|
1137
|
+
if (nestedCaseHandler) {
|
|
1138
|
+
return nestedCaseHandler(expr);
|
|
1139
|
+
}
|
|
1140
|
+
// falls through
|
|
1141
|
+
default: {
|
|
1142
|
+
const idx = ++state.paramIndex;
|
|
1143
|
+
state.parameters.push(value);
|
|
1144
|
+
return createParamRef(idx);
|
|
1190
1145
|
}
|
|
1191
1146
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
decisions.push({ type: "limit", limit: intent.limit });
|
|
1199
|
-
}
|
|
1200
|
-
if (intent.offset !== void 0) {
|
|
1201
|
-
decisions.push({ type: "offset", offset: intent.offset });
|
|
1147
|
+
}
|
|
1148
|
+
var init_case_value = __esm({
|
|
1149
|
+
"src/handlers/expression/case-value.ts"() {
|
|
1150
|
+
"use strict";
|
|
1151
|
+
init_ast_helpers();
|
|
1152
|
+
init_param_ref();
|
|
1202
1153
|
}
|
|
1203
|
-
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
// src/handlers/expression/custom.ts
|
|
1157
|
+
import { validateTypeName } from "@dbsp/core";
|
|
1158
|
+
function registerWhereDispatcherFactory(factory) {
|
|
1159
|
+
_whereDispatcherFactory = factory;
|
|
1204
1160
|
}
|
|
1205
|
-
function
|
|
1206
|
-
if (
|
|
1207
|
-
|
|
1208
|
-
|
|
1161
|
+
function _createWhereDispatcher() {
|
|
1162
|
+
if (!_whereDispatcherFactory) {
|
|
1163
|
+
throw new Error(
|
|
1164
|
+
"compileExpressionIntent (case): WHERE dispatcher not initialized. Ensure compile-where.ts is imported before any CASE expression is compiled."
|
|
1165
|
+
);
|
|
1209
1166
|
}
|
|
1167
|
+
return _whereDispatcherFactory();
|
|
1210
1168
|
}
|
|
1211
|
-
function
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1169
|
+
function assertSafeOperator(op3, opts) {
|
|
1170
|
+
if (typeof op3 !== "string") {
|
|
1171
|
+
throw new Error(
|
|
1172
|
+
`Invalid operator: expected a string, got ${typeof op3}. Operator must be a plain string value.`
|
|
1173
|
+
);
|
|
1215
1174
|
}
|
|
1216
|
-
if (
|
|
1217
|
-
|
|
1218
|
-
type: "select",
|
|
1219
|
-
column: field,
|
|
1220
|
-
table: rootTable
|
|
1221
|
-
}));
|
|
1175
|
+
if (!op3) {
|
|
1176
|
+
throw new Error(`Invalid operator "${op3}". Operator must not be empty.`);
|
|
1222
1177
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1178
|
+
const SYMBOLIC_RE = /^[-+*/<>=~!@#%^&|?]+$/;
|
|
1179
|
+
const isSymbolic = SYMBOLIC_RE.test(op3);
|
|
1180
|
+
const isAllowedWord = opts?.allowWords?.some((w) => w.toLowerCase() === op3.toLowerCase()) ?? false;
|
|
1181
|
+
if (!isSymbolic && !isAllowedWord) {
|
|
1182
|
+
throw new Error(
|
|
1183
|
+
`Invalid operator "${op3}". Operator must consist only of symbolic characters (e.g. <=>, <->, @@, @>, <@, &&, ||, ~)` + (opts?.allowWords?.length ? ` or one of the allowed words: ${opts.allowWords.join(", ")}.` : ".")
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
if (op3.includes("--") || op3.includes("/*") || op3.includes("*/")) {
|
|
1187
|
+
throw new Error(
|
|
1188
|
+
`Invalid operator "${op3}" \u2014 must not contain SQL comment sequences (-- /* */).`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
function compileExpressionIntent(intent, ctx, state) {
|
|
1193
|
+
const kind = intent.kind;
|
|
1194
|
+
switch (kind) {
|
|
1195
|
+
case "customOp": {
|
|
1196
|
+
const i = intent;
|
|
1197
|
+
const operator = i.operator;
|
|
1198
|
+
assertSafeOperator(operator);
|
|
1199
|
+
const leftNode = compileExpressionIntent(i.left, ctx, state);
|
|
1200
|
+
const rightNode = compileExpressionIntent(i.right, ctx, state);
|
|
1201
|
+
return {
|
|
1202
|
+
A_Expr: {
|
|
1203
|
+
kind: "AEXPR_OP",
|
|
1204
|
+
name: [{ String: { sval: operator } }],
|
|
1205
|
+
lexpr: leftNode,
|
|
1206
|
+
rexpr: rightNode
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
case "customFn": {
|
|
1211
|
+
const i = intent;
|
|
1212
|
+
const nameParts = i.name.split(".");
|
|
1213
|
+
const argNodes = i.args.map(
|
|
1214
|
+
(arg) => compileExpressionIntent(arg, ctx, state)
|
|
1215
|
+
);
|
|
1216
|
+
const orderByNodes = i.aggOrderBy && i.aggOrderBy.length > 0 ? i.aggOrderBy.map((ob) => {
|
|
1217
|
+
const colNode = columnRef(
|
|
1218
|
+
ob.field,
|
|
1219
|
+
void 0,
|
|
1220
|
+
void 0,
|
|
1221
|
+
ctx.naming
|
|
1236
1222
|
);
|
|
1223
|
+
return sortBy(colNode, ob.direction === "desc" ? "DESC" : "ASC");
|
|
1224
|
+
}) : void 0;
|
|
1225
|
+
return funcCall(nameParts, argNodes, {
|
|
1226
|
+
...orderByNodes ? { orderBy: orderByNodes } : {}
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
case "ref": {
|
|
1230
|
+
const i = intent;
|
|
1231
|
+
const dotIdx = i.column.indexOf(".");
|
|
1232
|
+
if (dotIdx !== -1) {
|
|
1233
|
+
const table = i.column.slice(0, dotIdx);
|
|
1234
|
+
const col = i.column.slice(dotIdx + 1);
|
|
1235
|
+
return columnRef(col, table, void 0, ctx.naming);
|
|
1237
1236
|
}
|
|
1237
|
+
return columnRef(i.column, void 0, void 0, ctx.naming);
|
|
1238
1238
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1239
|
+
case "param": {
|
|
1240
|
+
const i = intent;
|
|
1241
|
+
const idx = ++state.paramIndex;
|
|
1242
|
+
state.parameters.push(i.value);
|
|
1243
|
+
return createParamRef(idx);
|
|
1244
|
+
}
|
|
1245
|
+
case "cast": {
|
|
1246
|
+
const i = intent;
|
|
1247
|
+
const typeName = i.typeName;
|
|
1248
|
+
if (typeof typeName !== "string") {
|
|
1249
|
+
throw new Error(
|
|
1250
|
+
`cast(): typeName must be a plain string, got ${typeof typeName}.`
|
|
1251
|
+
);
|
|
1246
1252
|
}
|
|
1253
|
+
validateTypeName(typeName);
|
|
1254
|
+
const argNode = compileExpressionIntent(i.expr, ctx, state);
|
|
1255
|
+
return typeCast(argNode, typeName);
|
|
1247
1256
|
}
|
|
1248
|
-
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
column: agg.field ?? "*",
|
|
1253
|
-
table: rootTable
|
|
1254
|
-
};
|
|
1255
|
-
if (agg.as) {
|
|
1256
|
-
aggDecision.alias = agg.as;
|
|
1257
|
+
case "literal": {
|
|
1258
|
+
const i = intent;
|
|
1259
|
+
if (i.value === null || i.value === void 0) {
|
|
1260
|
+
return nullConstNode();
|
|
1257
1261
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1262
|
+
if (typeof i.value === "boolean") {
|
|
1263
|
+
return booleanConstNode(i.value);
|
|
1264
|
+
}
|
|
1265
|
+
if (typeof i.value === "number") {
|
|
1266
|
+
if (!Number.isFinite(i.value)) {
|
|
1267
|
+
throw new Error(
|
|
1268
|
+
`literal(): numeric value must be finite; got ${i.value}. Use param() for computed values.`
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
if (Number.isInteger(i.value)) {
|
|
1272
|
+
return integerNode(i.value);
|
|
1273
|
+
}
|
|
1274
|
+
return floatNode(String(i.value));
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof i.value === "string") {
|
|
1277
|
+
return {
|
|
1278
|
+
A_Const: { sval: { sval: i.value } }
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
throw new Error(
|
|
1282
|
+
`literal(): unsupported value type "${typeof i.value}". Only null, boolean, number, and string are allowed. Use param() to bind computed or user-supplied values.`
|
|
1283
|
+
);
|
|
1260
1284
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if (subquery.having) unsupported.push("HAVING");
|
|
1274
|
-
if (subquery.offset != null) unsupported.push("OFFSET");
|
|
1275
|
-
if (subquery.distinct) unsupported.push("DISTINCT");
|
|
1276
|
-
if (subquery.distinctOn && subquery.distinctOn.length > 0)
|
|
1277
|
-
unsupported.push("DISTINCT ON");
|
|
1278
|
-
if (subquery.include && subquery.include.length > 0)
|
|
1279
|
-
unsupported.push("include (relation hydration)");
|
|
1280
|
-
if (subquery.joins && subquery.joins.length > 0) unsupported.push("joins");
|
|
1281
|
-
if (subquery.existsWrap) unsupported.push("existsWrap");
|
|
1282
|
-
if (subquery.lock) unsupported.push("lock");
|
|
1283
|
-
if (subquery.batchValuesSource) unsupported.push("batchValuesSource");
|
|
1284
|
-
if (context === "rawExists" || context === "scalar-direct") {
|
|
1285
|
-
if (subquery.limit != null) unsupported.push("LIMIT");
|
|
1286
|
-
}
|
|
1287
|
-
if (context === "rawExists" || context === "scalar-direct") {
|
|
1288
|
-
if (subquery.orderBy && subquery.orderBy.length > 0) {
|
|
1289
|
-
unsupported.push("ORDER BY");
|
|
1285
|
+
case "unary": {
|
|
1286
|
+
const i = intent;
|
|
1287
|
+
const operator = i.operator;
|
|
1288
|
+
assertSafeOperator(operator, { allowWords: ["NOT"] });
|
|
1289
|
+
const operandNode = compileExpressionIntent(i.operand, ctx, state);
|
|
1290
|
+
return {
|
|
1291
|
+
A_Expr: {
|
|
1292
|
+
kind: "AEXPR_OP",
|
|
1293
|
+
name: [{ String: { sval: operator } }],
|
|
1294
|
+
rexpr: operandNode
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1290
1297
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
"orderBy with expression (only field-based ORDER BY is supported)"
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
if (context === "IN") {
|
|
1301
|
-
const select = subquery.select;
|
|
1302
|
-
if (!select) {
|
|
1303
|
-
unsupported.push(
|
|
1304
|
-
"missing select (IN subquery must project exactly one named column)"
|
|
1305
|
-
);
|
|
1306
|
-
} else if (select.type === "aggregate") {
|
|
1307
|
-
unsupported.push(
|
|
1308
|
-
"aggregate SELECT (use a scalar subquery comparison instead)"
|
|
1309
|
-
);
|
|
1310
|
-
} else if (select.type === "expressions") {
|
|
1311
|
-
unsupported.push("expressions SELECT (not supported in IN subquery)");
|
|
1312
|
-
} else if (select.type === "all") {
|
|
1313
|
-
unsupported.push(
|
|
1314
|
-
"SELECT * / all (IN subquery must project exactly one named column)"
|
|
1315
|
-
);
|
|
1316
|
-
} else if (select.type === "fields" || Array.isArray(select.fields) || // Also catch the typeless `{ fields: undefined | null }` shape: the
|
|
1317
|
-
// `fields` key is present (triggering isSelectWithFields in the compiler)
|
|
1318
|
-
// but the value is not a non-empty array. Without this branch,
|
|
1319
|
-
// `{ fields: undefined }` falls through all checks and the compiler
|
|
1320
|
-
// silently falls back to SELECT *, producing wrong SQL.
|
|
1321
|
-
// Guard: `in` operator crashes on primitives; a string select like 'id'
|
|
1322
|
-
// is a valid single-column selector — only check for the `fields` key on
|
|
1323
|
-
// actual objects.
|
|
1324
|
-
typeof select === "object" && select !== null && "fields" in select) {
|
|
1325
|
-
if (!select.fields || select.fields.length === 0) {
|
|
1326
|
-
unsupported.push(
|
|
1327
|
-
"empty fields list (IN subquery must project exactly one named column)"
|
|
1298
|
+
case "namedArg": {
|
|
1299
|
+
const nae = intent;
|
|
1300
|
+
const name = nae.name;
|
|
1301
|
+
if (typeof name !== "string") {
|
|
1302
|
+
throw new Error(
|
|
1303
|
+
`namedArg(): name must be a plain string, got ${typeof name}.`
|
|
1328
1304
|
);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1305
|
+
}
|
|
1306
|
+
validateIdentifier(name, "alias");
|
|
1307
|
+
const argNode = compileExpressionIntent(nae.value, ctx, state);
|
|
1308
|
+
return {
|
|
1309
|
+
NamedArgExpr: {
|
|
1310
|
+
arg: argNode,
|
|
1311
|
+
name,
|
|
1312
|
+
argnumber: -1
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
case "star":
|
|
1317
|
+
return {
|
|
1318
|
+
ColumnRef: { fields: [{ A_Star: {} }] }
|
|
1319
|
+
};
|
|
1320
|
+
case "array": {
|
|
1321
|
+
const ae = intent;
|
|
1322
|
+
const elements = ae.elements.map(
|
|
1323
|
+
(el) => compileExpressionIntent(el, ctx, state)
|
|
1324
|
+
);
|
|
1325
|
+
return { A_ArrayExpr: { elements } };
|
|
1326
|
+
}
|
|
1327
|
+
case "subquery": {
|
|
1328
|
+
const sq = intent;
|
|
1329
|
+
if (!ctx.compileSubquery) {
|
|
1330
|
+
throw new Error(
|
|
1331
|
+
"compileExpressionIntent: 'subquery' expression kind requires ctx.compileSubquery to be set. Use asExpr() only in .columns() context, not in standalone expressions."
|
|
1332
1332
|
);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1333
|
+
}
|
|
1334
|
+
const { ast: innerAst, parameters: innerParams } = ctx.compileSubquery(
|
|
1335
|
+
sq.query,
|
|
1336
|
+
state.paramIndex
|
|
1337
|
+
);
|
|
1338
|
+
for (const p of innerParams) {
|
|
1339
|
+
state.parameters.push(p);
|
|
1340
|
+
}
|
|
1341
|
+
state.paramIndex += innerParams.length;
|
|
1342
|
+
return {
|
|
1343
|
+
SubLink: {
|
|
1344
|
+
subLinkType: "EXPR_SUBLINK",
|
|
1345
|
+
subselect: innerAst
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
case "relationColumn": {
|
|
1350
|
+
const rc = intent;
|
|
1351
|
+
const alias = state.aliases.get(rc.relation) ?? rc.relation;
|
|
1352
|
+
return columnRef(rc.column, alias, void 0, ctx.naming);
|
|
1353
|
+
}
|
|
1354
|
+
case "case": {
|
|
1355
|
+
const dispatch = _createWhereDispatcher();
|
|
1356
|
+
const caseIntent = intent;
|
|
1357
|
+
if (!caseIntent.when || caseIntent.when.length === 0) {
|
|
1358
|
+
throw new Error("CASE expression requires at least one WHEN clause");
|
|
1359
|
+
}
|
|
1360
|
+
const caseArgs = caseIntent.when.map((branch) => {
|
|
1361
|
+
const whenNode = dispatch(
|
|
1362
|
+
branch.condition,
|
|
1363
|
+
ctx,
|
|
1364
|
+
state
|
|
1336
1365
|
);
|
|
1366
|
+
const thenNode = compileExpressionIntent(branch.result, ctx, state);
|
|
1367
|
+
return {
|
|
1368
|
+
CaseWhen: { expr: whenNode, result: thenNode }
|
|
1369
|
+
};
|
|
1370
|
+
});
|
|
1371
|
+
let defresult;
|
|
1372
|
+
if (caseIntent.else !== void 0) {
|
|
1373
|
+
defresult = compileExpressionIntent(caseIntent.else, ctx, state);
|
|
1337
1374
|
}
|
|
1375
|
+
return {
|
|
1376
|
+
CaseExpr: {
|
|
1377
|
+
args: caseArgs,
|
|
1378
|
+
...defresult !== void 0 ? { defresult } : {}
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1338
1381
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
const select = subquery.select;
|
|
1343
|
-
if (select?.type === "expressions") {
|
|
1344
|
-
unsupported.push("expressions SELECT (not supported in scalar subquery)");
|
|
1345
|
-
} else if (select?.type === "fields" && select.fields != null && select.fields.length > 1) {
|
|
1346
|
-
unsupported.push(
|
|
1347
|
-
`multi-field projection [${select.fields.join(", ")}] (scalar subquery must project exactly one column \u2014 use a single field)`
|
|
1348
|
-
);
|
|
1349
|
-
} else if (select?.type === "aggregate" && select.aggregates != null && select.aggregates.length > 1) {
|
|
1350
|
-
unsupported.push(
|
|
1351
|
-
`multi-aggregate projection (scalar subquery must project exactly one column \u2014 use a single aggregate)`
|
|
1382
|
+
default: {
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
`compileExpressionIntent: unsupported expression kind '${kind}'`
|
|
1352
1385
|
);
|
|
1353
1386
|
}
|
|
1354
1387
|
}
|
|
1355
|
-
if (unsupported.length > 0) {
|
|
1356
|
-
const label = context === "IN" ? "IN" : context === "rawExists" ? "rawExists" : "scalar";
|
|
1357
|
-
throw new Error(
|
|
1358
|
-
`${label} subquery with ${unsupported.join(", ")} is not supported \u2014 it would silently change which rows match; restructure the query or use a CTE.`
|
|
1359
|
-
);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
function isOuterRef(value) {
|
|
1363
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1364
|
-
const v = value;
|
|
1365
|
-
return v.kind === "ref" && v.outer === true;
|
|
1366
1388
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1389
|
+
var _whereDispatcherFactory, customExpressionHandler;
|
|
1390
|
+
var init_custom = __esm({
|
|
1391
|
+
"src/handlers/expression/custom.ts"() {
|
|
1392
|
+
"use strict";
|
|
1393
|
+
init_ast_helpers();
|
|
1394
|
+
init_param_ref();
|
|
1395
|
+
init_validate();
|
|
1396
|
+
customExpressionHandler = {
|
|
1397
|
+
types: [
|
|
1398
|
+
"customOp",
|
|
1399
|
+
"customFn",
|
|
1400
|
+
"ref",
|
|
1401
|
+
"param",
|
|
1402
|
+
"cast",
|
|
1403
|
+
"unary",
|
|
1404
|
+
"customExpression"
|
|
1405
|
+
],
|
|
1406
|
+
compile(decision, ctx, state) {
|
|
1407
|
+
const expressionIntent = decision.expressionIntent;
|
|
1408
|
+
return compileExpressionIntent(expressionIntent, ctx, state);
|
|
1375
1409
|
}
|
|
1376
|
-
}
|
|
1377
|
-
if (containsOuterRef(value)) return true;
|
|
1378
|
-
}
|
|
1410
|
+
};
|
|
1379
1411
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
column: rawValue.column
|
|
1388
|
-
} : rawValue;
|
|
1389
|
-
const result = {
|
|
1390
|
-
type: "where",
|
|
1391
|
-
column: cond.field,
|
|
1392
|
-
operator: cond.operator,
|
|
1393
|
-
value: resolvedValue,
|
|
1394
|
-
table: rootTable
|
|
1395
|
-
};
|
|
1396
|
-
if (cond.jsonPath) result.jsonPath = cond.jsonPath;
|
|
1397
|
-
if (cond.jsonMode) result.jsonMode = cond.jsonMode;
|
|
1398
|
-
return result;
|
|
1399
|
-
}
|
|
1400
|
-
function convertLike(cond, rootTable) {
|
|
1401
|
-
const result = {
|
|
1402
|
-
type: "where",
|
|
1403
|
-
column: cond.field,
|
|
1404
|
-
operator: cond.caseInsensitive ? "ilike" : "like",
|
|
1405
|
-
value: cond.pattern,
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
// src/select-expression-handlers.ts
|
|
1415
|
+
function handleColumnExpression(expr, rootTable, decisions) {
|
|
1416
|
+
const decision = {
|
|
1417
|
+
type: "select",
|
|
1418
|
+
column: expr.column,
|
|
1406
1419
|
table: rootTable
|
|
1407
1420
|
};
|
|
1408
|
-
|
|
1409
|
-
|
|
1421
|
+
const alias = expr.as ?? expr.alias;
|
|
1422
|
+
if (alias) decision.alias = alias;
|
|
1423
|
+
decisions.push(decision);
|
|
1410
1424
|
}
|
|
1411
|
-
function
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
return converted ? [converted] : [];
|
|
1423
|
-
})() : [];
|
|
1424
|
-
const rawLimit = rawSubquery.limit;
|
|
1425
|
-
const rawOrderBy = rawSubquery.orderBy;
|
|
1426
|
-
return {
|
|
1427
|
-
type: "where",
|
|
1428
|
-
column: cond.field,
|
|
1429
|
-
operator: cond.not ? "notInSubquery" : "inSubquery",
|
|
1430
|
-
targetTable: rawSubquery.from,
|
|
1431
|
-
selectColumn,
|
|
1432
|
-
conditions: subConditions,
|
|
1433
|
-
table: rootTable,
|
|
1434
|
-
// Provenance: original QueryIntent for validation in buildPredicateSubquerySelect
|
|
1435
|
-
subqueryIntent: rawSubquery,
|
|
1436
|
-
...rawLimit != null && { limit: rawLimit },
|
|
1437
|
-
...rawOrderBy && {
|
|
1438
|
-
orderBy: rawOrderBy.map((o) => ({
|
|
1439
|
-
field: o.field,
|
|
1440
|
-
direction: o.direction?.toLowerCase() ?? "asc"
|
|
1441
|
-
}))
|
|
1442
|
-
}
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
return {
|
|
1446
|
-
type: "where",
|
|
1447
|
-
column: cond.field,
|
|
1448
|
-
operator: cond.not ? "notIn" : "in",
|
|
1449
|
-
value: cond.values,
|
|
1450
|
-
table: rootTable
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
function convertNull(cond, rootTable) {
|
|
1454
|
-
return {
|
|
1455
|
-
type: "where",
|
|
1456
|
-
column: cond.field,
|
|
1457
|
-
operator: cond.operator,
|
|
1458
|
-
table: rootTable
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
|
-
function convertRange(cond, rootTable) {
|
|
1462
|
-
const rangeOperator = cond.operator;
|
|
1463
|
-
const col = cond.field;
|
|
1464
|
-
if (rangeOperator === "contains" || rangeOperator === "containedBy" || rangeOperator === "overlaps") {
|
|
1465
|
-
return {
|
|
1466
|
-
type: "where",
|
|
1467
|
-
column: col,
|
|
1468
|
-
operator: rangeOperator,
|
|
1469
|
-
value: cond.value,
|
|
1470
|
-
table: rootTable
|
|
1471
|
-
};
|
|
1472
|
-
}
|
|
1473
|
-
if (rangeOperator === "between") {
|
|
1474
|
-
const rangeVal = cond.value;
|
|
1475
|
-
return {
|
|
1476
|
-
type: "where",
|
|
1477
|
-
column: col,
|
|
1478
|
-
operator: "between",
|
|
1479
|
-
value: [rangeVal.lower, rangeVal.upper],
|
|
1480
|
-
table: rootTable
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
if (cond.gte !== void 0 && cond.lte !== void 0) {
|
|
1484
|
-
return {
|
|
1485
|
-
type: "where",
|
|
1486
|
-
column: col,
|
|
1487
|
-
operator: "between",
|
|
1488
|
-
value: [cond.gte, cond.lte],
|
|
1489
|
-
table: rootTable
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1492
|
-
if (cond.gte !== void 0) {
|
|
1493
|
-
return {
|
|
1494
|
-
type: "where",
|
|
1495
|
-
column: col,
|
|
1496
|
-
operator: "gte",
|
|
1497
|
-
value: cond.gte,
|
|
1498
|
-
table: rootTable
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
if (cond.gt !== void 0) {
|
|
1502
|
-
return {
|
|
1503
|
-
type: "where",
|
|
1504
|
-
column: col,
|
|
1505
|
-
operator: "gt",
|
|
1506
|
-
value: cond.gt,
|
|
1425
|
+
function handleAggregateExpression(expr, rootTable, decisions, applyFilter) {
|
|
1426
|
+
const aggFunc = expr.function;
|
|
1427
|
+
const aggField = expr.field;
|
|
1428
|
+
const aggAs = expr.as;
|
|
1429
|
+
const aggDistinct = expr.distinct;
|
|
1430
|
+
const aggFilter = expr.filter;
|
|
1431
|
+
if (aggFunc === "count" && !aggField) {
|
|
1432
|
+
const decision = {
|
|
1433
|
+
type: "selectFunction",
|
|
1434
|
+
function: "count",
|
|
1435
|
+
column: "*",
|
|
1507
1436
|
table: rootTable
|
|
1508
1437
|
};
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1438
|
+
if (aggAs) decision.alias = aggAs;
|
|
1439
|
+
applyFilter(decision, aggFilter, rootTable);
|
|
1440
|
+
decisions.push(decision);
|
|
1441
|
+
} else if (aggFunc === "count" && aggDistinct && aggField) {
|
|
1442
|
+
const decision = {
|
|
1443
|
+
type: "selectFunction",
|
|
1444
|
+
function: "countDistinct",
|
|
1445
|
+
column: aggField,
|
|
1516
1446
|
table: rootTable
|
|
1517
1447
|
};
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1448
|
+
if (aggAs) decision.alias = aggAs;
|
|
1449
|
+
applyFilter(decision, aggFilter, rootTable);
|
|
1450
|
+
decisions.push(decision);
|
|
1451
|
+
} else {
|
|
1452
|
+
const decision = {
|
|
1453
|
+
type: "selectFunction",
|
|
1454
|
+
function: aggFunc,
|
|
1525
1455
|
table: rootTable
|
|
1526
1456
|
};
|
|
1457
|
+
if (aggField) decision.column = aggField;
|
|
1458
|
+
if (aggAs) decision.alias = aggAs;
|
|
1459
|
+
applyFilter(decision, aggFilter, rootTable);
|
|
1460
|
+
decisions.push(decision);
|
|
1527
1461
|
}
|
|
1528
|
-
return null;
|
|
1529
1462
|
}
|
|
1530
|
-
function
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1463
|
+
function handleCoalesceExpression(expr, rootTable, decisions) {
|
|
1464
|
+
const decision = {
|
|
1465
|
+
type: "selectFunction",
|
|
1466
|
+
function: "coalesce",
|
|
1467
|
+
args: expr.fields,
|
|
1468
|
+
table: rootTable
|
|
1469
|
+
};
|
|
1470
|
+
if (expr.as) decision.alias = expr.as;
|
|
1471
|
+
decisions.push(decision);
|
|
1538
1472
|
}
|
|
1539
|
-
function
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1473
|
+
function handleRawExpression(expr, rootTable, decisions) {
|
|
1474
|
+
const decision = {
|
|
1475
|
+
type: "selectFunction",
|
|
1476
|
+
function: "raw",
|
|
1477
|
+
args: [expr.sql],
|
|
1478
|
+
table: rootTable
|
|
1479
|
+
};
|
|
1480
|
+
if (expr.as) decision.alias = expr.as;
|
|
1481
|
+
decisions.push(decision);
|
|
1546
1482
|
}
|
|
1547
|
-
function
|
|
1548
|
-
const
|
|
1549
|
-
const
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1483
|
+
function handleWindowExpression(expr, rootTable, decisions) {
|
|
1484
|
+
const windowFunc = expr.function;
|
|
1485
|
+
const windowAlias = expr.alias;
|
|
1486
|
+
const windowField = expr.field;
|
|
1487
|
+
const over = expr.over;
|
|
1488
|
+
const decision = {
|
|
1489
|
+
type: "selectWindow",
|
|
1490
|
+
function: windowFunc,
|
|
1491
|
+
alias: windowAlias,
|
|
1492
|
+
table: rootTable
|
|
1493
|
+
};
|
|
1494
|
+
if (windowField) decision.field = windowField;
|
|
1495
|
+
if (over.partitionBy) decision.partitionBy = over.partitionBy;
|
|
1496
|
+
if (over.orderBy) decision.orderBy = over.orderBy;
|
|
1497
|
+
const windowOffset = expr.offset;
|
|
1498
|
+
const windowDefault = expr.defaultValue;
|
|
1499
|
+
if (windowOffset !== void 0) decision.args = [windowOffset];
|
|
1500
|
+
if (windowDefault !== void 0) decision.value = windowDefault;
|
|
1501
|
+
decisions.push(decision);
|
|
1552
1502
|
}
|
|
1553
|
-
function
|
|
1554
|
-
const
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
if (select) {
|
|
1569
|
-
if ("type" in select && select.type === "aggregate") {
|
|
1570
|
-
const agg = select.aggregates?.[0];
|
|
1571
|
-
if (agg) {
|
|
1572
|
-
aggregate = agg.function;
|
|
1573
|
-
selectColumn = agg.field ?? "*";
|
|
1574
|
-
}
|
|
1575
|
-
} else if ("fields" in select && select.fields?.length) {
|
|
1576
|
-
selectColumn = select.fields[0];
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
const subConditions = [];
|
|
1580
|
-
if (subquery.where) {
|
|
1581
|
-
const innerWhere = convertWhereCondition(
|
|
1582
|
-
subquery.where,
|
|
1583
|
-
targetTable
|
|
1584
|
-
);
|
|
1585
|
-
if (innerWhere) subConditions.push(innerWhere);
|
|
1503
|
+
function handleCaseExpression(expr, rootTable, decisions, _applyFilter, convertCondition) {
|
|
1504
|
+
const whenClauses = expr.when;
|
|
1505
|
+
const conditions = whenClauses.map((wc) => ({
|
|
1506
|
+
when: convertCondition(wc.condition, rootTable),
|
|
1507
|
+
// biome-ignore lint/suspicious/noThenProperty: intentional reserved word in decision object
|
|
1508
|
+
then: wc.result
|
|
1509
|
+
}));
|
|
1510
|
+
const decision = {
|
|
1511
|
+
type: "selectExpression",
|
|
1512
|
+
expressionType: "case",
|
|
1513
|
+
table: rootTable
|
|
1514
|
+
};
|
|
1515
|
+
decision.conditions = conditions;
|
|
1516
|
+
if (expr.else) {
|
|
1517
|
+
decision.value = expr.else;
|
|
1586
1518
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1519
|
+
if (expr.as) decision.alias = expr.as;
|
|
1520
|
+
decisions.push(decision);
|
|
1521
|
+
}
|
|
1522
|
+
function handleRelationColumnExpression(expr, rootTable, decisions) {
|
|
1523
|
+
const decision = {
|
|
1524
|
+
type: "selectRelationColumn",
|
|
1525
|
+
relation: expr.relation,
|
|
1526
|
+
column: expr.column ?? "*",
|
|
1527
|
+
table: rootTable
|
|
1596
1528
|
};
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
...aggregate && { aggregate },
|
|
1607
|
-
...subConditions.length > 0 && { conditions: subConditions },
|
|
1608
|
-
...rawLimit != null && { limit: rawLimit },
|
|
1609
|
-
...rawOrderBy && rawOrderBy.length > 0 && {
|
|
1610
|
-
orderBy: rawOrderBy.map((o) => ({
|
|
1611
|
-
field: o.field,
|
|
1612
|
-
direction: o.direction?.toLowerCase() ?? "asc"
|
|
1613
|
-
}))
|
|
1614
|
-
}
|
|
1529
|
+
if (expr.as) decision.alias = expr.as;
|
|
1530
|
+
decisions.push(decision);
|
|
1531
|
+
}
|
|
1532
|
+
function handleArithmeticExpression(expr, rootTable, decisions) {
|
|
1533
|
+
const decision = {
|
|
1534
|
+
type: "selectArithmetic",
|
|
1535
|
+
operator: expr.operator,
|
|
1536
|
+
args: [expr.left, expr.right],
|
|
1537
|
+
table: rootTable
|
|
1615
1538
|
};
|
|
1539
|
+
if (expr.as) decision.alias = expr.as;
|
|
1540
|
+
decisions.push(decision);
|
|
1616
1541
|
}
|
|
1617
|
-
function
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
case "range":
|
|
1629
|
-
return convertRange(cond, rootTable);
|
|
1630
|
-
case "and":
|
|
1631
|
-
return convertLogicalGroup(cond, rootTable, "whereAnd");
|
|
1632
|
-
case "or":
|
|
1633
|
-
return convertLogicalGroup(cond, rootTable, "whereOr");
|
|
1634
|
-
case "not":
|
|
1635
|
-
return convertNot(cond, rootTable);
|
|
1636
|
-
case "exists":
|
|
1637
|
-
return convertExistsLike(cond, "exists");
|
|
1638
|
-
case "notExists":
|
|
1639
|
-
return convertExistsLike(cond, "notExists");
|
|
1640
|
-
case "relationFilter": {
|
|
1641
|
-
const mode = cond.mode || "some";
|
|
1642
|
-
const operator = mode === "none" ? "notExists" : mode === "every" ? "every" : "exists";
|
|
1643
|
-
return convertExistsLike(cond, operator);
|
|
1644
|
-
}
|
|
1645
|
-
case "subquery":
|
|
1646
|
-
return convertSubquery(cond);
|
|
1647
|
-
case "rawExists":
|
|
1648
|
-
case "rawNotExists": {
|
|
1649
|
-
const sub = cond.subquery;
|
|
1650
|
-
if (!sub) {
|
|
1651
|
-
throw new Error(
|
|
1652
|
-
`${cond.kind}: missing subquery \u2014 pass the result of subquery(table).select(...) or a builder with buildIntent()`
|
|
1653
|
-
);
|
|
1654
|
-
}
|
|
1655
|
-
assertNoUnsupportedSubqueryModifiers(sub, "rawExists");
|
|
1656
|
-
if (sub.where && containsOuterRef(sub.where)) {
|
|
1657
|
-
throw new Error(
|
|
1658
|
-
`${cond.kind}: correlated subqueries (outerRef inside the inner WHERE) are not yet supported. Workaround: use exists("relation", { where: ... }) when a schema relation exists, or wait for the rawExists correlation pipeline (tracked in TODO).`
|
|
1659
|
-
);
|
|
1660
|
-
}
|
|
1661
|
-
return {
|
|
1662
|
-
type: "where",
|
|
1663
|
-
operator: cond.kind,
|
|
1664
|
-
// Reuse expressionIntent (already present on PlanDecision) to carry the
|
|
1665
|
-
// inner QueryIntent; the rawExistsHandler discriminates by operator name.
|
|
1666
|
-
expressionIntent: sub,
|
|
1667
|
-
table: rootTable
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
|
-
case "jsonContains":
|
|
1671
|
-
return {
|
|
1672
|
-
type: "where",
|
|
1673
|
-
column: cond.field,
|
|
1674
|
-
operator: cond.reversed ? "jsonContainedBy" : "jsonContains",
|
|
1675
|
-
value: cond.value,
|
|
1676
|
-
table: rootTable
|
|
1677
|
-
};
|
|
1678
|
-
case "any":
|
|
1679
|
-
return {
|
|
1680
|
-
type: "where",
|
|
1681
|
-
column: cond.field,
|
|
1682
|
-
operator: "any",
|
|
1683
|
-
values: cond.values,
|
|
1684
|
-
table: rootTable
|
|
1685
|
-
};
|
|
1686
|
-
case "jsonExists":
|
|
1687
|
-
return {
|
|
1688
|
-
type: "where",
|
|
1689
|
-
column: cond.field,
|
|
1690
|
-
operator: "jsonExists",
|
|
1691
|
-
value: cond.key,
|
|
1692
|
-
table: rootTable
|
|
1693
|
-
};
|
|
1694
|
-
case "expression":
|
|
1695
|
-
return {
|
|
1696
|
-
type: "where",
|
|
1697
|
-
operator: "expression",
|
|
1698
|
-
expressionIntent: cond.expr,
|
|
1699
|
-
value: cond.value,
|
|
1700
|
-
subqueryOperator: cond.operator,
|
|
1701
|
-
table: rootTable
|
|
1702
|
-
};
|
|
1703
|
-
default:
|
|
1704
|
-
return null;
|
|
1705
|
-
}
|
|
1542
|
+
function handleJsonExtractExpression(expr, rootTable, decisions) {
|
|
1543
|
+
const decision = {
|
|
1544
|
+
type: "selectFunction",
|
|
1545
|
+
function: "jsonExtract",
|
|
1546
|
+
column: expr.field,
|
|
1547
|
+
args: expr.path,
|
|
1548
|
+
table: rootTable
|
|
1549
|
+
};
|
|
1550
|
+
if (expr.mode) decision.jsonMode = expr.mode;
|
|
1551
|
+
if (expr.as) decision.alias = expr.as;
|
|
1552
|
+
decisions.push(decision);
|
|
1706
1553
|
}
|
|
1707
|
-
function
|
|
1708
|
-
const direction = order.direction === "desc" ? "DESC" : "ASC";
|
|
1709
|
-
const nulls = order.nulls ? order.nulls === "first" ? "FIRST" : "LAST" : void 0;
|
|
1710
|
-
if (order.expression) {
|
|
1711
|
-
const base = {
|
|
1712
|
-
type: "orderBy",
|
|
1713
|
-
expressionIntent: order.expression,
|
|
1714
|
-
direction,
|
|
1715
|
-
table: rootTable
|
|
1716
|
-
};
|
|
1717
|
-
if (nulls) {
|
|
1718
|
-
return { ...base, nulls };
|
|
1719
|
-
}
|
|
1720
|
-
return base;
|
|
1721
|
-
}
|
|
1554
|
+
function handleJsonPathExtractExpression(expr, rootTable, decisions) {
|
|
1722
1555
|
const decision = {
|
|
1723
|
-
type: "
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1556
|
+
type: "selectFunction",
|
|
1557
|
+
function: "jsonPathExtract",
|
|
1558
|
+
column: expr.field,
|
|
1559
|
+
args: [expr.path],
|
|
1560
|
+
table: rootTable
|
|
1728
1561
|
};
|
|
1729
|
-
if (
|
|
1730
|
-
|
|
1562
|
+
if (expr.mode) decision.jsonMode = expr.mode;
|
|
1563
|
+
if (expr.as) decision.alias = expr.as;
|
|
1564
|
+
decisions.push(decision);
|
|
1565
|
+
}
|
|
1566
|
+
function handleCustomExpressionSelect(expr, rootTable, decisions) {
|
|
1567
|
+
const decision = {
|
|
1568
|
+
type: "selectCustomExpression",
|
|
1569
|
+
expressionIntent: expr,
|
|
1570
|
+
table: rootTable
|
|
1571
|
+
};
|
|
1572
|
+
const explicitAlias = expr.as;
|
|
1573
|
+
if (explicitAlias) {
|
|
1574
|
+
decision.alias = explicitAlias;
|
|
1575
|
+
} else if (expr.kind === "cast") {
|
|
1576
|
+
const inner = expr.expr;
|
|
1577
|
+
if (inner?.kind === "ref") {
|
|
1578
|
+
const col = inner.column;
|
|
1579
|
+
const dotIdx = col.lastIndexOf(".");
|
|
1580
|
+
decision.alias = dotIdx !== -1 ? col.slice(dotIdx + 1) : col;
|
|
1581
|
+
}
|
|
1731
1582
|
}
|
|
1732
|
-
|
|
1583
|
+
decisions.push(decision);
|
|
1733
1584
|
}
|
|
1734
|
-
var
|
|
1735
|
-
|
|
1585
|
+
var EXPRESSION_HANDLERS;
|
|
1586
|
+
var init_select_expression_handlers = __esm({
|
|
1587
|
+
"src/select-expression-handlers.ts"() {
|
|
1736
1588
|
"use strict";
|
|
1737
|
-
|
|
1589
|
+
EXPRESSION_HANDLERS = {
|
|
1590
|
+
column: (expr, rootTable, decisions) => handleColumnExpression(expr, rootTable, decisions),
|
|
1591
|
+
columnAlias: (expr, rootTable, decisions) => handleColumnExpression(expr, rootTable, decisions),
|
|
1592
|
+
aggregate: (expr, rootTable, decisions, applyFilter) => handleAggregateExpression(expr, rootTable, decisions, applyFilter),
|
|
1593
|
+
coalesce: (expr, rootTable, decisions) => handleCoalesceExpression(expr, rootTable, decisions),
|
|
1594
|
+
raw: (expr, rootTable, decisions) => handleRawExpression(expr, rootTable, decisions),
|
|
1595
|
+
window: (expr, rootTable, decisions) => handleWindowExpression(expr, rootTable, decisions),
|
|
1596
|
+
case: (expr, rootTable, decisions, applyFilter, convertCondition) => handleCaseExpression(
|
|
1597
|
+
expr,
|
|
1598
|
+
rootTable,
|
|
1599
|
+
decisions,
|
|
1600
|
+
applyFilter,
|
|
1601
|
+
convertCondition
|
|
1602
|
+
),
|
|
1603
|
+
relationColumn: (expr, rootTable, decisions) => handleRelationColumnExpression(expr, rootTable, decisions),
|
|
1604
|
+
// pseudoColumn: intentional no-op — planner hints, not SQL
|
|
1605
|
+
arithmetic: (expr, rootTable, decisions) => handleArithmeticExpression(expr, rootTable, decisions),
|
|
1606
|
+
jsonExtract: (expr, rootTable, decisions) => handleJsonExtractExpression(expr, rootTable, decisions),
|
|
1607
|
+
jsonPathExtract: (expr, rootTable, decisions) => handleJsonPathExtractExpression(expr, rootTable, decisions),
|
|
1608
|
+
// Custom expression types — produce 'selectCustomExpression' decisions
|
|
1609
|
+
literal: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1610
|
+
customOp: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1611
|
+
customFn: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1612
|
+
ref: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1613
|
+
param: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1614
|
+
cast: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions),
|
|
1615
|
+
unary: (expr, rootTable, decisions) => handleCustomExpressionSelect(expr, rootTable, decisions)
|
|
1616
|
+
};
|
|
1738
1617
|
}
|
|
1739
1618
|
});
|
|
1740
1619
|
|
|
1741
|
-
// src/
|
|
1742
|
-
function
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1620
|
+
// src/intent-to-decisions.ts
|
|
1621
|
+
function intentToDecisions(intent, rootTable) {
|
|
1622
|
+
const decisions = [];
|
|
1623
|
+
if (intent.select) {
|
|
1624
|
+
decisions.push(...convertSelect(intent.select, rootTable));
|
|
1625
|
+
} else {
|
|
1626
|
+
decisions.push({ type: "select", column: "*", table: rootTable });
|
|
1627
|
+
}
|
|
1628
|
+
if (intent.where) {
|
|
1629
|
+
decisions.push(...convertWhere(intent.where, rootTable));
|
|
1630
|
+
}
|
|
1631
|
+
if (intent.orderBy && intent.orderBy.length > 0) {
|
|
1632
|
+
for (const order of intent.orderBy) {
|
|
1633
|
+
decisions.push(convertOrderBy(order, rootTable));
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
if (intent.groupBy && intent.groupBy.length > 0) {
|
|
1637
|
+
for (const col of intent.groupBy) {
|
|
1638
|
+
decisions.push({ type: "groupBy", column: col, table: rootTable });
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (intent.having) {
|
|
1642
|
+
const havingDecisions = convertWhere(intent.having, rootTable);
|
|
1643
|
+
for (const d of havingDecisions) {
|
|
1644
|
+
decisions.push({ ...d, type: "having" });
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (intent.distinctOn && intent.distinctOn.length > 0) {
|
|
1648
|
+
decisions.push({ type: "distinctOn", columns: intent.distinctOn });
|
|
1649
|
+
} else if (intent.distinct) {
|
|
1650
|
+
decisions.push({ type: "distinct" });
|
|
1651
|
+
}
|
|
1652
|
+
if (intent.limit !== void 0) {
|
|
1653
|
+
decisions.push({ type: "limit", limit: intent.limit });
|
|
1654
|
+
}
|
|
1655
|
+
if (intent.offset !== void 0) {
|
|
1656
|
+
decisions.push({ type: "offset", offset: intent.offset });
|
|
1657
|
+
}
|
|
1658
|
+
return decisions;
|
|
1753
1659
|
}
|
|
1754
|
-
function
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
}
|
|
1760
|
-
function isSelectWithFields(value) {
|
|
1761
|
-
return typeof value === "object" && value !== null && "fields" in value && (Array.isArray(value.fields) || value.fields === void 0);
|
|
1660
|
+
function applyFilterCondition(decision, filter, rootTable) {
|
|
1661
|
+
if (filter) {
|
|
1662
|
+
const filterDecision = convertWhereCondition(filter, rootTable);
|
|
1663
|
+
if (filterDecision) decision.filterCondition = filterDecision;
|
|
1664
|
+
}
|
|
1762
1665
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
"
|
|
1766
|
-
"
|
|
1767
|
-
COMPARISON_OPERATORS = {
|
|
1768
|
-
EQ: "=",
|
|
1769
|
-
NEQ: "!=",
|
|
1770
|
-
LT: "<",
|
|
1771
|
-
LTE: "<=",
|
|
1772
|
-
GT: ">",
|
|
1773
|
-
GTE: ">=",
|
|
1774
|
-
// Aliases
|
|
1775
|
-
eq: "=",
|
|
1776
|
-
ne: "!=",
|
|
1777
|
-
lt: "<",
|
|
1778
|
-
lte: "<=",
|
|
1779
|
-
gt: ">",
|
|
1780
|
-
gte: ">="
|
|
1781
|
-
};
|
|
1782
|
-
PATTERN_OPERATORS = {
|
|
1783
|
-
LIKE: "like",
|
|
1784
|
-
ILIKE: "ilike",
|
|
1785
|
-
NOT_LIKE: "notLike",
|
|
1786
|
-
NOT_ILIKE: "notIlike"
|
|
1787
|
-
};
|
|
1788
|
-
NULL_OPERATORS = {
|
|
1789
|
-
IS_NULL: "isNull",
|
|
1790
|
-
IS_NOT_NULL: "isNotNull"
|
|
1791
|
-
};
|
|
1792
|
-
COLLECTION_OPERATORS = {
|
|
1793
|
-
IN: "in",
|
|
1794
|
-
NOT_IN: "notIn",
|
|
1795
|
-
ANY: "any"
|
|
1796
|
-
};
|
|
1797
|
-
LOGICAL_OPERATORS = {
|
|
1798
|
-
AND: "and",
|
|
1799
|
-
OR: "or",
|
|
1800
|
-
NOT: "not"
|
|
1801
|
-
};
|
|
1802
|
-
ALL_OPERATORS = {
|
|
1803
|
-
...COMPARISON_OPERATORS,
|
|
1804
|
-
...PATTERN_OPERATORS,
|
|
1805
|
-
...NULL_OPERATORS,
|
|
1806
|
-
...COLLECTION_OPERATORS,
|
|
1807
|
-
...LOGICAL_OPERATORS
|
|
1808
|
-
};
|
|
1666
|
+
function convertSelect(select, rootTable) {
|
|
1667
|
+
const selectType = "type" in select ? select.type : void 0;
|
|
1668
|
+
if ("all" in select && select.all === true) {
|
|
1669
|
+
return [{ type: "select", column: "*", table: rootTable }];
|
|
1809
1670
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
num += src[i++];
|
|
1832
|
-
}
|
|
1833
|
-
tokens.push({ kind: "FLOAT", value: num });
|
|
1834
|
-
} else {
|
|
1835
|
-
tokens.push({ kind: "INT", value: num });
|
|
1671
|
+
if (selectType === "fields" && "fields" in select) {
|
|
1672
|
+
return select.fields.map((field) => ({
|
|
1673
|
+
type: "select",
|
|
1674
|
+
column: field,
|
|
1675
|
+
table: rootTable
|
|
1676
|
+
}));
|
|
1677
|
+
}
|
|
1678
|
+
if (selectType === "expressions" && "columns" in select) {
|
|
1679
|
+
const decisions = [];
|
|
1680
|
+
const columns = select.columns;
|
|
1681
|
+
for (const exprUnknown of columns) {
|
|
1682
|
+
const expr = exprUnknown;
|
|
1683
|
+
const handler = EXPRESSION_HANDLERS[expr.kind];
|
|
1684
|
+
if (handler) {
|
|
1685
|
+
handler(
|
|
1686
|
+
expr,
|
|
1687
|
+
rootTable,
|
|
1688
|
+
decisions,
|
|
1689
|
+
applyFilterCondition,
|
|
1690
|
+
convertWhereCondition
|
|
1691
|
+
);
|
|
1836
1692
|
}
|
|
1837
|
-
continue;
|
|
1838
1693
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
} else if (src[i] === "'") {
|
|
1847
|
-
i++;
|
|
1848
|
-
break;
|
|
1849
|
-
} else {
|
|
1850
|
-
str += src[i++];
|
|
1851
|
-
}
|
|
1694
|
+
return decisions;
|
|
1695
|
+
}
|
|
1696
|
+
if ("aggregates" in select) {
|
|
1697
|
+
const decisions = [];
|
|
1698
|
+
if (select.fields) {
|
|
1699
|
+
for (const field of select.fields) {
|
|
1700
|
+
decisions.push({ type: "select", column: field, table: rootTable });
|
|
1852
1701
|
}
|
|
1853
|
-
tokens.push({ kind: "STRING", value: str });
|
|
1854
|
-
continue;
|
|
1855
1702
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1703
|
+
for (const agg of select.aggregates) {
|
|
1704
|
+
const aggDecision = {
|
|
1705
|
+
type: "selectFunction",
|
|
1706
|
+
function: agg.function === "count" && agg.field === "*" ? "count" : agg.function,
|
|
1707
|
+
column: agg.field ?? "*",
|
|
1708
|
+
table: rootTable
|
|
1709
|
+
};
|
|
1710
|
+
if (agg.as) {
|
|
1711
|
+
aggDecision.alias = agg.as;
|
|
1860
1712
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
}
|
|
1864
|
-
if (ch === ":" && src[i + 1] === ":") {
|
|
1865
|
-
tokens.push({ kind: "CAST", value: "::" });
|
|
1866
|
-
i += 2;
|
|
1867
|
-
continue;
|
|
1868
|
-
}
|
|
1869
|
-
if (["+", "-", "*", "/"].includes(ch)) {
|
|
1870
|
-
tokens.push({ kind: "OP", value: ch });
|
|
1871
|
-
i++;
|
|
1872
|
-
continue;
|
|
1873
|
-
}
|
|
1874
|
-
if (ch === "(") {
|
|
1875
|
-
tokens.push({ kind: "LPAREN", value: "(" });
|
|
1876
|
-
i++;
|
|
1877
|
-
continue;
|
|
1878
|
-
}
|
|
1879
|
-
if (ch === ")") {
|
|
1880
|
-
tokens.push({ kind: "RPAREN", value: ")" });
|
|
1881
|
-
i++;
|
|
1882
|
-
continue;
|
|
1883
|
-
}
|
|
1884
|
-
if (ch === ",") {
|
|
1885
|
-
tokens.push({ kind: "COMMA", value: "," });
|
|
1886
|
-
i++;
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
if (ch === ".") {
|
|
1890
|
-
tokens.push({ kind: "DOT", value: "." });
|
|
1891
|
-
i++;
|
|
1892
|
-
continue;
|
|
1713
|
+
applyFilterCondition(aggDecision, agg.filter, rootTable);
|
|
1714
|
+
decisions.push(aggDecision);
|
|
1893
1715
|
}
|
|
1894
|
-
|
|
1895
|
-
`parseExpression: unexpected character '${ch}' at position ${i} in: ${input}`
|
|
1896
|
-
);
|
|
1716
|
+
return decisions;
|
|
1897
1717
|
}
|
|
1898
|
-
|
|
1899
|
-
return tokens;
|
|
1718
|
+
return [{ type: "select", column: "*", table: rootTable }];
|
|
1900
1719
|
}
|
|
1901
|
-
function
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
kind: "AEXPR_OP",
|
|
1905
|
-
name: [{ String: { sval: op3 } }],
|
|
1906
|
-
lexpr: left,
|
|
1907
|
-
rexpr: right
|
|
1908
|
-
}
|
|
1909
|
-
};
|
|
1720
|
+
function convertWhere(where, rootTable) {
|
|
1721
|
+
const decision = convertWhereCondition(where, rootTable);
|
|
1722
|
+
return decision ? [decision] : [];
|
|
1910
1723
|
}
|
|
1911
|
-
function
|
|
1912
|
-
const
|
|
1913
|
-
if (
|
|
1914
|
-
|
|
1724
|
+
function assertNoUnsupportedSubqueryModifiers(subquery, context) {
|
|
1725
|
+
const unsupported = [];
|
|
1726
|
+
if (subquery.groupBy && subquery.groupBy.length > 0)
|
|
1727
|
+
unsupported.push("GROUP BY");
|
|
1728
|
+
if (subquery.having) unsupported.push("HAVING");
|
|
1729
|
+
if (subquery.offset != null) unsupported.push("OFFSET");
|
|
1730
|
+
if (subquery.distinct) unsupported.push("DISTINCT");
|
|
1731
|
+
if (subquery.distinctOn && subquery.distinctOn.length > 0)
|
|
1732
|
+
unsupported.push("DISTINCT ON");
|
|
1733
|
+
if (subquery.include && subquery.include.length > 0)
|
|
1734
|
+
unsupported.push("include (relation hydration)");
|
|
1735
|
+
if (subquery.joins && subquery.joins.length > 0) unsupported.push("joins");
|
|
1736
|
+
if (subquery.existsWrap) unsupported.push("existsWrap");
|
|
1737
|
+
if (subquery.lock) unsupported.push("lock");
|
|
1738
|
+
if (subquery.batchValuesSource) unsupported.push("batchValuesSource");
|
|
1739
|
+
if (context === "rawExists" || context === "scalar-direct") {
|
|
1740
|
+
if (subquery.limit != null) unsupported.push("LIMIT");
|
|
1915
1741
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1742
|
+
if (context === "rawExists" || context === "scalar-direct") {
|
|
1743
|
+
if (subquery.orderBy && subquery.orderBy.length > 0) {
|
|
1744
|
+
unsupported.push("ORDER BY");
|
|
1745
|
+
}
|
|
1746
|
+
} else if (subquery.orderBy && subquery.orderBy.length > 0) {
|
|
1747
|
+
const hasExpressionSort = subquery.orderBy.some(
|
|
1748
|
+
(o) => !("field" in o) || o.field == null
|
|
1923
1749
|
);
|
|
1750
|
+
if (hasExpressionSort)
|
|
1751
|
+
unsupported.push(
|
|
1752
|
+
"orderBy with expression (only field-based ORDER BY is supported)"
|
|
1753
|
+
);
|
|
1924
1754
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1755
|
+
if (context === "IN") {
|
|
1756
|
+
const select = subquery.select;
|
|
1757
|
+
if (!select) {
|
|
1758
|
+
unsupported.push(
|
|
1759
|
+
"missing select (IN subquery must project exactly one named column)"
|
|
1760
|
+
);
|
|
1761
|
+
} else if (select.type === "aggregate") {
|
|
1762
|
+
unsupported.push(
|
|
1763
|
+
"aggregate SELECT (use a scalar subquery comparison instead)"
|
|
1764
|
+
);
|
|
1765
|
+
} else if (select.type === "expressions") {
|
|
1766
|
+
unsupported.push("expressions SELECT (not supported in IN subquery)");
|
|
1767
|
+
} else if (select.type === "all") {
|
|
1768
|
+
unsupported.push(
|
|
1769
|
+
"SELECT * / all (IN subquery must project exactly one named column)"
|
|
1770
|
+
);
|
|
1771
|
+
} else if (select.type === "fields" || Array.isArray(select.fields) || // Also catch the typeless `{ fields: undefined | null }` shape: the
|
|
1772
|
+
// `fields` key is present (triggering isSelectWithFields in the compiler)
|
|
1773
|
+
// but the value is not a non-empty array. Without this branch,
|
|
1774
|
+
// `{ fields: undefined }` falls through all checks and the compiler
|
|
1775
|
+
// silently falls back to SELECT *, producing wrong SQL.
|
|
1776
|
+
// Guard: `in` operator crashes on primitives; a string select like 'id'
|
|
1777
|
+
// is a valid single-column selector — only check for the `fields` key on
|
|
1778
|
+
// actual objects.
|
|
1779
|
+
typeof select === "object" && select !== null && "fields" in select) {
|
|
1780
|
+
if (!select.fields || select.fields.length === 0) {
|
|
1781
|
+
unsupported.push(
|
|
1782
|
+
"empty fields list (IN subquery must project exactly one named column)"
|
|
1783
|
+
);
|
|
1784
|
+
} else if (select.fields.length > 1) {
|
|
1785
|
+
unsupported.push(
|
|
1786
|
+
`multi-field projection [${select.fields.join(", ")}] (IN subquery must project exactly one named column \u2014 use a single field)`
|
|
1787
|
+
);
|
|
1788
|
+
} else if (typeof select.fields[0] !== "string") {
|
|
1789
|
+
unsupported.push(
|
|
1790
|
+
`non-string field element ${JSON.stringify(select.fields[0])} (IN subquery fields must contain a plain column name string)`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
const isScalarContext = context === "scalar" || context === "scalar-direct";
|
|
1796
|
+
if (isScalarContext) {
|
|
1797
|
+
const select = subquery.select;
|
|
1798
|
+
if (select?.type === "expressions") {
|
|
1799
|
+
unsupported.push("expressions SELECT (not supported in scalar subquery)");
|
|
1800
|
+
} else if (select?.type === "fields" && select.fields != null && select.fields.length > 1) {
|
|
1801
|
+
unsupported.push(
|
|
1802
|
+
`multi-field projection [${select.fields.join(", ")}] (scalar subquery must project exactly one column \u2014 use a single field)`
|
|
1803
|
+
);
|
|
1804
|
+
} else if (select?.type === "aggregate" && select.aggregates != null && select.aggregates.length > 1) {
|
|
1805
|
+
unsupported.push(
|
|
1806
|
+
`multi-aggregate projection (scalar subquery must project exactly one column \u2014 use a single aggregate)`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
if (unsupported.length > 0) {
|
|
1811
|
+
const label = context === "IN" ? "IN" : context === "rawExists" ? "rawExists" : "scalar";
|
|
1812
|
+
throw new Error(
|
|
1813
|
+
`${label} subquery with ${unsupported.join(", ")} is not supported \u2014 it would silently change which rows match; restructure the query or use a CTE.`
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
function isOuterRef(value) {
|
|
1818
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1819
|
+
const v = value;
|
|
1820
|
+
return v.kind === "ref" && v.outer === true;
|
|
1821
|
+
}
|
|
1822
|
+
function containsOuterRef(where) {
|
|
1823
|
+
if (!where || typeof where !== "object") return false;
|
|
1824
|
+
const w = where;
|
|
1825
|
+
if (isOuterRef(w)) return true;
|
|
1826
|
+
for (const value of Object.values(w)) {
|
|
1827
|
+
if (Array.isArray(value)) {
|
|
1828
|
+
for (const item of value) {
|
|
1829
|
+
if (containsOuterRef(item)) return true;
|
|
1830
|
+
}
|
|
1831
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1832
|
+
if (containsOuterRef(value)) return true;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1837
|
+
function convertComparison(cond, rootTable) {
|
|
1838
|
+
const rawValue = cond.value;
|
|
1839
|
+
const resolvedValue = isOuterRef(rawValue) ? {
|
|
1840
|
+
kind: "fieldRef",
|
|
1841
|
+
scope: "outer",
|
|
1842
|
+
column: rawValue.column
|
|
1843
|
+
} : rawValue;
|
|
1844
|
+
const result = {
|
|
1845
|
+
type: "where",
|
|
1846
|
+
column: cond.field,
|
|
1847
|
+
operator: cond.operator,
|
|
1848
|
+
value: resolvedValue,
|
|
1849
|
+
table: rootTable
|
|
1850
|
+
};
|
|
1851
|
+
if (cond.jsonPath) result.jsonPath = cond.jsonPath;
|
|
1852
|
+
if (cond.jsonMode) result.jsonMode = cond.jsonMode;
|
|
1853
|
+
return result;
|
|
1854
|
+
}
|
|
1855
|
+
function convertLike(cond, rootTable) {
|
|
1856
|
+
const result = {
|
|
1857
|
+
type: "where",
|
|
1858
|
+
column: cond.field,
|
|
1859
|
+
operator: cond.caseInsensitive ? "ilike" : "like",
|
|
1860
|
+
value: cond.pattern,
|
|
1861
|
+
table: rootTable
|
|
1862
|
+
};
|
|
1863
|
+
if (cond.escape !== void 0) result.escape = cond.escape;
|
|
1864
|
+
return result;
|
|
1865
|
+
}
|
|
1866
|
+
function convertIn(cond, rootTable) {
|
|
1867
|
+
const rawSubquery = cond.subquery;
|
|
1868
|
+
if (rawSubquery) {
|
|
1869
|
+
assertNoUnsupportedSubqueryModifiers(rawSubquery, "IN");
|
|
1870
|
+
const selectField = rawSubquery.select;
|
|
1871
|
+
const selectColumn = selectField && "fields" in selectField && Array.isArray(selectField.fields) ? selectField.fields[0] ?? "*" : "*";
|
|
1872
|
+
const subConditions = rawSubquery.where ? (() => {
|
|
1873
|
+
const converted = convertWhereCondition(
|
|
1874
|
+
rawSubquery.where,
|
|
1875
|
+
rawSubquery.from
|
|
1876
|
+
);
|
|
1877
|
+
return converted ? [converted] : [];
|
|
1878
|
+
})() : [];
|
|
1879
|
+
const rawLimit = rawSubquery.limit;
|
|
1880
|
+
const rawOrderBy = rawSubquery.orderBy;
|
|
1881
|
+
return {
|
|
1882
|
+
type: "where",
|
|
1883
|
+
column: cond.field,
|
|
1884
|
+
operator: cond.not ? "notInSubquery" : "inSubquery",
|
|
1885
|
+
targetTable: rawSubquery.from,
|
|
1886
|
+
selectColumn,
|
|
1887
|
+
conditions: subConditions,
|
|
1888
|
+
table: rootTable,
|
|
1889
|
+
// Provenance: original QueryIntent for validation in buildPredicateSubquerySelect
|
|
1890
|
+
subqueryIntent: rawSubquery,
|
|
1891
|
+
...rawLimit != null && { limit: rawLimit },
|
|
1892
|
+
...rawOrderBy && {
|
|
1893
|
+
orderBy: rawOrderBy.map((o) => ({
|
|
1894
|
+
field: o.field,
|
|
1895
|
+
direction: o.direction?.toLowerCase() ?? "asc"
|
|
1896
|
+
}))
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
return {
|
|
1901
|
+
type: "where",
|
|
1902
|
+
column: cond.field,
|
|
1903
|
+
operator: cond.not ? "notIn" : "in",
|
|
1904
|
+
value: cond.values,
|
|
1905
|
+
table: rootTable
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
function convertNull(cond, rootTable) {
|
|
1909
|
+
return {
|
|
1910
|
+
type: "where",
|
|
1911
|
+
column: cond.field,
|
|
1912
|
+
operator: cond.operator,
|
|
1913
|
+
table: rootTable
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
function convertRange(cond, rootTable) {
|
|
1917
|
+
const rangeOperator = cond.operator;
|
|
1918
|
+
const col = cond.field;
|
|
1919
|
+
if (rangeOperator === "contains" || rangeOperator === "containedBy" || rangeOperator === "overlaps") {
|
|
1920
|
+
return {
|
|
1921
|
+
type: "where",
|
|
1922
|
+
column: col,
|
|
1923
|
+
operator: rangeOperator,
|
|
1924
|
+
value: cond.value,
|
|
1925
|
+
table: rootTable
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
if (rangeOperator === "between") {
|
|
1929
|
+
const rangeVal = cond.value;
|
|
1930
|
+
return {
|
|
1931
|
+
type: "where",
|
|
1932
|
+
column: col,
|
|
1933
|
+
operator: "between",
|
|
1934
|
+
value: [rangeVal.lower, rangeVal.upper],
|
|
1935
|
+
table: rootTable
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
if (cond.gte !== void 0 && cond.lte !== void 0) {
|
|
1939
|
+
return {
|
|
1940
|
+
type: "where",
|
|
1941
|
+
column: col,
|
|
1942
|
+
operator: "between",
|
|
1943
|
+
value: [cond.gte, cond.lte],
|
|
1944
|
+
table: rootTable
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
if (cond.gte !== void 0) {
|
|
1948
|
+
return {
|
|
1949
|
+
type: "where",
|
|
1950
|
+
column: col,
|
|
1951
|
+
operator: "gte",
|
|
1952
|
+
value: cond.gte,
|
|
1953
|
+
table: rootTable
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
if (cond.gt !== void 0) {
|
|
1957
|
+
return {
|
|
1958
|
+
type: "where",
|
|
1959
|
+
column: col,
|
|
1960
|
+
operator: "gt",
|
|
1961
|
+
value: cond.gt,
|
|
1962
|
+
table: rootTable
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
if (cond.lte !== void 0) {
|
|
1966
|
+
return {
|
|
1967
|
+
type: "where",
|
|
1968
|
+
column: col,
|
|
1969
|
+
operator: "lte",
|
|
1970
|
+
value: cond.lte,
|
|
1971
|
+
table: rootTable
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
if (cond.lt !== void 0) {
|
|
1975
|
+
return {
|
|
1976
|
+
type: "where",
|
|
1977
|
+
column: col,
|
|
1978
|
+
operator: "lt",
|
|
1979
|
+
value: cond.lt,
|
|
1980
|
+
table: rootTable
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
return null;
|
|
1984
|
+
}
|
|
1985
|
+
function convertLogicalGroup(cond, rootTable, decisionType) {
|
|
1986
|
+
const subDecisions = [];
|
|
1987
|
+
for (const sub of cond.conditions) {
|
|
1988
|
+
const subDecision = convertWhereCondition(sub, rootTable);
|
|
1989
|
+
if (subDecision) subDecisions.push(subDecision);
|
|
1990
|
+
}
|
|
1991
|
+
if (subDecisions.length === 0) return null;
|
|
1992
|
+
return { type: decisionType, conditions: subDecisions };
|
|
1993
|
+
}
|
|
1994
|
+
function convertNot(cond, rootTable) {
|
|
1995
|
+
const subDecision = convertWhereCondition(
|
|
1996
|
+
cond.condition,
|
|
1997
|
+
rootTable
|
|
1998
|
+
);
|
|
1999
|
+
if (!subDecision) return null;
|
|
2000
|
+
return { type: "whereNot", conditions: [subDecision] };
|
|
2001
|
+
}
|
|
2002
|
+
function convertExistsLike(cond, operator) {
|
|
2003
|
+
const targetTable = cond.relation;
|
|
2004
|
+
const subDecisions = cond.where ? convertWhere(cond.where, targetTable) : [];
|
|
2005
|
+
const base = { type: "where", operator, targetTable };
|
|
2006
|
+
return subDecisions.length > 0 ? { ...base, conditions: subDecisions } : base;
|
|
2007
|
+
}
|
|
2008
|
+
function convertSubquery(cond) {
|
|
2009
|
+
const field = cond.field;
|
|
2010
|
+
const operator = cond.operator;
|
|
2011
|
+
const subquery = cond.subquery;
|
|
2012
|
+
if (!subquery || !field) return null;
|
|
2013
|
+
assertNoUnsupportedSubqueryModifiers(subquery, "scalar");
|
|
2014
|
+
if (subquery.where && containsOuterRef(subquery.where)) {
|
|
2015
|
+
throw new Error(
|
|
2016
|
+
'scalar subquery with correlated outerRef() is not yet supported \u2014 use exists("relation", { where: ... }) when a schema relation exists, or restructure the query to avoid the correlation.'
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
const targetTable = subquery.from;
|
|
2020
|
+
let selectColumn = "*";
|
|
2021
|
+
let aggregate;
|
|
2022
|
+
const select = subquery.select;
|
|
2023
|
+
if (select) {
|
|
2024
|
+
if ("type" in select && select.type === "aggregate") {
|
|
2025
|
+
const agg = select.aggregates?.[0];
|
|
2026
|
+
if (agg) {
|
|
2027
|
+
aggregate = agg.function;
|
|
2028
|
+
selectColumn = agg.field ?? "*";
|
|
2029
|
+
}
|
|
2030
|
+
} else if ("fields" in select && select.fields?.length) {
|
|
2031
|
+
selectColumn = select.fields[0];
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
const subConditions = [];
|
|
2035
|
+
if (subquery.where) {
|
|
2036
|
+
const innerWhere = convertWhereCondition(
|
|
2037
|
+
subquery.where,
|
|
2038
|
+
targetTable
|
|
2039
|
+
);
|
|
2040
|
+
if (innerWhere) subConditions.push(innerWhere);
|
|
2041
|
+
}
|
|
2042
|
+
const rawLimit = subquery.limit;
|
|
2043
|
+
const rawOrderBy = subquery.orderBy;
|
|
2044
|
+
const opMap = {
|
|
2045
|
+
eq: "=",
|
|
2046
|
+
neq: "!=",
|
|
2047
|
+
gt: ">",
|
|
2048
|
+
gte: ">=",
|
|
2049
|
+
lt: "<",
|
|
2050
|
+
lte: "<="
|
|
2051
|
+
};
|
|
2052
|
+
return {
|
|
2053
|
+
type: "where",
|
|
2054
|
+
column: field,
|
|
2055
|
+
operator: "scalarSubquery",
|
|
2056
|
+
targetTable,
|
|
2057
|
+
selectColumn,
|
|
2058
|
+
subqueryOperator: opMap[operator] ?? "=",
|
|
2059
|
+
// Provenance: original QueryIntent for validation in buildPredicateSubquerySelect
|
|
2060
|
+
subqueryIntent: subquery,
|
|
2061
|
+
...aggregate && { aggregate },
|
|
2062
|
+
...subConditions.length > 0 && { conditions: subConditions },
|
|
2063
|
+
...rawLimit != null && { limit: rawLimit },
|
|
2064
|
+
...rawOrderBy && rawOrderBy.length > 0 && {
|
|
2065
|
+
orderBy: rawOrderBy.map((o) => ({
|
|
2066
|
+
field: o.field,
|
|
2067
|
+
direction: o.direction?.toLowerCase() ?? "asc"
|
|
2068
|
+
}))
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function convertWhereCondition(condition, rootTable) {
|
|
2073
|
+
const cond = condition;
|
|
2074
|
+
switch (cond.kind) {
|
|
2075
|
+
case "comparison":
|
|
2076
|
+
return convertComparison(cond, rootTable);
|
|
2077
|
+
case "like":
|
|
2078
|
+
return convertLike(cond, rootTable);
|
|
2079
|
+
case "in":
|
|
2080
|
+
return convertIn(cond, rootTable);
|
|
2081
|
+
case "null":
|
|
2082
|
+
return convertNull(cond, rootTable);
|
|
2083
|
+
case "range":
|
|
2084
|
+
return convertRange(cond, rootTable);
|
|
2085
|
+
case "and":
|
|
2086
|
+
return convertLogicalGroup(cond, rootTable, "whereAnd");
|
|
2087
|
+
case "or":
|
|
2088
|
+
return convertLogicalGroup(cond, rootTable, "whereOr");
|
|
2089
|
+
case "not":
|
|
2090
|
+
return convertNot(cond, rootTable);
|
|
2091
|
+
case "exists":
|
|
2092
|
+
return convertExistsLike(cond, "exists");
|
|
2093
|
+
case "notExists":
|
|
2094
|
+
return convertExistsLike(cond, "notExists");
|
|
2095
|
+
case "relationFilter": {
|
|
2096
|
+
const mode = cond.mode || "some";
|
|
2097
|
+
const operator = mode === "none" ? "notExists" : mode === "every" ? "every" : "exists";
|
|
2098
|
+
return convertExistsLike(cond, operator);
|
|
2099
|
+
}
|
|
2100
|
+
case "subquery":
|
|
2101
|
+
return convertSubquery(cond);
|
|
2102
|
+
case "rawExists":
|
|
2103
|
+
case "rawNotExists": {
|
|
2104
|
+
const sub = cond.subquery;
|
|
2105
|
+
if (!sub) {
|
|
2106
|
+
throw new Error(
|
|
2107
|
+
`${cond.kind}: missing subquery \u2014 pass the result of subquery(table).select(...) or a builder with buildIntent()`
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
assertNoUnsupportedSubqueryModifiers(sub, "rawExists");
|
|
2111
|
+
if (sub.where && containsOuterRef(sub.where)) {
|
|
2112
|
+
throw new Error(
|
|
2113
|
+
`${cond.kind}: correlated subqueries (outerRef inside the inner WHERE) are not yet supported. Workaround: use exists("relation", { where: ... }) when a schema relation exists, or wait for the rawExists correlation pipeline (tracked in TODO).`
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
type: "where",
|
|
2118
|
+
operator: cond.kind,
|
|
2119
|
+
// Reuse expressionIntent (already present on PlanDecision) to carry the
|
|
2120
|
+
// inner QueryIntent; the rawExistsHandler discriminates by operator name.
|
|
2121
|
+
expressionIntent: sub,
|
|
2122
|
+
table: rootTable
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
case "jsonContains":
|
|
2126
|
+
return {
|
|
2127
|
+
type: "where",
|
|
2128
|
+
column: cond.field,
|
|
2129
|
+
operator: cond.reversed ? "jsonContainedBy" : "jsonContains",
|
|
2130
|
+
value: cond.value,
|
|
2131
|
+
table: rootTable
|
|
2132
|
+
};
|
|
2133
|
+
case "any":
|
|
2134
|
+
return {
|
|
2135
|
+
type: "where",
|
|
2136
|
+
column: cond.field,
|
|
2137
|
+
operator: "any",
|
|
2138
|
+
values: cond.values,
|
|
2139
|
+
table: rootTable
|
|
2140
|
+
};
|
|
2141
|
+
case "jsonExists":
|
|
2142
|
+
return {
|
|
2143
|
+
type: "where",
|
|
2144
|
+
column: cond.field,
|
|
2145
|
+
operator: "jsonExists",
|
|
2146
|
+
value: cond.key,
|
|
2147
|
+
table: rootTable
|
|
2148
|
+
};
|
|
2149
|
+
case "expression":
|
|
2150
|
+
return {
|
|
2151
|
+
type: "where",
|
|
2152
|
+
operator: "expression",
|
|
2153
|
+
expressionIntent: cond.expr,
|
|
2154
|
+
value: cond.value,
|
|
2155
|
+
subqueryOperator: cond.operator,
|
|
2156
|
+
table: rootTable
|
|
2157
|
+
};
|
|
2158
|
+
default:
|
|
2159
|
+
return null;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
function convertOrderBy(order, rootTable) {
|
|
2163
|
+
const direction = order.direction === "desc" ? "DESC" : "ASC";
|
|
2164
|
+
const nulls = order.nulls ? order.nulls === "first" ? "FIRST" : "LAST" : void 0;
|
|
2165
|
+
if (order.expression) {
|
|
2166
|
+
const base = {
|
|
2167
|
+
type: "orderBy",
|
|
2168
|
+
expressionIntent: order.expression,
|
|
2169
|
+
direction,
|
|
2170
|
+
table: rootTable
|
|
2171
|
+
};
|
|
2172
|
+
if (nulls) {
|
|
2173
|
+
return { ...base, nulls };
|
|
2174
|
+
}
|
|
2175
|
+
return base;
|
|
2176
|
+
}
|
|
2177
|
+
const decision = {
|
|
2178
|
+
type: "orderBy",
|
|
2179
|
+
direction,
|
|
2180
|
+
table: rootTable,
|
|
2181
|
+
// field is optional in OrderByIntent after expression extension (exactOptionalPropertyTypes)
|
|
2182
|
+
...order.field ? { column: order.field } : {}
|
|
2183
|
+
};
|
|
2184
|
+
if (nulls) {
|
|
2185
|
+
return { ...decision, nulls };
|
|
2186
|
+
}
|
|
2187
|
+
return decision;
|
|
2188
|
+
}
|
|
2189
|
+
var init_intent_to_decisions = __esm({
|
|
2190
|
+
"src/intent-to-decisions.ts"() {
|
|
2191
|
+
"use strict";
|
|
2192
|
+
init_select_expression_handlers();
|
|
2193
|
+
}
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
// src/handlers/types.ts
|
|
2197
|
+
function createCompilerState() {
|
|
2198
|
+
return {
|
|
2199
|
+
parameters: [],
|
|
2200
|
+
paramIndex: 0,
|
|
2201
|
+
ctes: /* @__PURE__ */ new Map(),
|
|
2202
|
+
aliases: /* @__PURE__ */ new Map(),
|
|
2203
|
+
joins: []
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
function isSqlExpression(value) {
|
|
2207
|
+
return typeof value === "object" && value !== null && "sql" in value && typeof value.sql === "string";
|
|
2208
|
+
}
|
|
2209
|
+
function isRangeValue(value) {
|
|
2210
|
+
return typeof value === "object" && value !== null && ("lower" in value || "upper" in value);
|
|
2211
|
+
}
|
|
2212
|
+
function isParamRef(value) {
|
|
2213
|
+
return typeof value === "object" && value !== null && "paramIndex" in value && typeof value.paramIndex === "number";
|
|
2214
|
+
}
|
|
2215
|
+
function isSelectWithFields(value) {
|
|
2216
|
+
return typeof value === "object" && value !== null && "fields" in value && (Array.isArray(value.fields) || value.fields === void 0);
|
|
2217
|
+
}
|
|
2218
|
+
var COMPARISON_OPERATORS, PATTERN_OPERATORS, NULL_OPERATORS, COLLECTION_OPERATORS, LOGICAL_OPERATORS, ALL_OPERATORS;
|
|
2219
|
+
var init_types = __esm({
|
|
2220
|
+
"src/handlers/types.ts"() {
|
|
2221
|
+
"use strict";
|
|
2222
|
+
COMPARISON_OPERATORS = {
|
|
2223
|
+
EQ: "=",
|
|
2224
|
+
NEQ: "!=",
|
|
2225
|
+
LT: "<",
|
|
2226
|
+
LTE: "<=",
|
|
2227
|
+
GT: ">",
|
|
2228
|
+
GTE: ">=",
|
|
2229
|
+
// Aliases
|
|
2230
|
+
eq: "=",
|
|
2231
|
+
ne: "!=",
|
|
2232
|
+
lt: "<",
|
|
2233
|
+
lte: "<=",
|
|
2234
|
+
gt: ">",
|
|
2235
|
+
gte: ">="
|
|
2236
|
+
};
|
|
2237
|
+
PATTERN_OPERATORS = {
|
|
2238
|
+
LIKE: "like",
|
|
2239
|
+
ILIKE: "ilike",
|
|
2240
|
+
NOT_LIKE: "notLike",
|
|
2241
|
+
NOT_ILIKE: "notIlike"
|
|
2242
|
+
};
|
|
2243
|
+
NULL_OPERATORS = {
|
|
2244
|
+
IS_NULL: "isNull",
|
|
2245
|
+
IS_NOT_NULL: "isNotNull"
|
|
2246
|
+
};
|
|
2247
|
+
COLLECTION_OPERATORS = {
|
|
2248
|
+
IN: "in",
|
|
2249
|
+
NOT_IN: "notIn",
|
|
2250
|
+
ANY: "any"
|
|
2251
|
+
};
|
|
2252
|
+
LOGICAL_OPERATORS = {
|
|
2253
|
+
AND: "and",
|
|
2254
|
+
OR: "or",
|
|
2255
|
+
NOT: "not"
|
|
2256
|
+
};
|
|
2257
|
+
ALL_OPERATORS = {
|
|
2258
|
+
...COMPARISON_OPERATORS,
|
|
2259
|
+
...PATTERN_OPERATORS,
|
|
2260
|
+
...NULL_OPERATORS,
|
|
2261
|
+
...COLLECTION_OPERATORS,
|
|
2262
|
+
...LOGICAL_OPERATORS
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
// src/raw-expression-parser.ts
|
|
2268
|
+
function tokenise(input) {
|
|
2269
|
+
const tokens = [];
|
|
2270
|
+
let i = 0;
|
|
2271
|
+
const src = input.trim();
|
|
2272
|
+
while (i < src.length) {
|
|
2273
|
+
const ch = src[i];
|
|
2274
|
+
if (/\s/.test(ch)) {
|
|
2275
|
+
i++;
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (/[0-9]/.test(ch)) {
|
|
2279
|
+
let num = "";
|
|
2280
|
+
while (i < src.length && /[0-9]/.test(src[i])) {
|
|
2281
|
+
num += src[i++];
|
|
2282
|
+
}
|
|
2283
|
+
if (src[i] === "." && i + 1 < src.length && /[0-9]/.test(src[i + 1])) {
|
|
2284
|
+
num += src[i++];
|
|
2285
|
+
while (i < src.length && /[0-9]/.test(src[i])) {
|
|
2286
|
+
num += src[i++];
|
|
2287
|
+
}
|
|
2288
|
+
tokens.push({ kind: "FLOAT", value: num });
|
|
2289
|
+
} else {
|
|
2290
|
+
tokens.push({ kind: "INT", value: num });
|
|
2291
|
+
}
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
if (ch === "'") {
|
|
2295
|
+
let str = "";
|
|
2296
|
+
i++;
|
|
2297
|
+
while (i < src.length) {
|
|
2298
|
+
if (src[i] === "'" && src[i + 1] === "'") {
|
|
2299
|
+
str += "'";
|
|
2300
|
+
i += 2;
|
|
2301
|
+
} else if (src[i] === "'") {
|
|
2302
|
+
i++;
|
|
2303
|
+
break;
|
|
2304
|
+
} else {
|
|
2305
|
+
str += src[i++];
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
tokens.push({ kind: "STRING", value: str });
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
2312
|
+
let ident = "";
|
|
2313
|
+
while (i < src.length && /[a-zA-Z0-9_]/.test(src[i])) {
|
|
2314
|
+
ident += src[i++];
|
|
2315
|
+
}
|
|
2316
|
+
tokens.push({ kind: "IDENT", value: ident });
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
if (ch === ":" && src[i + 1] === ":") {
|
|
2320
|
+
tokens.push({ kind: "CAST", value: "::" });
|
|
2321
|
+
i += 2;
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (["+", "-", "*", "/"].includes(ch)) {
|
|
2325
|
+
tokens.push({ kind: "OP", value: ch });
|
|
2326
|
+
i++;
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
if (ch === "(") {
|
|
2330
|
+
tokens.push({ kind: "LPAREN", value: "(" });
|
|
2331
|
+
i++;
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
if (ch === ")") {
|
|
2335
|
+
tokens.push({ kind: "RPAREN", value: ")" });
|
|
2336
|
+
i++;
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
if (ch === ",") {
|
|
2340
|
+
tokens.push({ kind: "COMMA", value: "," });
|
|
2341
|
+
i++;
|
|
2342
|
+
continue;
|
|
2343
|
+
}
|
|
2344
|
+
if (ch === ".") {
|
|
2345
|
+
tokens.push({ kind: "DOT", value: "." });
|
|
2346
|
+
i++;
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
throw new Error(
|
|
2350
|
+
`parseExpression: unexpected character '${ch}' at position ${i} in: ${input}`
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
tokens.push({ kind: "EOF", value: "" });
|
|
2354
|
+
return tokens;
|
|
2355
|
+
}
|
|
2356
|
+
function buildAExpr(op3, left, right) {
|
|
2357
|
+
return {
|
|
2358
|
+
A_Expr: {
|
|
2359
|
+
kind: "AEXPR_OP",
|
|
2360
|
+
name: [{ String: { sval: op3 } }],
|
|
2361
|
+
lexpr: left,
|
|
2362
|
+
rexpr: right
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
function parseExpression(sqlFragment) {
|
|
2367
|
+
const trimmed = sqlFragment.trim();
|
|
2368
|
+
if (!trimmed) {
|
|
2369
|
+
throw new Error("parseExpression: empty SQL fragment");
|
|
2370
|
+
}
|
|
2371
|
+
const tokens = tokenise(trimmed);
|
|
2372
|
+
const parser = new Parser(tokens);
|
|
2373
|
+
const node = parser.parseExpr();
|
|
2374
|
+
if (parser["peek"]().kind !== "EOF") {
|
|
2375
|
+
const remaining = tokens.slice(parser["pos"]).map((t) => t.value).join(" ");
|
|
2376
|
+
throw new Error(
|
|
2377
|
+
`parseExpression: unexpected trailing tokens '${remaining}' in: ${sqlFragment}`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
return node;
|
|
2381
|
+
}
|
|
2382
|
+
var Parser;
|
|
2383
|
+
var init_raw_expression_parser = __esm({
|
|
2384
|
+
"src/raw-expression-parser.ts"() {
|
|
2385
|
+
"use strict";
|
|
2386
|
+
Parser = class {
|
|
2387
|
+
constructor(tokens) {
|
|
2388
|
+
this.tokens = tokens;
|
|
2389
|
+
}
|
|
2390
|
+
tokens;
|
|
2391
|
+
pos = 0;
|
|
2392
|
+
peek() {
|
|
2393
|
+
return this.tokens[this.pos];
|
|
2394
|
+
}
|
|
2395
|
+
consume() {
|
|
2396
|
+
const tok = this.tokens[this.pos];
|
|
2397
|
+
this.pos++;
|
|
2398
|
+
return tok;
|
|
2399
|
+
}
|
|
1945
2400
|
expect(kind) {
|
|
1946
2401
|
const tok = this.peek();
|
|
1947
2402
|
if (tok.kind !== kind) {
|
|
@@ -2121,566 +2576,177 @@ var init_raw_expression_parser = __esm({
|
|
|
2121
2576
|
int4: "int4",
|
|
2122
2577
|
int2: "int2",
|
|
2123
2578
|
smallint: "int2",
|
|
2124
|
-
int8: "int8",
|
|
2125
|
-
bigint: "int8",
|
|
2126
|
-
float4: "float4",
|
|
2127
|
-
real: "float4",
|
|
2128
|
-
float8: "float8",
|
|
2129
|
-
"double precision": "float8",
|
|
2130
|
-
bool: "bool",
|
|
2131
|
-
boolean: "bool",
|
|
2132
|
-
text: "text",
|
|
2133
|
-
varchar: "varchar",
|
|
2134
|
-
bpchar: "bpchar",
|
|
2135
|
-
char: "bpchar",
|
|
2136
|
-
bytea: "bytea",
|
|
2137
|
-
date: "date",
|
|
2138
|
-
time: "time",
|
|
2139
|
-
timetz: "timetz",
|
|
2140
|
-
timestamp: "timestamp",
|
|
2141
|
-
timestamptz: "timestamptz",
|
|
2142
|
-
interval: "interval",
|
|
2143
|
-
numeric: "numeric",
|
|
2144
|
-
decimal: "numeric",
|
|
2145
|
-
uuid: "uuid",
|
|
2146
|
-
json: "json",
|
|
2147
|
-
jsonb: "jsonb",
|
|
2148
|
-
oid: "oid",
|
|
2149
|
-
name: "name"
|
|
2150
|
-
};
|
|
2151
|
-
const pgName = pgCatalogTypes[lower];
|
|
2152
|
-
if (pgName) {
|
|
2153
|
-
return {
|
|
2154
|
-
names: [
|
|
2155
|
-
{ String: { sval: "pg_catalog" } },
|
|
2156
|
-
{ String: { sval: pgName } }
|
|
2157
|
-
],
|
|
2158
|
-
typemod: -1
|
|
2159
|
-
};
|
|
2160
|
-
}
|
|
2161
|
-
return {
|
|
2162
|
-
names: [{ String: { sval: lower } }],
|
|
2163
|
-
typemod: -1
|
|
2164
|
-
};
|
|
2165
|
-
}
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
});
|
|
2169
|
-
|
|
2170
|
-
// src/compiler-utils.ts
|
|
2171
|
-
import { InvalidOperationError } from "@dbsp/core";
|
|
2172
|
-
function inferPgArrayType(columnName, columnTypes, sampleValue) {
|
|
2173
|
-
if (columnTypes?.[columnName]) {
|
|
2174
|
-
const pgBase = mapToPgBaseType(columnTypes[columnName]);
|
|
2175
|
-
return `${pgBase}[]`;
|
|
2176
|
-
}
|
|
2177
|
-
if (sampleValue !== null && sampleValue !== void 0) {
|
|
2178
|
-
if (typeof sampleValue === "bigint") return "int8[]";
|
|
2179
|
-
if (typeof sampleValue === "number") {
|
|
2180
|
-
return Number.isInteger(sampleValue) ? "int4[]" : "float8[]";
|
|
2181
|
-
}
|
|
2182
|
-
if (typeof sampleValue === "string") return "text[]";
|
|
2183
|
-
if (typeof sampleValue === "boolean") return "bool[]";
|
|
2184
|
-
if (typeof sampleValue === "object") return "jsonb[]";
|
|
2185
|
-
}
|
|
2186
|
-
return "text[]";
|
|
2187
|
-
}
|
|
2188
|
-
function mapToPgBaseType(pgType) {
|
|
2189
|
-
const normalized = pgType.toUpperCase().replace(/\(.*\)/, "").trim();
|
|
2190
|
-
switch (normalized) {
|
|
2191
|
-
// ColumnType aliases (lowercase ColumnType values from ModelIR)
|
|
2192
|
-
case "STRING":
|
|
2193
|
-
return "text";
|
|
2194
|
-
case "NUMBER":
|
|
2195
|
-
return "float8";
|
|
2196
|
-
case "DATETIME":
|
|
2197
|
-
return "timestamptz";
|
|
2198
|
-
case "TIME":
|
|
2199
|
-
return "time";
|
|
2200
|
-
// PostgreSQL native types
|
|
2201
|
-
case "INTEGER":
|
|
2202
|
-
case "INT":
|
|
2203
|
-
case "INT4":
|
|
2204
|
-
case "SERIAL":
|
|
2205
|
-
return "int4";
|
|
2206
|
-
case "BIGINT":
|
|
2207
|
-
case "INT8":
|
|
2208
|
-
case "BIGSERIAL":
|
|
2209
|
-
return "int8";
|
|
2210
|
-
case "SMALLINT":
|
|
2211
|
-
case "INT2":
|
|
2212
|
-
return "int2";
|
|
2213
|
-
case "REAL":
|
|
2214
|
-
case "FLOAT4":
|
|
2215
|
-
return "float4";
|
|
2216
|
-
case "DOUBLE PRECISION":
|
|
2217
|
-
case "FLOAT8":
|
|
2218
|
-
case "FLOAT":
|
|
2219
|
-
case "NUMERIC":
|
|
2220
|
-
case "DECIMAL":
|
|
2221
|
-
return "float8";
|
|
2222
|
-
case "TEXT":
|
|
2223
|
-
case "VARCHAR":
|
|
2224
|
-
case "CHAR":
|
|
2225
|
-
case "CHARACTER VARYING":
|
|
2226
|
-
return "text";
|
|
2227
|
-
case "BOOLEAN":
|
|
2228
|
-
case "BOOL":
|
|
2229
|
-
return "bool";
|
|
2230
|
-
case "JSON":
|
|
2231
|
-
case "JSONB":
|
|
2232
|
-
return "jsonb";
|
|
2233
|
-
case "UUID":
|
|
2234
|
-
return "uuid";
|
|
2235
|
-
case "TIMESTAMP":
|
|
2236
|
-
case "TIMESTAMPTZ":
|
|
2237
|
-
case "TIMESTAMP WITH TIME ZONE":
|
|
2238
|
-
return "timestamptz";
|
|
2239
|
-
case "DATE":
|
|
2240
|
-
return "date";
|
|
2241
|
-
default:
|
|
2242
|
-
return pgType.toLowerCase();
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
function stripArraySuffix(pgArrayType) {
|
|
2246
|
-
return pgArrayType.endsWith("[]") ? pgArrayType.slice(0, -2) : pgArrayType;
|
|
2247
|
-
}
|
|
2248
|
-
function mapModelIRTypeToPgBase(dataType) {
|
|
2249
|
-
const t = dataType.toLowerCase();
|
|
2250
|
-
if (t === "integer" || t === "int" || t === "serial" || t === "bigserial")
|
|
2251
|
-
return "int4";
|
|
2252
|
-
if (t === "bigint") return "int8";
|
|
2253
|
-
if (t === "decimal" || t === "float" || t === "double" || t === "real" || t === "numeric")
|
|
2254
|
-
return "float8";
|
|
2255
|
-
if (t === "text" || t === "string" || t === "varchar" || t === "char")
|
|
2256
|
-
return "text";
|
|
2257
|
-
if (t === "boolean" || t === "bool") return "bool";
|
|
2258
|
-
if (t === "json" || t === "jsonb") return "jsonb";
|
|
2259
|
-
if (t === "uuid") return "uuid";
|
|
2260
|
-
if (t === "timestamp" || t === "timestamptz" || t === "datetime")
|
|
2261
|
-
return "timestamptz";
|
|
2262
|
-
if (t === "date") return "date";
|
|
2263
|
-
return void 0;
|
|
2264
|
-
}
|
|
2265
|
-
function transposeToColumnArrays(columns, values) {
|
|
2266
|
-
return columns.map((_, colIdx) => values.map((row) => row[colIdx]));
|
|
2267
|
-
}
|
|
2268
|
-
function validateBatchCardinality(columns, values) {
|
|
2269
|
-
for (let i = 0; i < values.length; i++) {
|
|
2270
|
-
const row = values[i];
|
|
2271
|
-
if (row === void 0 || row.length !== columns.length) {
|
|
2272
|
-
throw new InvalidOperationError(
|
|
2273
|
-
"insert",
|
|
2274
|
-
`Array length mismatch at row ${i}: expected ${columns.length} columns, got ${row?.length ?? 0}`
|
|
2275
|
-
);
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
function parseRawExpression(sqlFragment) {
|
|
2280
|
-
try {
|
|
2281
|
-
return parseExpression(sqlFragment);
|
|
2282
|
-
} catch {
|
|
2283
|
-
throw new Error(
|
|
2284
|
-
`sql(): cannot parse raw SQL fragment as expression: ${sqlFragment}`
|
|
2285
|
-
);
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
var init_compiler_utils = __esm({
|
|
2289
|
-
"src/compiler-utils.ts"() {
|
|
2290
|
-
"use strict";
|
|
2291
|
-
init_raw_expression_parser();
|
|
2292
|
-
}
|
|
2293
|
-
});
|
|
2294
|
-
|
|
2295
|
-
// src/validate.ts
|
|
2296
|
-
function validateIdentifier(value, type) {
|
|
2297
|
-
if (!value || value.length === 0) {
|
|
2298
|
-
throw new InvalidIdentifierError(value, type, "cannot be empty");
|
|
2299
|
-
}
|
|
2300
|
-
if (value.length > 63) {
|
|
2301
|
-
throw new InvalidIdentifierError(
|
|
2302
|
-
value,
|
|
2303
|
-
type,
|
|
2304
|
-
`exceeds maximum length of 63 characters (got ${value.length})`
|
|
2305
|
-
);
|
|
2306
|
-
}
|
|
2307
|
-
const validIdentifierPattern = /^[a-zA-Z_][a-zA-Z0-9_$]*$/;
|
|
2308
|
-
if (!validIdentifierPattern.test(value)) {
|
|
2309
|
-
if (/[\x00-\x1f\x7f]/.test(value)) {
|
|
2310
|
-
throw new InvalidIdentifierError(
|
|
2311
|
-
value,
|
|
2312
|
-
type,
|
|
2313
|
-
"contains control characters"
|
|
2314
|
-
);
|
|
2315
|
-
}
|
|
2316
|
-
if (/^[0-9]/.test(value)) {
|
|
2317
|
-
throw new InvalidIdentifierError(
|
|
2318
|
-
value,
|
|
2319
|
-
type,
|
|
2320
|
-
"cannot start with a digit"
|
|
2321
|
-
);
|
|
2322
|
-
}
|
|
2323
|
-
if (/[^\w$]/.test(value)) {
|
|
2324
|
-
throw new InvalidIdentifierError(
|
|
2325
|
-
value,
|
|
2326
|
-
type,
|
|
2327
|
-
"contains invalid characters (only letters, digits, underscore, and $ allowed)"
|
|
2328
|
-
);
|
|
2329
|
-
}
|
|
2330
|
-
throw new InvalidIdentifierError(
|
|
2331
|
-
value,
|
|
2332
|
-
type,
|
|
2333
|
-
"does not match valid identifier pattern"
|
|
2334
|
-
);
|
|
2335
|
-
}
|
|
2336
|
-
if (value.includes("\0")) {
|
|
2337
|
-
throw new InvalidIdentifierError(value, type, "contains null byte");
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
function isReservedKeyword(value) {
|
|
2341
|
-
return SQL_RESERVED_KEYWORDS.has(value.toLowerCase());
|
|
2342
|
-
}
|
|
2343
|
-
function validateQualifiedIdentifier(schemaTable) {
|
|
2344
|
-
const parts = schemaTable.split(".");
|
|
2345
|
-
if (parts.length === 1) {
|
|
2346
|
-
validateIdentifier(parts[0], "table");
|
|
2347
|
-
return { table: parts[0] };
|
|
2579
|
+
int8: "int8",
|
|
2580
|
+
bigint: "int8",
|
|
2581
|
+
float4: "float4",
|
|
2582
|
+
real: "float4",
|
|
2583
|
+
float8: "float8",
|
|
2584
|
+
"double precision": "float8",
|
|
2585
|
+
bool: "bool",
|
|
2586
|
+
boolean: "bool",
|
|
2587
|
+
text: "text",
|
|
2588
|
+
varchar: "varchar",
|
|
2589
|
+
bpchar: "bpchar",
|
|
2590
|
+
char: "bpchar",
|
|
2591
|
+
bytea: "bytea",
|
|
2592
|
+
date: "date",
|
|
2593
|
+
time: "time",
|
|
2594
|
+
timetz: "timetz",
|
|
2595
|
+
timestamp: "timestamp",
|
|
2596
|
+
timestamptz: "timestamptz",
|
|
2597
|
+
interval: "interval",
|
|
2598
|
+
numeric: "numeric",
|
|
2599
|
+
decimal: "numeric",
|
|
2600
|
+
uuid: "uuid",
|
|
2601
|
+
json: "json",
|
|
2602
|
+
jsonb: "jsonb",
|
|
2603
|
+
oid: "oid",
|
|
2604
|
+
name: "name"
|
|
2605
|
+
};
|
|
2606
|
+
const pgName = pgCatalogTypes[lower];
|
|
2607
|
+
if (pgName) {
|
|
2608
|
+
return {
|
|
2609
|
+
names: [
|
|
2610
|
+
{ String: { sval: "pg_catalog" } },
|
|
2611
|
+
{ String: { sval: pgName } }
|
|
2612
|
+
],
|
|
2613
|
+
typemod: -1
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
return {
|
|
2617
|
+
names: [{ String: { sval: lower } }],
|
|
2618
|
+
typemod: -1
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2348
2622
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2623
|
+
});
|
|
2624
|
+
|
|
2625
|
+
// src/compiler-utils.ts
|
|
2626
|
+
import { InvalidOperationError, validateTypeName as validateTypeName2 } from "@dbsp/core";
|
|
2627
|
+
function inferPgArrayType(columnName, columnTypes, sampleValue) {
|
|
2628
|
+
if (columnTypes?.[columnName]) {
|
|
2629
|
+
const pgBase = mapToPgBaseType(columnTypes[columnName]);
|
|
2630
|
+
return `${pgBase}[]`;
|
|
2355
2631
|
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
"
|
|
2359
|
-
|
|
2360
|
-
);
|
|
2361
|
-
}
|
|
2362
|
-
function validateIdentifiers(identifiers) {
|
|
2363
|
-
for (const [value, type] of Object.entries(identifiers)) {
|
|
2364
|
-
if (value) {
|
|
2365
|
-
validateIdentifier(value, type);
|
|
2632
|
+
if (sampleValue !== null && sampleValue !== void 0) {
|
|
2633
|
+
if (typeof sampleValue === "bigint") return "int8[]";
|
|
2634
|
+
if (typeof sampleValue === "number") {
|
|
2635
|
+
return Number.isInteger(sampleValue) ? "int4[]" : "float8[]";
|
|
2366
2636
|
}
|
|
2637
|
+
if (typeof sampleValue === "string") return "text[]";
|
|
2638
|
+
if (typeof sampleValue === "boolean") return "bool[]";
|
|
2639
|
+
if (typeof sampleValue === "object") return "jsonb[]";
|
|
2367
2640
|
}
|
|
2641
|
+
return "text[]";
|
|
2368
2642
|
}
|
|
2369
|
-
function
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
name,
|
|
2428
|
-
context,
|
|
2429
|
-
"contains block-comment opener (/*)"
|
|
2430
|
-
);
|
|
2431
|
-
}
|
|
2432
|
-
if (/\*\//.test(name)) {
|
|
2433
|
-
throw new InvalidIdentifierError(
|
|
2434
|
-
name,
|
|
2435
|
-
context,
|
|
2436
|
-
"contains block-comment closer (*/)"
|
|
2437
|
-
);
|
|
2438
|
-
}
|
|
2439
|
-
if (/\$\$/.test(name)) {
|
|
2440
|
-
throw new InvalidIdentifierError(
|
|
2441
|
-
name,
|
|
2442
|
-
context,
|
|
2443
|
-
"contains dollar-quoting ($$)"
|
|
2444
|
-
);
|
|
2445
|
-
}
|
|
2446
|
-
if (/\s/.test(name)) {
|
|
2447
|
-
throw new InvalidIdentifierError(name, context, "contains whitespace");
|
|
2448
|
-
}
|
|
2449
|
-
if (!/^[a-zA-Z0-9_\-.]+$/.test(name)) {
|
|
2450
|
-
throw new InvalidIdentifierError(
|
|
2451
|
-
name,
|
|
2452
|
-
context,
|
|
2453
|
-
"contains characters not allowed in extension names (only letters, digits, underscore, hyphen, and dot allowed)"
|
|
2454
|
-
);
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
function validateCollationName(name, context = "collation") {
|
|
2458
|
-
if (!name || name.length === 0) {
|
|
2459
|
-
throw new InvalidIdentifierError(name, context, "cannot be empty");
|
|
2460
|
-
}
|
|
2461
|
-
if (Buffer.byteLength(name, "utf8") > 63) {
|
|
2462
|
-
throw new InvalidIdentifierError(
|
|
2463
|
-
name,
|
|
2464
|
-
context,
|
|
2465
|
-
`exceeds maximum length of 63 bytes (got ${Buffer.byteLength(name, "utf8")})`
|
|
2466
|
-
);
|
|
2467
|
-
}
|
|
2468
|
-
if (/\x00/.test(name)) {
|
|
2469
|
-
throw new InvalidIdentifierError(
|
|
2470
|
-
name,
|
|
2471
|
-
context,
|
|
2472
|
-
"contains NUL byte (\\x00) which would be silently truncated by PostgreSQL"
|
|
2473
|
-
);
|
|
2474
|
-
}
|
|
2475
|
-
if (/[\x01-\x1f\x7f]/.test(name)) {
|
|
2476
|
-
throw new InvalidIdentifierError(
|
|
2477
|
-
name,
|
|
2478
|
-
context,
|
|
2479
|
-
"contains control characters (only printable characters allowed)"
|
|
2480
|
-
);
|
|
2481
|
-
}
|
|
2482
|
-
if (/[\\]/.test(name)) {
|
|
2483
|
-
throw new InvalidIdentifierError(
|
|
2484
|
-
name,
|
|
2485
|
-
context,
|
|
2486
|
-
"contains backslash (forbidden in collation names)"
|
|
2487
|
-
);
|
|
2488
|
-
}
|
|
2489
|
-
if (/"/.test(name)) {
|
|
2490
|
-
throw new InvalidIdentifierError(
|
|
2491
|
-
name,
|
|
2492
|
-
context,
|
|
2493
|
-
"contains double-quote (identifier injection risk)"
|
|
2494
|
-
);
|
|
2495
|
-
}
|
|
2496
|
-
if (/'/.test(name)) {
|
|
2497
|
-
throw new InvalidIdentifierError(
|
|
2498
|
-
name,
|
|
2499
|
-
context,
|
|
2500
|
-
"contains single-quote (string injection risk)"
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2503
|
-
if (/;/.test(name)) {
|
|
2504
|
-
throw new InvalidIdentifierError(
|
|
2505
|
-
name,
|
|
2506
|
-
context,
|
|
2507
|
-
"contains semicolon (statement injection risk)"
|
|
2508
|
-
);
|
|
2509
|
-
}
|
|
2510
|
-
if (/--/.test(name)) {
|
|
2511
|
-
throw new InvalidIdentifierError(
|
|
2512
|
-
name,
|
|
2513
|
-
context,
|
|
2514
|
-
"contains line-comment marker (--)"
|
|
2515
|
-
);
|
|
2516
|
-
}
|
|
2517
|
-
if (/\/\*/.test(name)) {
|
|
2518
|
-
throw new InvalidIdentifierError(
|
|
2519
|
-
name,
|
|
2520
|
-
context,
|
|
2521
|
-
"contains block-comment opener (/*)"
|
|
2522
|
-
);
|
|
2523
|
-
}
|
|
2524
|
-
if (/\*\//.test(name)) {
|
|
2525
|
-
throw new InvalidIdentifierError(
|
|
2526
|
-
name,
|
|
2527
|
-
context,
|
|
2528
|
-
"contains block-comment closer (*/)"
|
|
2529
|
-
);
|
|
2530
|
-
}
|
|
2531
|
-
if (/\$\$/.test(name)) {
|
|
2532
|
-
throw new InvalidIdentifierError(
|
|
2533
|
-
name,
|
|
2534
|
-
context,
|
|
2535
|
-
"contains dollar-quoting ($$)"
|
|
2536
|
-
);
|
|
2537
|
-
}
|
|
2538
|
-
if (/\s/.test(name)) {
|
|
2539
|
-
throw new InvalidIdentifierError(name, context, "contains whitespace");
|
|
2540
|
-
}
|
|
2541
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_.-]*(?:@[A-Za-z0-9-]{1,10})?$/.test(name)) {
|
|
2542
|
-
throw new InvalidIdentifierError(
|
|
2543
|
-
name,
|
|
2544
|
-
context,
|
|
2545
|
-
"contains characters not allowed in collation names (only letters, digits, underscore, hyphen, and dot allowed; optional @modifier must be 1-10 alphanumeric/hyphen characters, e.g. @euro, @latin9, @iso8859-15)"
|
|
2546
|
-
);
|
|
2643
|
+
function mapToPgBaseType(pgType) {
|
|
2644
|
+
const normalized = pgType.toUpperCase().replace(/\(.*\)/, "").trim();
|
|
2645
|
+
switch (normalized) {
|
|
2646
|
+
// ColumnType aliases (lowercase ColumnType values from ModelIR)
|
|
2647
|
+
case "STRING":
|
|
2648
|
+
return "text";
|
|
2649
|
+
case "NUMBER":
|
|
2650
|
+
return "float8";
|
|
2651
|
+
case "DATETIME":
|
|
2652
|
+
return "timestamptz";
|
|
2653
|
+
case "TIME":
|
|
2654
|
+
return "time";
|
|
2655
|
+
// PostgreSQL native types
|
|
2656
|
+
case "INTEGER":
|
|
2657
|
+
case "INT":
|
|
2658
|
+
case "INT4":
|
|
2659
|
+
case "SERIAL":
|
|
2660
|
+
return "int4";
|
|
2661
|
+
case "BIGINT":
|
|
2662
|
+
case "INT8":
|
|
2663
|
+
case "BIGSERIAL":
|
|
2664
|
+
return "int8";
|
|
2665
|
+
case "SMALLINT":
|
|
2666
|
+
case "INT2":
|
|
2667
|
+
return "int2";
|
|
2668
|
+
case "REAL":
|
|
2669
|
+
case "FLOAT4":
|
|
2670
|
+
return "float4";
|
|
2671
|
+
case "DOUBLE PRECISION":
|
|
2672
|
+
case "FLOAT8":
|
|
2673
|
+
case "FLOAT":
|
|
2674
|
+
case "NUMERIC":
|
|
2675
|
+
case "DECIMAL":
|
|
2676
|
+
return "float8";
|
|
2677
|
+
case "TEXT":
|
|
2678
|
+
case "VARCHAR":
|
|
2679
|
+
case "CHAR":
|
|
2680
|
+
case "CHARACTER VARYING":
|
|
2681
|
+
return "text";
|
|
2682
|
+
case "BOOLEAN":
|
|
2683
|
+
case "BOOL":
|
|
2684
|
+
return "bool";
|
|
2685
|
+
case "JSON":
|
|
2686
|
+
case "JSONB":
|
|
2687
|
+
return "jsonb";
|
|
2688
|
+
case "UUID":
|
|
2689
|
+
return "uuid";
|
|
2690
|
+
case "TIMESTAMP":
|
|
2691
|
+
case "TIMESTAMPTZ":
|
|
2692
|
+
case "TIMESTAMP WITH TIME ZONE":
|
|
2693
|
+
return "timestamptz";
|
|
2694
|
+
case "DATE":
|
|
2695
|
+
return "date";
|
|
2696
|
+
default: {
|
|
2697
|
+
const customType = pgType.toLowerCase();
|
|
2698
|
+
validateTypeName2(customType);
|
|
2699
|
+
return customType;
|
|
2700
|
+
}
|
|
2547
2701
|
}
|
|
2548
2702
|
}
|
|
2549
|
-
function
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2703
|
+
function stripArraySuffix(pgArrayType) {
|
|
2704
|
+
return pgArrayType.endsWith("[]") ? pgArrayType.slice(0, -2) : pgArrayType;
|
|
2705
|
+
}
|
|
2706
|
+
function mapModelIRTypeToPgBase(dataType) {
|
|
2707
|
+
const t = dataType.toLowerCase();
|
|
2708
|
+
if (t === "integer" || t === "int" || t === "serial" || t === "bigserial")
|
|
2709
|
+
return "int4";
|
|
2710
|
+
if (t === "bigint") return "int8";
|
|
2711
|
+
if (t === "decimal" || t === "float" || t === "double" || t === "real" || t === "numeric")
|
|
2712
|
+
return "float8";
|
|
2713
|
+
if (t === "text" || t === "string" || t === "varchar" || t === "char")
|
|
2714
|
+
return "text";
|
|
2715
|
+
if (t === "boolean" || t === "bool") return "bool";
|
|
2716
|
+
if (t === "json" || t === "jsonb") return "jsonb";
|
|
2717
|
+
if (t === "uuid") return "uuid";
|
|
2718
|
+
if (t === "timestamp" || t === "timestamptz" || t === "datetime")
|
|
2719
|
+
return "timestamptz";
|
|
2720
|
+
if (t === "date") return "date";
|
|
2721
|
+
return void 0;
|
|
2722
|
+
}
|
|
2723
|
+
function transposeToColumnArrays(columns, values) {
|
|
2724
|
+
return columns.map((_, colIdx) => values.map((row) => row[colIdx]));
|
|
2725
|
+
}
|
|
2726
|
+
function validateBatchCardinality(columns, values) {
|
|
2727
|
+
for (let i = 0; i < values.length; i++) {
|
|
2728
|
+
const row = values[i];
|
|
2729
|
+
if (row === void 0 || row.length !== columns.length) {
|
|
2730
|
+
throw new InvalidOperationError(
|
|
2731
|
+
"insert",
|
|
2732
|
+
`Array length mismatch at row ${i}: expected ${columns.length} columns, got ${row?.length ?? 0}`
|
|
2733
|
+
);
|
|
2734
|
+
}
|
|
2554
2735
|
}
|
|
2555
2736
|
}
|
|
2556
|
-
function
|
|
2557
|
-
|
|
2737
|
+
function parseRawExpression(sqlFragment) {
|
|
2738
|
+
try {
|
|
2739
|
+
return parseExpression(sqlFragment);
|
|
2740
|
+
} catch {
|
|
2558
2741
|
throw new Error(
|
|
2559
|
-
`
|
|
2742
|
+
`sql(): cannot parse raw SQL fragment as expression: ${sqlFragment}`
|
|
2560
2743
|
);
|
|
2561
2744
|
}
|
|
2562
|
-
return type;
|
|
2563
2745
|
}
|
|
2564
|
-
var
|
|
2565
|
-
|
|
2566
|
-
"src/validate.ts"() {
|
|
2746
|
+
var init_compiler_utils = __esm({
|
|
2747
|
+
"src/compiler-utils.ts"() {
|
|
2567
2748
|
"use strict";
|
|
2568
|
-
|
|
2569
|
-
"all",
|
|
2570
|
-
"analyse",
|
|
2571
|
-
"analyze",
|
|
2572
|
-
"and",
|
|
2573
|
-
"any",
|
|
2574
|
-
"array",
|
|
2575
|
-
"as",
|
|
2576
|
-
"asc",
|
|
2577
|
-
"asymmetric",
|
|
2578
|
-
"authorization",
|
|
2579
|
-
"binary",
|
|
2580
|
-
"both",
|
|
2581
|
-
"case",
|
|
2582
|
-
"cast",
|
|
2583
|
-
"check",
|
|
2584
|
-
"collate",
|
|
2585
|
-
"collation",
|
|
2586
|
-
"column",
|
|
2587
|
-
"concurrently",
|
|
2588
|
-
"constraint",
|
|
2589
|
-
"create",
|
|
2590
|
-
"cross",
|
|
2591
|
-
"current_catalog",
|
|
2592
|
-
"current_date",
|
|
2593
|
-
"current_role",
|
|
2594
|
-
"current_schema",
|
|
2595
|
-
"current_time",
|
|
2596
|
-
"current_timestamp",
|
|
2597
|
-
"current_user",
|
|
2598
|
-
"default",
|
|
2599
|
-
"deferrable",
|
|
2600
|
-
"desc",
|
|
2601
|
-
"distinct",
|
|
2602
|
-
"do",
|
|
2603
|
-
"else",
|
|
2604
|
-
"end",
|
|
2605
|
-
"except",
|
|
2606
|
-
"false",
|
|
2607
|
-
"fetch",
|
|
2608
|
-
"for",
|
|
2609
|
-
"foreign",
|
|
2610
|
-
"freeze",
|
|
2611
|
-
"from",
|
|
2612
|
-
"full",
|
|
2613
|
-
"grant",
|
|
2614
|
-
"group",
|
|
2615
|
-
"having",
|
|
2616
|
-
"ilike",
|
|
2617
|
-
"in",
|
|
2618
|
-
"initially",
|
|
2619
|
-
"inner",
|
|
2620
|
-
"intersect",
|
|
2621
|
-
"into",
|
|
2622
|
-
"is",
|
|
2623
|
-
"isnull",
|
|
2624
|
-
"join",
|
|
2625
|
-
"lateral",
|
|
2626
|
-
"leading",
|
|
2627
|
-
"left",
|
|
2628
|
-
"like",
|
|
2629
|
-
"limit",
|
|
2630
|
-
"localtime",
|
|
2631
|
-
"localtimestamp",
|
|
2632
|
-
"natural",
|
|
2633
|
-
"not",
|
|
2634
|
-
"notnull",
|
|
2635
|
-
"null",
|
|
2636
|
-
"offset",
|
|
2637
|
-
"on",
|
|
2638
|
-
"only",
|
|
2639
|
-
"or",
|
|
2640
|
-
"order",
|
|
2641
|
-
"outer",
|
|
2642
|
-
"overlaps",
|
|
2643
|
-
"placing",
|
|
2644
|
-
"primary",
|
|
2645
|
-
"references",
|
|
2646
|
-
"returning",
|
|
2647
|
-
"right",
|
|
2648
|
-
"select",
|
|
2649
|
-
"session_user",
|
|
2650
|
-
"similar",
|
|
2651
|
-
"some",
|
|
2652
|
-
"symmetric",
|
|
2653
|
-
"table",
|
|
2654
|
-
"timestamp",
|
|
2655
|
-
"tablesample",
|
|
2656
|
-
"then",
|
|
2657
|
-
"to",
|
|
2658
|
-
"trailing",
|
|
2659
|
-
"true",
|
|
2660
|
-
"union",
|
|
2661
|
-
"unique",
|
|
2662
|
-
"user",
|
|
2663
|
-
"using",
|
|
2664
|
-
"variadic",
|
|
2665
|
-
"verbose",
|
|
2666
|
-
"when",
|
|
2667
|
-
"where",
|
|
2668
|
-
"window",
|
|
2669
|
-
"with"
|
|
2670
|
-
]);
|
|
2671
|
-
InvalidIdentifierError = class extends Error {
|
|
2672
|
-
constructor(identifier, identifierType, reason) {
|
|
2673
|
-
super(`Invalid ${identifierType} identifier "${identifier}": ${reason}`);
|
|
2674
|
-
this.identifier = identifier;
|
|
2675
|
-
this.identifierType = identifierType;
|
|
2676
|
-
this.reason = reason;
|
|
2677
|
-
this.name = "InvalidIdentifierError";
|
|
2678
|
-
}
|
|
2679
|
-
identifier;
|
|
2680
|
-
identifierType;
|
|
2681
|
-
reason;
|
|
2682
|
-
};
|
|
2683
|
-
SAFE_TYPE_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_ ]*(\(\d+(,\s*\d+)?\))?(\[\])?$/;
|
|
2749
|
+
init_raw_expression_parser();
|
|
2684
2750
|
}
|
|
2685
2751
|
});
|
|
2686
2752
|
|
|
@@ -9516,14 +9582,50 @@ function idxName(table, columns, customName) {
|
|
|
9516
9582
|
function buildPolicySQL(tableName, policy, schemaName) {
|
|
9517
9583
|
const policyName = quoteIdent2(policy.name, "alias");
|
|
9518
9584
|
const qt = qualifyTable(tableName, schemaName);
|
|
9519
|
-
const
|
|
9585
|
+
const ALLOWED_RLS_COMMANDS = [
|
|
9586
|
+
"ALL",
|
|
9587
|
+
"SELECT",
|
|
9588
|
+
"INSERT",
|
|
9589
|
+
"UPDATE",
|
|
9590
|
+
"DELETE"
|
|
9591
|
+
];
|
|
9592
|
+
const rawCommand = policy.command;
|
|
9593
|
+
if (rawCommand !== void 0 && rawCommand !== null && typeof rawCommand !== "string") {
|
|
9594
|
+
throw new Error(
|
|
9595
|
+
`RLS policy command must be a string, got ${typeof rawCommand}.`
|
|
9596
|
+
);
|
|
9597
|
+
}
|
|
9598
|
+
const rlsCommand = rawCommand ? rawCommand.toUpperCase() : "ALL";
|
|
9599
|
+
if (!ALLOWED_RLS_COMMANDS.includes(
|
|
9600
|
+
rlsCommand
|
|
9601
|
+
)) {
|
|
9602
|
+
throw new Error(
|
|
9603
|
+
`Invalid RLS policy command "${rawCommand}". Must be one of: ${ALLOWED_RLS_COMMANDS.join(", ")}.`
|
|
9604
|
+
);
|
|
9605
|
+
}
|
|
9606
|
+
const forClause = rlsCommand !== "ALL" ? ` FOR ${rlsCommand}` : " FOR ALL";
|
|
9520
9607
|
const asClause = policy.permissive === false ? " AS RESTRICTIVE" : " AS PERMISSIVE";
|
|
9521
9608
|
const toClause = policy.roles && policy.roles.length > 0 ? ` TO ${policy.roles.map((r) => quoteRoleName(r)).join(", ")}` : "";
|
|
9522
|
-
|
|
9523
|
-
if (
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9609
|
+
const usingExpr = policy.using;
|
|
9610
|
+
if (usingExpr !== void 0 && usingExpr !== null && usingExpr !== "") {
|
|
9611
|
+
if (typeof usingExpr !== "string") {
|
|
9612
|
+
throw new Error(
|
|
9613
|
+
`RLS policy USING: expression must be a plain string, got ${typeof usingExpr}.`
|
|
9614
|
+
);
|
|
9615
|
+
}
|
|
9616
|
+
validateSqlExpression(usingExpr, "USING expression");
|
|
9617
|
+
}
|
|
9618
|
+
const withCheckExpr = policy.withCheck;
|
|
9619
|
+
if (withCheckExpr !== void 0 && withCheckExpr !== null && withCheckExpr !== "") {
|
|
9620
|
+
if (typeof withCheckExpr !== "string") {
|
|
9621
|
+
throw new Error(
|
|
9622
|
+
`RLS policy WITH CHECK: expression must be a plain string, got ${typeof withCheckExpr}.`
|
|
9623
|
+
);
|
|
9624
|
+
}
|
|
9625
|
+
validateSqlExpression(withCheckExpr, "WITH CHECK expression");
|
|
9626
|
+
}
|
|
9627
|
+
const usingClause = usingExpr ? ` USING (${usingExpr})` : "";
|
|
9628
|
+
const withCheckClause = withCheckExpr ? ` WITH CHECK (${withCheckExpr})` : "";
|
|
9527
9629
|
return `CREATE POLICY ${policyName} ON ${qt}${forClause}${asClause}${toClause}${usingClause}${withCheckClause};`;
|
|
9528
9630
|
}
|
|
9529
9631
|
function buildSequenceClause(verb, seqName, seq, includeCycleNoCycle = false) {
|
|
@@ -10257,8 +10359,9 @@ function generateCreateTableSQL(table, schemaName) {
|
|
|
10257
10359
|
${body}
|
|
10258
10360
|
)`;
|
|
10259
10361
|
if (table.partition) {
|
|
10362
|
+
const strategy = assertPartitionStrategy(table.partition.strategy);
|
|
10260
10363
|
const partCols = table.partition.columns.map((n) => quoteIdent2(n, "alias")).join(", ");
|
|
10261
|
-
sql += ` PARTITION BY ${
|
|
10364
|
+
sql += ` PARTITION BY ${strategy} (${partCols})`;
|
|
10262
10365
|
}
|
|
10263
10366
|
sql += ";";
|
|
10264
10367
|
return sql;
|
|
@@ -10300,6 +10403,16 @@ function generateTablesPhase(ctx) {
|
|
|
10300
10403
|
}
|
|
10301
10404
|
|
|
10302
10405
|
// src/ddl/ddl-generator.ts
|
|
10406
|
+
var ALLOWED_PARTITION_STRATEGIES = ["RANGE", "LIST", "HASH"];
|
|
10407
|
+
function assertPartitionStrategy(value) {
|
|
10408
|
+
const upper = value.toUpperCase();
|
|
10409
|
+
if (!ALLOWED_PARTITION_STRATEGIES.includes(upper)) {
|
|
10410
|
+
throw new Error(
|
|
10411
|
+
`Invalid partition strategy "${value}". Must be one of: ${ALLOWED_PARTITION_STRATEGIES.join(", ")}.`
|
|
10412
|
+
);
|
|
10413
|
+
}
|
|
10414
|
+
return upper;
|
|
10415
|
+
}
|
|
10303
10416
|
function generateDDL(schema, options = {}) {
|
|
10304
10417
|
const {
|
|
10305
10418
|
includeDropStatements = false,
|
|
@@ -10379,8 +10492,9 @@ function generateCreateTable(table, schemaName, naming) {
|
|
|
10379
10492
|
${elementsStr}
|
|
10380
10493
|
)`;
|
|
10381
10494
|
if (table.partition) {
|
|
10495
|
+
const strategy = assertPartitionStrategy(table.partition.strategy);
|
|
10382
10496
|
const partCols = table.partition.columns.map((col) => quoteIdentifier(naming.toDatabase(col))).join(", ");
|
|
10383
|
-
sql += ` PARTITION BY ${
|
|
10497
|
+
sql += ` PARTITION BY ${strategy} (${partCols})`;
|
|
10384
10498
|
}
|
|
10385
10499
|
sql += ";";
|
|
10386
10500
|
return sql;
|
|
@@ -10459,17 +10573,50 @@ function generateCreateIndex(tableName, idx, schemaName, naming) {
|
|
|
10459
10573
|
function generateCreatePolicy(tableName, policy, schemaName, naming) {
|
|
10460
10574
|
const qualifiedTable = qualifyTable2(tableName, schemaName, naming);
|
|
10461
10575
|
const policyName = quoteIdentifier(policy.name);
|
|
10462
|
-
const
|
|
10576
|
+
const ALLOWED_RLS_COMMANDS = [
|
|
10577
|
+
"ALL",
|
|
10578
|
+
"SELECT",
|
|
10579
|
+
"INSERT",
|
|
10580
|
+
"UPDATE",
|
|
10581
|
+
"DELETE"
|
|
10582
|
+
];
|
|
10583
|
+
const rawCommand = policy.command;
|
|
10584
|
+
if (rawCommand !== void 0 && rawCommand !== null && typeof rawCommand !== "string") {
|
|
10585
|
+
throw new Error(
|
|
10586
|
+
`RLS policy command must be a string, got ${typeof rawCommand}.`
|
|
10587
|
+
);
|
|
10588
|
+
}
|
|
10589
|
+
const rlsCommand = rawCommand ? rawCommand.toUpperCase() : "ALL";
|
|
10590
|
+
if (!ALLOWED_RLS_COMMANDS.includes(
|
|
10591
|
+
rlsCommand
|
|
10592
|
+
)) {
|
|
10593
|
+
throw new Error(
|
|
10594
|
+
`Invalid RLS policy command "${rawCommand}". Must be one of: ${ALLOWED_RLS_COMMANDS.join(", ")}.`
|
|
10595
|
+
);
|
|
10596
|
+
}
|
|
10597
|
+
const forClause = rlsCommand !== "ALL" ? ` FOR ${rlsCommand}` : " FOR ALL";
|
|
10463
10598
|
const asClause = policy.permissive === false ? " AS RESTRICTIVE" : " AS PERMISSIVE";
|
|
10464
10599
|
const toClause = policy.roles && policy.roles.length > 0 ? ` TO ${policy.roles.map((r) => quoteRoleName(r)).join(", ")}` : "";
|
|
10465
|
-
|
|
10466
|
-
|
|
10600
|
+
const usingExpr = policy.using;
|
|
10601
|
+
if (usingExpr !== void 0 && usingExpr !== null && usingExpr !== "") {
|
|
10602
|
+
if (typeof usingExpr !== "string") {
|
|
10603
|
+
throw new Error(
|
|
10604
|
+
`RLS policy USING: expression must be a plain string, got ${typeof usingExpr}.`
|
|
10605
|
+
);
|
|
10606
|
+
}
|
|
10607
|
+
validateSqlExpression(usingExpr, "policy USING expression");
|
|
10467
10608
|
}
|
|
10468
|
-
|
|
10469
|
-
|
|
10609
|
+
const withCheckExpr = policy.withCheck;
|
|
10610
|
+
if (withCheckExpr !== void 0 && withCheckExpr !== null && withCheckExpr !== "") {
|
|
10611
|
+
if (typeof withCheckExpr !== "string") {
|
|
10612
|
+
throw new Error(
|
|
10613
|
+
`RLS policy WITH CHECK: expression must be a plain string, got ${typeof withCheckExpr}.`
|
|
10614
|
+
);
|
|
10615
|
+
}
|
|
10616
|
+
validateSqlExpression(withCheckExpr, "policy WITH CHECK expression");
|
|
10470
10617
|
}
|
|
10471
|
-
const usingClause =
|
|
10472
|
-
const withCheckClause =
|
|
10618
|
+
const usingClause = usingExpr ? ` USING (${usingExpr})` : "";
|
|
10619
|
+
const withCheckClause = withCheckExpr ? ` WITH CHECK (${withCheckExpr})` : "";
|
|
10473
10620
|
return `CREATE POLICY ${policyName} ON ${qualifiedTable}${forClause}${asClause}${toClause}${usingClause}${withCheckClause};`;
|
|
10474
10621
|
}
|
|
10475
10622
|
|
|
@@ -16163,8 +16310,18 @@ function generateAlterColumnSQL(table, column, options, schema) {
|
|
|
16163
16310
|
const statements = [];
|
|
16164
16311
|
if (options.type !== void 0) {
|
|
16165
16312
|
const safeType = validateDbTypeName(options.type);
|
|
16166
|
-
const using = options.using
|
|
16167
|
-
|
|
16313
|
+
const using = options.using;
|
|
16314
|
+
if (using !== void 0) {
|
|
16315
|
+
if (typeof using !== "string") {
|
|
16316
|
+
throw new Error(
|
|
16317
|
+
`ALTER COLUMN USING: expression must be a plain string, got ${typeof using}.`
|
|
16318
|
+
);
|
|
16319
|
+
}
|
|
16320
|
+
validateSqlExpression(using, "ALTER COLUMN USING expression");
|
|
16321
|
+
statements.push(`${prefix} TYPE ${safeType} USING ${using}`);
|
|
16322
|
+
} else {
|
|
16323
|
+
statements.push(`${prefix} TYPE ${safeType}`);
|
|
16324
|
+
}
|
|
16168
16325
|
}
|
|
16169
16326
|
if (options.setNotNull === true) {
|
|
16170
16327
|
statements.push(`${prefix} SET NOT NULL`);
|