@vibeorm/sql 5.7.0 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/ast.d.ts +174 -6
- package/dist/dialect.d.ts +6 -1
- package/dist/dialects/diagnostics.d.ts +1 -1
- package/dist/dialects/migration-shadow-marker.d.ts +65 -0
- package/dist/dialects/postgres-index-expressions.d.ts +3 -1
- package/dist/dialects/sqlite-index-expressions.d.ts +3 -1
- package/dist/index-expressions.d.ts +8 -2
- package/dist/index.d.ts +8 -3
- package/dist/index.js +1095 -389
- package/dist/index.js.map +18 -14
- package/dist/json-patch.d.ts +96 -0
- package/dist/json-path.d.ts +3 -0
- package/dist/native-types.d.ts +70 -0
- package/dist/render.d.ts +9 -0
- package/dist/result-decoder.d.ts +2 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -99,6 +99,360 @@ function jsonListParameter(params) {
|
|
|
99
99
|
function isJsonListParameter(value) {
|
|
100
100
|
return typeof value === "object" && value !== null && JSON_LIST_PARAMETER in value;
|
|
101
101
|
}
|
|
102
|
+
// packages/sql/src/json-patch.ts
|
|
103
|
+
import { internalError } from "@vibeorm/schema";
|
|
104
|
+
|
|
105
|
+
// packages/sql/src/ast.ts
|
|
106
|
+
var COALESCE_REF_NAME = "__vibe_coalesce";
|
|
107
|
+
function coalesceRef(params) {
|
|
108
|
+
return { name: COALESCE_REF_NAME, coalesce: params.members };
|
|
109
|
+
}
|
|
110
|
+
var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
|
|
111
|
+
function isSetOperation(value) {
|
|
112
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
113
|
+
return false;
|
|
114
|
+
const keys = Object.keys(value);
|
|
115
|
+
if (!keys.includes("__op") || !keys.includes("value"))
|
|
116
|
+
return false;
|
|
117
|
+
const candidate = value;
|
|
118
|
+
if (keys.length !== 2 && !(keys.length === 3 && keys.includes("numericType") && candidate.numericType === "Decimal"))
|
|
119
|
+
return false;
|
|
120
|
+
return typeof candidate.__op === "string" && SET_OPERATORS.includes(candidate.__op) && (typeof candidate.value === "number" && Number.isFinite(candidate.value) || typeof candidate.value === "string" || typeof candidate.value === "bigint");
|
|
121
|
+
}
|
|
122
|
+
var LIST_SET_OPERATORS = ["append", "concat"];
|
|
123
|
+
function isListSetOperation(value) {
|
|
124
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
125
|
+
return false;
|
|
126
|
+
const keys = Object.keys(value);
|
|
127
|
+
if (keys.length !== 2 || !keys.includes("__listOp") || !keys.includes("value"))
|
|
128
|
+
return false;
|
|
129
|
+
const candidate = value;
|
|
130
|
+
return typeof candidate.__listOp === "string" && LIST_SET_OPERATORS.includes(candidate.__listOp);
|
|
131
|
+
}
|
|
132
|
+
function dbNowMarker(params) {
|
|
133
|
+
return { __dbNow: true, offsetSeconds: params.offsetSeconds };
|
|
134
|
+
}
|
|
135
|
+
function isDbNowMarker(value) {
|
|
136
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
137
|
+
return false;
|
|
138
|
+
if (!Object.hasOwn(value, "__dbNow"))
|
|
139
|
+
return false;
|
|
140
|
+
const keys = Object.keys(value);
|
|
141
|
+
if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
|
|
142
|
+
return false;
|
|
143
|
+
const candidate = value;
|
|
144
|
+
return candidate.__dbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
|
|
145
|
+
}
|
|
146
|
+
var SQL_DEFAULT_CELL = Object.freeze({ __sqlDefault: true });
|
|
147
|
+
var SQL_KEEP_CELL = Object.freeze({ __sqlKeep: true });
|
|
148
|
+
function isSqlKeepCell(value) {
|
|
149
|
+
return value === SQL_KEEP_CELL;
|
|
150
|
+
}
|
|
151
|
+
function isSqlDefaultCell(value) {
|
|
152
|
+
return value === SQL_DEFAULT_CELL;
|
|
153
|
+
}
|
|
154
|
+
function jsonParamCell(params) {
|
|
155
|
+
return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
|
|
156
|
+
}
|
|
157
|
+
function isJsonParamCell(value) {
|
|
158
|
+
return typeof value === "object" && value !== null && value.__jsonParam === true;
|
|
159
|
+
}
|
|
160
|
+
function excludedRef(params) {
|
|
161
|
+
return { __excluded: true, column: params.column };
|
|
162
|
+
}
|
|
163
|
+
function isExcludedRef(value) {
|
|
164
|
+
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
165
|
+
}
|
|
166
|
+
function returningColumnName(column) {
|
|
167
|
+
return typeof column === "string" ? column : ("capture" in column) || ("from" in column) ? column.alias : column.name;
|
|
168
|
+
}
|
|
169
|
+
function isOtherRowReturningColumn(column) {
|
|
170
|
+
return typeof column !== "string" && "from" in column;
|
|
171
|
+
}
|
|
172
|
+
function isCaptureReturningColumn(column) {
|
|
173
|
+
return typeof column !== "string" && "capture" in column;
|
|
174
|
+
}
|
|
175
|
+
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
176
|
+
function stepColumnRef(params) {
|
|
177
|
+
return { __stepColumn: true, step: params.step, column: params.column };
|
|
178
|
+
}
|
|
179
|
+
function sourceColumnRef(params) {
|
|
180
|
+
return Object.freeze({ __sourceColumn: true, column: params.column });
|
|
181
|
+
}
|
|
182
|
+
function isSourceColumnRef(value) {
|
|
183
|
+
if (typeof value !== "object" || value === null)
|
|
184
|
+
return false;
|
|
185
|
+
const candidate = value;
|
|
186
|
+
return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
|
|
187
|
+
}
|
|
188
|
+
function isStepColumnRef(value) {
|
|
189
|
+
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
190
|
+
}
|
|
191
|
+
var CHAIN_VETO_PREFIX = "__vibe_veto:";
|
|
192
|
+
var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
193
|
+
function chainVetoToken(params) {
|
|
194
|
+
const { message, nonce } = params;
|
|
195
|
+
if (nonce === "")
|
|
196
|
+
return;
|
|
197
|
+
for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
|
|
198
|
+
const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
|
|
199
|
+
const match = /^([a-z][a-z0-9_]*):/.exec(rest);
|
|
200
|
+
if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
|
|
201
|
+
return match[1];
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// packages/sql/src/json-path.ts
|
|
207
|
+
function jsonPathString(path) {
|
|
208
|
+
let out = "$";
|
|
209
|
+
for (const segment of path) {
|
|
210
|
+
if (typeof segment === "number") {
|
|
211
|
+
out += `[${segment}]`;
|
|
212
|
+
} else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
|
|
213
|
+
out += `.${segment}`;
|
|
214
|
+
} else {
|
|
215
|
+
out += `."${segment.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// packages/sql/src/json-patch.ts
|
|
222
|
+
var JSON_PATCH_VETO_TOKEN = "json_patch";
|
|
223
|
+
function isJsonPatchOperation(value) {
|
|
224
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
225
|
+
return false;
|
|
226
|
+
if (!Object.hasOwn(value, "__jsonPatch"))
|
|
227
|
+
return false;
|
|
228
|
+
const keys = Object.keys(value);
|
|
229
|
+
if (keys.some((key) => key !== "__jsonPatch" && key !== "steps" && key !== "nonce" && key !== "storage"))
|
|
230
|
+
return false;
|
|
231
|
+
const candidate = value;
|
|
232
|
+
return candidate.__jsonPatch === true && Array.isArray(candidate.steps) && candidate.steps.length > 0 && typeof candidate.nonce === "string" && (candidate.storage === undefined || candidate.storage === "json");
|
|
233
|
+
}
|
|
234
|
+
var editNode = () => ({ kind: "edit", create: false, children: new Map });
|
|
235
|
+
function isJsonObject(value) {
|
|
236
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
237
|
+
}
|
|
238
|
+
function foundType(value) {
|
|
239
|
+
if (value === null)
|
|
240
|
+
return "null";
|
|
241
|
+
if (Array.isArray(value))
|
|
242
|
+
return "array";
|
|
243
|
+
switch (typeof value) {
|
|
244
|
+
case "string":
|
|
245
|
+
return "string";
|
|
246
|
+
case "number":
|
|
247
|
+
return "number";
|
|
248
|
+
case "boolean":
|
|
249
|
+
return "boolean";
|
|
250
|
+
}
|
|
251
|
+
throw internalError({ message: "a parsed JSON value has no JSON type" });
|
|
252
|
+
}
|
|
253
|
+
function defineMember(params) {
|
|
254
|
+
Object.defineProperty(params.container, params.key, { value: params.value, enumerable: true, writable: true, configurable: true });
|
|
255
|
+
}
|
|
256
|
+
function applyKnown(params) {
|
|
257
|
+
const { rest, step, index } = params;
|
|
258
|
+
let container = params.value;
|
|
259
|
+
for (let depth = 0;depth < rest.length; depth++) {
|
|
260
|
+
if (!isJsonObject(container))
|
|
261
|
+
return { step: index, path: [...params.at, ...rest.slice(0, depth)], found: foundType(container) };
|
|
262
|
+
const key = rest[depth];
|
|
263
|
+
if (depth === rest.length - 1) {
|
|
264
|
+
if ("set" in step)
|
|
265
|
+
defineMember({ container, key, value: JSON.parse(step.value) });
|
|
266
|
+
else
|
|
267
|
+
delete container[key];
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (!Object.hasOwn(container, key)) {
|
|
271
|
+
if (!("set" in step))
|
|
272
|
+
return;
|
|
273
|
+
defineMember({ container, key, value: {} });
|
|
274
|
+
}
|
|
275
|
+
container = container[key];
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
function fold(steps) {
|
|
280
|
+
const root = editNode();
|
|
281
|
+
const checks = [];
|
|
282
|
+
const checked = new Set;
|
|
283
|
+
for (let index = 0;index < steps.length; index++) {
|
|
284
|
+
const step = steps[index];
|
|
285
|
+
const setting = "set" in step;
|
|
286
|
+
const path = setting ? step.set : step.remove;
|
|
287
|
+
if (path.length === 0)
|
|
288
|
+
throw internalError({ message: "a JSON patch step needs a non-empty path" });
|
|
289
|
+
let node = root;
|
|
290
|
+
for (let depth = 0;depth < path.length; depth++) {
|
|
291
|
+
const signature = JSON.stringify(path.slice(0, depth));
|
|
292
|
+
if (!checked.has(signature)) {
|
|
293
|
+
checked.add(signature);
|
|
294
|
+
checks.push({ path: path.slice(0, depth), step: index });
|
|
295
|
+
}
|
|
296
|
+
if (setting)
|
|
297
|
+
node.create = true;
|
|
298
|
+
const key = path[depth];
|
|
299
|
+
if (depth === path.length - 1) {
|
|
300
|
+
node.children.set(key, setting ? { kind: "replace", text: step.value } : { kind: "delete" });
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
let child = node.children.get(key);
|
|
304
|
+
if (child === undefined) {
|
|
305
|
+
child = editNode();
|
|
306
|
+
node.children.set(key, child);
|
|
307
|
+
}
|
|
308
|
+
if (child.kind === "edit") {
|
|
309
|
+
node = child;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (child.kind === "delete") {
|
|
313
|
+
if (!setting)
|
|
314
|
+
break;
|
|
315
|
+
child = { kind: "replace", text: "{}", parsed: {} };
|
|
316
|
+
node.children.set(key, child);
|
|
317
|
+
}
|
|
318
|
+
child.parsed ??= JSON.parse(child.text);
|
|
319
|
+
const conflict = applyKnown({ value: child.parsed, rest: path.slice(depth + 1), step, at: path.slice(0, depth + 1), index });
|
|
320
|
+
if (conflict !== undefined)
|
|
321
|
+
return { conflict };
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { root, checks };
|
|
326
|
+
}
|
|
327
|
+
function planJsonPatch(params) {
|
|
328
|
+
const folded = fold(params.steps);
|
|
329
|
+
return "conflict" in folded ? { conflict: folded.conflict } : { checks: folded.checks };
|
|
330
|
+
}
|
|
331
|
+
var textOf = (node) => node.parsed === undefined ? node.text : JSON.stringify(node.parsed);
|
|
332
|
+
function writtenMembers(node) {
|
|
333
|
+
const out = [];
|
|
334
|
+
for (const [key, child] of node.children) {
|
|
335
|
+
if (child.kind === "replace" || child.kind === "edit" && child.create)
|
|
336
|
+
out.push([key, child]);
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
function deletedPaths(params) {
|
|
341
|
+
const out = [];
|
|
342
|
+
for (const [key, child] of params.node.children) {
|
|
343
|
+
if (child.kind === "delete")
|
|
344
|
+
out.push([...params.path, key]);
|
|
345
|
+
else if (child.kind === "edit")
|
|
346
|
+
out.push(...deletedPaths({ node: child, path: [...params.path, key] }));
|
|
347
|
+
}
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
var VETO_TEXT = `${CHAIN_VETO_PREFIX}${JSON_PATCH_VETO_TOKEN}:`;
|
|
351
|
+
function renderJsonPatch(params) {
|
|
352
|
+
const folded = fold(params.patch.steps);
|
|
353
|
+
if ("conflict" in folded) {
|
|
354
|
+
throw internalError({ message: "a JSON patch whose steps conflict reached the renderer; the query builder refuses it" });
|
|
355
|
+
}
|
|
356
|
+
switch (params.dialect.jsonPathMode) {
|
|
357
|
+
case "operators":
|
|
358
|
+
return renderPostgresPatch({ ...params, ...folded });
|
|
359
|
+
case "sqliteExtract":
|
|
360
|
+
return renderSqlitePatch({ ...params, ...folded });
|
|
361
|
+
case "mysqlExtract":
|
|
362
|
+
return renderMysqlPatch({ ...params, ...folded });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function renderPostgresPatch(params) {
|
|
366
|
+
const { patch, read, collector } = params;
|
|
367
|
+
const json = patch.storage === "json";
|
|
368
|
+
const typeOf = json ? "json_typeof" : "jsonb_typeof";
|
|
369
|
+
const at = (source, path) => path.length === 0 ? source : `${source} #> ${collector.add([...path])}::text[]`;
|
|
370
|
+
const arms = params.checks.map((check, index) => {
|
|
371
|
+
const condition = `${typeOf}(${at(read, check.path)}) <> 'object'`;
|
|
372
|
+
const marker = collector.add(`${patch.nonce}:${index}`);
|
|
373
|
+
return `WHEN ${condition} THEN to_jsonb(CAST(concat('${VETO_TEXT}', ${marker}::text, ':', ${typeOf}(${at(read, check.path)})) AS integer))`;
|
|
374
|
+
});
|
|
375
|
+
const source = json ? `${read}::jsonb` : read;
|
|
376
|
+
const build = (node, path) => {
|
|
377
|
+
const base = at(source, path);
|
|
378
|
+
const start = node.create ? `COALESCE(${base}, '{}'::jsonb)` : base;
|
|
379
|
+
const members = writtenMembers(node);
|
|
380
|
+
if (members.length === 0)
|
|
381
|
+
return start;
|
|
382
|
+
const pairs = members.map(([key, child]) => {
|
|
383
|
+
const name = `${collector.add(key)}::text`;
|
|
384
|
+
return `${name}, ${child.kind === "replace" ? `${collector.add(textOf(child))}::text::jsonb` : build(child, [...path, key])}`;
|
|
385
|
+
});
|
|
386
|
+
return `(${start} || jsonb_build_object(${pairs.join(", ")}))`;
|
|
387
|
+
};
|
|
388
|
+
let result = build(params.root, []);
|
|
389
|
+
const deletes = deletedPaths({ node: params.root, path: [] });
|
|
390
|
+
if (deletes.length > 0)
|
|
391
|
+
result = `(${result}${deletes.map((path) => ` #- ${collector.add(path)}::text[]`).join("")})`;
|
|
392
|
+
const value = `CASE ${arms.join(" ")} ELSE ${result} END`;
|
|
393
|
+
return json ? `(${value})::json` : value;
|
|
394
|
+
}
|
|
395
|
+
function renderSqlitePatch(params) {
|
|
396
|
+
const { patch, read, collector } = params;
|
|
397
|
+
const typeOf = (path) => path.length === 0 ? `json_type(${read})` : `json_type(${read}, ${collector.add(jsonPathString(path))})`;
|
|
398
|
+
const arms = params.checks.map((check, index) => {
|
|
399
|
+
const condition = `${typeOf(check.path)} <> 'object'`;
|
|
400
|
+
const marker = collector.add(`${patch.nonce}:${index}`);
|
|
401
|
+
return `WHEN ${condition} THEN json_extract('{}', '${VETO_TEXT}' || ${marker} || ':' || ${typeOf(check.path)})`;
|
|
402
|
+
});
|
|
403
|
+
const build = (node, path) => {
|
|
404
|
+
const base = path.length === 0 ? read : `json_extract(${read}, ${collector.add(jsonPathString(path))})`;
|
|
405
|
+
const start = node.create ? `COALESCE(${base}, '{}')` : base;
|
|
406
|
+
const members = writtenMembers(node);
|
|
407
|
+
if (members.length === 0)
|
|
408
|
+
return start;
|
|
409
|
+
const pairs = members.map(([key, child]) => {
|
|
410
|
+
const member = collector.add(jsonPathString([key]));
|
|
411
|
+
return `${member}, json(${child.kind === "replace" ? collector.add(textOf(child)) : build(child, [...path, key])})`;
|
|
412
|
+
});
|
|
413
|
+
return `json_set(${start}, ${pairs.join(", ")})`;
|
|
414
|
+
};
|
|
415
|
+
let result = build(params.root, []);
|
|
416
|
+
const deletes = deletedPaths({ node: params.root, path: [] });
|
|
417
|
+
if (deletes.length > 0)
|
|
418
|
+
result = `json_remove(${result}, ${deletes.map((path) => collector.add(jsonPathString(path))).join(", ")})`;
|
|
419
|
+
return `CASE ${arms.join(" ")} ELSE ${result} END`;
|
|
420
|
+
}
|
|
421
|
+
function renderMysqlPatch(params) {
|
|
422
|
+
const { patch, read, collector } = params;
|
|
423
|
+
const at = (path) => path.length === 0 ? read : `JSON_EXTRACT(${read}, ${collector.add(jsonPathString(path))})`;
|
|
424
|
+
const arms = params.checks.map((check, index) => {
|
|
425
|
+
const condition = `JSON_TYPE(${at(check.path)}) <> 'OBJECT'`;
|
|
426
|
+
const marker = collector.add(`${patch.nonce}:${index}`);
|
|
427
|
+
return `WHEN ${condition} THEN JSON_ARRAY(UUID_TO_BIN(CONCAT('${VETO_TEXT}', ${marker}, ':', JSON_TYPE(${at(check.path)}))))`;
|
|
428
|
+
});
|
|
429
|
+
const build = (node, path) => {
|
|
430
|
+
const base = at(path);
|
|
431
|
+
const start = node.create ? `COALESCE(${base}, JSON_OBJECT())` : base;
|
|
432
|
+
const members = writtenMembers(node);
|
|
433
|
+
if (members.length === 0)
|
|
434
|
+
return start;
|
|
435
|
+
const pairs = members.map(([key, child]) => {
|
|
436
|
+
const member = collector.add(jsonPathString([key]));
|
|
437
|
+
return `${member}, ${child.kind === "replace" ? `CAST(${collector.add(textOf(child))} AS JSON)` : build(child, [...path, key])}`;
|
|
438
|
+
});
|
|
439
|
+
return `JSON_SET(${start}, ${pairs.join(", ")})`;
|
|
440
|
+
};
|
|
441
|
+
let result = build(params.root, []);
|
|
442
|
+
const deletes = deletedPaths({ node: params.root, path: [] });
|
|
443
|
+
if (deletes.length > 0)
|
|
444
|
+
result = `JSON_REMOVE(${result}, ${deletes.map((path) => collector.add(jsonPathString(path))).join(", ")})`;
|
|
445
|
+
return `CASE ${arms.join(" ")} ELSE ${result} END`;
|
|
446
|
+
}
|
|
447
|
+
function jsonPatchVetoOf(params) {
|
|
448
|
+
const start = params.message.indexOf(VETO_TEXT);
|
|
449
|
+
if (start < 0)
|
|
450
|
+
return;
|
|
451
|
+
const match = /^([0-9a-f]+):(\d+):([A-Za-z]+)/.exec(params.message.slice(start + VETO_TEXT.length));
|
|
452
|
+
if (match === null)
|
|
453
|
+
return;
|
|
454
|
+
return { nonce: match[1], check: Number(match[2]), found: match[3] };
|
|
455
|
+
}
|
|
102
456
|
// packages/sql/src/dialects/external-ownership.ts
|
|
103
457
|
import { unsupportedCapability } from "@vibeorm/schema";
|
|
104
458
|
function renderExternalOwnershipCatalog(params) {
|
|
@@ -672,6 +1026,27 @@ function scalar(type, valid) {
|
|
|
672
1026
|
}
|
|
673
1027
|
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
674
1028
|
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
1029
|
+
var bigIntDecoder = scalar("BigInt", (value) => typeof value === "bigint");
|
|
1030
|
+
var SAFE_INTEGER_LIMIT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
1031
|
+
var bigIntNumberDecoder = registerScalar({ identity: JSON.stringify(["scalar", "BigInt", "number"]), contract: { scalar: "BigInt", nullable: false, list: false }, decoder: Object.freeze({
|
|
1032
|
+
decode: (params) => {
|
|
1033
|
+
const exact = bigIntDecoder.decode(params);
|
|
1034
|
+
if (exact > SAFE_INTEGER_LIMIT || exact < -SAFE_INTEGER_LIMIT)
|
|
1035
|
+
refuse(false, `BigInt ${exact.toString()} is beyond Number.MAX_SAFE_INTEGER, so it cannot read as a safe number`);
|
|
1036
|
+
return Number(exact);
|
|
1037
|
+
},
|
|
1038
|
+
encode: (params) => {
|
|
1039
|
+
const { value } = params;
|
|
1040
|
+
const unsafe = `${String(value)} is not a safe integer, so it is refused instead of rounded`;
|
|
1041
|
+
if (typeof value === "number" && !Number.isSafeInteger(value))
|
|
1042
|
+
refuse(true, unsafe);
|
|
1043
|
+
const encoded = bigIntDecoder.encode(params);
|
|
1044
|
+
const exact = BigInt(String(encoded));
|
|
1045
|
+
if (exact > SAFE_INTEGER_LIMIT || exact < -SAFE_INTEGER_LIMIT)
|
|
1046
|
+
refuse(true, unsafe);
|
|
1047
|
+
return encoded;
|
|
1048
|
+
}
|
|
1049
|
+
}) });
|
|
675
1050
|
var extendedDecoder = registerScalar({ identity: "extended-date-time", contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
|
|
676
1051
|
decode: (params) => {
|
|
677
1052
|
if (params.value === "infinity" || params.value === Infinity)
|
|
@@ -752,7 +1127,8 @@ var decode = Object.freeze({
|
|
|
752
1127
|
boolean: scalar("Boolean", (value) => typeof value === "boolean"),
|
|
753
1128
|
int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
|
|
754
1129
|
float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
|
|
755
|
-
bigInt:
|
|
1130
|
+
bigInt: bigIntDecoder,
|
|
1131
|
+
bigIntNumber: bigIntNumberDecoder,
|
|
756
1132
|
decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
|
|
757
1133
|
dateTime: dateDecoder,
|
|
758
1134
|
extendedDateTime: extendedDecoder,
|
|
@@ -1171,7 +1547,7 @@ function renderPostgresPhysicalCatalog(params) {
|
|
|
1171
1547
|
let text;
|
|
1172
1548
|
switch (params.query) {
|
|
1173
1549
|
case "tables":
|
|
1174
|
-
text = `SELECT table_name FROM information_schema.tables WHERE table_schema = ${namespace} AND table_type = 'BASE TABLE' AND table_name <> '_vibe_migrations' AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
1550
|
+
text = `SELECT table_name, pg_catalog.array_to_json(storage_relation.reloptions)::text AS storage_options, pg_catalog.array_to_json(storage_toast.reloptions)::text AS toast_storage_options, storage_relation.reltoastrelid <> 0 AS has_toast_table FROM information_schema.tables JOIN pg_catalog.pg_namespace storage_namespace ON storage_namespace.nspname = table_schema JOIN pg_catalog.pg_class storage_relation ON storage_relation.relnamespace = storage_namespace.oid AND storage_relation.relname = table_name LEFT JOIN pg_catalog.pg_class storage_toast ON storage_toast.oid = storage_relation.reltoastrelid WHERE table_schema = ${namespace} AND table_type = 'BASE TABLE' AND table_name <> '_vibe_migrations' AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
1175
1551
|
break;
|
|
1176
1552
|
case "views":
|
|
1177
1553
|
text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
@@ -1192,7 +1568,7 @@ function renderPostgresPhysicalCatalog(params) {
|
|
|
1192
1568
|
text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_type'::regclass AND d.objid = t.oid AND d.deptype = 'e') ORDER BY t.typname, e.enumsortorder`;
|
|
1193
1569
|
break;
|
|
1194
1570
|
case "indexes":
|
|
1195
|
-
text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
|
|
1571
|
+
text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count, pg_catalog.array_to_json(ARRAY(SELECT included.attname FROM pg_catalog.generate_series(health.indnkeyatts + 1, health.indnatts) AS included_key(ordinal) JOIN pg_catalog.pg_attribute included ON included.attrelid = health.indrelid AND included.attnum = health.indkey[included_key.ordinal - 1] ORDER BY included_key.ordinal))::text AS included_columns FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
|
|
1196
1572
|
break;
|
|
1197
1573
|
}
|
|
1198
1574
|
return { text, values: collector.values, returning: "none" };
|
|
@@ -1788,7 +2164,7 @@ function extractIndexKeyList({ sql, open, close, tokens, wrapped = false, remove
|
|
|
1788
2164
|
}
|
|
1789
2165
|
return keys.length === 0 ? undefined : keys;
|
|
1790
2166
|
}
|
|
1791
|
-
function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
|
|
2167
|
+
function extractStandaloneIndexKeys({ definition, index, postgres = false, included }) {
|
|
1792
2168
|
const tokens = tableDefinitionTokens({ sql: definition });
|
|
1793
2169
|
const top = tokens.filter((token) => token.depth === 0);
|
|
1794
2170
|
let position = 0;
|
|
@@ -1829,6 +2205,16 @@ function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
|
|
|
1829
2205
|
const close = top[position++];
|
|
1830
2206
|
if (open?.kind !== "symbol" || open.value !== "(" || close?.kind !== "symbol" || close.value !== ")")
|
|
1831
2207
|
return;
|
|
2208
|
+
let includedCount = 0;
|
|
2209
|
+
if (postgres && word({ value: "INCLUDE" })) {
|
|
2210
|
+
const includeOpen = top[position++];
|
|
2211
|
+
const includeClose = top[position++];
|
|
2212
|
+
if (includeOpen?.kind !== "symbol" || includeOpen.value !== "(" || includeClose?.kind !== "symbol" || includeClose.value !== ")")
|
|
2213
|
+
return;
|
|
2214
|
+
includedCount = 1 + tokens.filter((token) => token.start > includeOpen.start && token.start < includeClose.start && token.depth === 1 && token.value === ",").length;
|
|
2215
|
+
}
|
|
2216
|
+
if (includedCount !== (included ?? 0))
|
|
2217
|
+
return;
|
|
1832
2218
|
if (postgres && word({ value: "NULLS" }) && (!word({ value: "NOT" }) || !word({ value: "DISTINCT" })))
|
|
1833
2219
|
return;
|
|
1834
2220
|
const tail = top[position];
|
|
@@ -1855,8 +2241,8 @@ function extractSqliteIndexKeys({ definition, index }) {
|
|
|
1855
2241
|
}
|
|
1856
2242
|
|
|
1857
2243
|
// packages/sql/src/dialects/postgres-index-expressions.ts
|
|
1858
|
-
function extractPostgresIndexKeys({ definition, index }) {
|
|
1859
|
-
return extractStandaloneIndexKeys({ definition, index, postgres: true });
|
|
2244
|
+
function extractPostgresIndexKeys({ definition, index, included }) {
|
|
2245
|
+
return extractStandaloneIndexKeys({ definition, index, postgres: true, ...included === undefined ? {} : { included } });
|
|
1860
2246
|
}
|
|
1861
2247
|
function renderPostgresIndexExpressionProbe({ ...params }) {
|
|
1862
2248
|
const { dialect, operation, probe } = params;
|
|
@@ -2125,10 +2511,10 @@ function renderIndexKeys({ dialect, keys, unique }) {
|
|
|
2125
2511
|
return `${expression}${key.desc ? " DESC" : ""}`;
|
|
2126
2512
|
});
|
|
2127
2513
|
}
|
|
2128
|
-
function extractIndexKeys({ dialect, definition, index, backslashEscapes = true }) {
|
|
2514
|
+
function extractIndexKeys({ dialect, definition, index, backslashEscapes = true, included }) {
|
|
2129
2515
|
switch (dialect) {
|
|
2130
2516
|
case "postgres":
|
|
2131
|
-
return extractPostgresIndexKeys({ definition, index });
|
|
2517
|
+
return extractPostgresIndexKeys({ definition, index, ...included === undefined ? {} : { included } });
|
|
2132
2518
|
case "sqlite":
|
|
2133
2519
|
return extractSqliteIndexKeys({ definition, index });
|
|
2134
2520
|
case "mysql":
|
|
@@ -2296,6 +2682,57 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
|
|
|
2296
2682
|
}
|
|
2297
2683
|
}
|
|
2298
2684
|
}
|
|
2685
|
+
// packages/sql/src/dialects/migration-shadow-marker.ts
|
|
2686
|
+
import { VibeError as VibeError12, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
|
|
2687
|
+
var SHADOW_MARKER_CAPABILITIES = Object.freeze({
|
|
2688
|
+
postgres: Object.freeze({ databaseMarker: true }),
|
|
2689
|
+
mysql: Object.freeze({ databaseMarker: false }),
|
|
2690
|
+
sqlite: Object.freeze({ databaseMarker: false })
|
|
2691
|
+
});
|
|
2692
|
+
var AUTOMATIC_SHADOW_NAME_PATTERN = "^(vibeorm_shadow|vibeorm_test_shadow)_[0-9a-f]{32}$";
|
|
2693
|
+
var NAME = new RegExp(AUTOMATIC_SHADOW_NAME_PATTERN);
|
|
2694
|
+
var MICROS = /^\d{1,20}$/;
|
|
2695
|
+
var HOST = /^[A-Za-z0-9._-]{1,253}$/;
|
|
2696
|
+
var MARKER = /^\{"vibeorm":"automatic-shadow","version":1,"database":"([a-z0-9_]+)","adminPid":(\d{1,10}),"adminStarted":(?:"(\d{1,20})"|null),"created":"(\d{1,20})","host":"([A-Za-z0-9._-]{1,253})","pid":(\d{1,10})\}$/;
|
|
2697
|
+
function micros(expression) {
|
|
2698
|
+
return `(extract(epoch FROM ${expression}) * 1000000)::bigint::text`;
|
|
2699
|
+
}
|
|
2700
|
+
function positiveInteger(value) {
|
|
2701
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
2702
|
+
}
|
|
2703
|
+
function automaticShadowMarkerText(params) {
|
|
2704
|
+
const { marker } = params;
|
|
2705
|
+
if (!NAME.test(marker.database) || !positiveInteger(marker.adminPid) || !positiveInteger(marker.pid) || marker.adminStarted !== null && !MICROS.test(marker.adminStarted) || !MICROS.test(marker.created) || !HOST.test(marker.host)) {
|
|
2706
|
+
throw new VibeError12({ code: "VIBE_INTERNAL", message: "Automatic shadow marker fields are outside their closed format", meta: { reason: "shadow-marker-invalid", database: marker.database } });
|
|
2707
|
+
}
|
|
2708
|
+
return `{"vibeorm":"automatic-shadow","version":1,"database":"${marker.database}","adminPid":${marker.adminPid},"adminStarted":${marker.adminStarted === null ? "null" : `"${marker.adminStarted}"`},"created":"${marker.created}","host":"${marker.host}","pid":${marker.pid}}`;
|
|
2709
|
+
}
|
|
2710
|
+
function parseAutomaticShadowMarker(params) {
|
|
2711
|
+
if (typeof params.text !== "string")
|
|
2712
|
+
return;
|
|
2713
|
+
const match = MARKER.exec(params.text);
|
|
2714
|
+
if (match === null)
|
|
2715
|
+
return;
|
|
2716
|
+
const marker = { database: match[1], adminPid: Number(match[2]), adminStarted: match[3] ?? null, created: match[4], host: match[5], pid: Number(match[6]) };
|
|
2717
|
+
return NAME.test(marker.database) && positiveInteger(marker.adminPid) && positiveInteger(marker.pid) ? marker : undefined;
|
|
2718
|
+
}
|
|
2719
|
+
function renderAutomaticShadowOperation(params) {
|
|
2720
|
+
const { dialect, operation } = params;
|
|
2721
|
+
if (!SHADOW_MARKER_CAPABILITIES[dialect.name].databaseMarker)
|
|
2722
|
+
throw unsupportedCapability14({ dialect: dialect.name, feature: "automaticShadowMarker", hint: "only PostgreSQL labels an automatic shadow database with its creator" });
|
|
2723
|
+
const collector = new ParamCollector(dialect);
|
|
2724
|
+
const result = (text) => ({ text, values: collector.values, returning: "none" });
|
|
2725
|
+
switch (operation.kind) {
|
|
2726
|
+
case "session":
|
|
2727
|
+
return result(`SELECT pg_catalog.pg_backend_pid() AS pid, (SELECT ${micros("a.backend_start")} FROM pg_catalog.pg_stat_activity a WHERE a.pid = pg_catalog.pg_backend_pid()) AS started, ${micros("pg_catalog.statement_timestamp()")} AS now`);
|
|
2728
|
+
case "mark":
|
|
2729
|
+
return result(`COMMENT ON DATABASE ${dialect.quoteIdent({ name: operation.marker.database })} IS '${automaticShadowMarkerText({ marker: operation.marker })}'`);
|
|
2730
|
+
case "list":
|
|
2731
|
+
return result(`SELECT d.datname::text AS name, pg_catalog.shobj_description(d.oid, 'pg_database') AS marker, (SELECT count(*)::int FROM pg_catalog.pg_stat_activity a WHERE a.datid = d.oid) AS sessions, ${micros("pg_catalog.statement_timestamp()")} AS now FROM pg_catalog.pg_database d WHERE d.datname ~ ${collector.add(AUTOMATIC_SHADOW_NAME_PATTERN)} ORDER BY d.datname`);
|
|
2732
|
+
case "backends":
|
|
2733
|
+
return result(`SELECT a.pid, ${micros("a.backend_start")} AS started FROM pg_catalog.pg_stat_activity a`);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2299
2736
|
// packages/sql/src/module-journal.ts
|
|
2300
2737
|
var MODULE_JOURNAL_COLUMN = "module_origin";
|
|
2301
2738
|
function renderModuleJournal(params) {
|
|
@@ -2333,10 +2770,10 @@ import {
|
|
|
2333
2770
|
} from "@vibeorm/schema";
|
|
2334
2771
|
|
|
2335
2772
|
// packages/sql/src/identifiers.ts
|
|
2336
|
-
import { VibeError as
|
|
2773
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
2337
2774
|
function assertNoNul(params) {
|
|
2338
2775
|
if (params.name.includes("\x00")) {
|
|
2339
|
-
throw new
|
|
2776
|
+
throw new VibeError13({
|
|
2340
2777
|
code: "VIBE_VALIDATION",
|
|
2341
2778
|
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
2342
2779
|
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
@@ -2378,6 +2815,7 @@ var sqliteDialect = {
|
|
|
2378
2815
|
jsonPathMode: "sqliteExtract",
|
|
2379
2816
|
supportedJsonOps: [
|
|
2380
2817
|
"equals",
|
|
2818
|
+
"exists",
|
|
2381
2819
|
"string_contains",
|
|
2382
2820
|
"string_starts_with",
|
|
2383
2821
|
"string_ends_with",
|
|
@@ -2482,11 +2920,11 @@ var SQLITE_SESSION_SQL = Object.freeze({
|
|
|
2482
2920
|
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
2483
2921
|
});
|
|
2484
2922
|
// packages/sql/src/composable.ts
|
|
2485
|
-
import { VibeError as
|
|
2923
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
2486
2924
|
var fragments = new WeakMap;
|
|
2487
2925
|
registerSqlHandleCheck({ check: (value) => fragments.has(value) });
|
|
2488
2926
|
function invalid4(message) {
|
|
2489
|
-
throw new
|
|
2927
|
+
throw new VibeError14({ code: "VIBE_VALIDATION", message });
|
|
2490
2928
|
}
|
|
2491
2929
|
function construct(parts) {
|
|
2492
2930
|
const snapshot = Object.freeze([...parts]);
|
|
@@ -2584,12 +3022,12 @@ function renderSqlFragment(params) {
|
|
|
2584
3022
|
const only = parts.length === 1 ? parts[0] : undefined;
|
|
2585
3023
|
if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
|
|
2586
3024
|
if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
|
|
2587
|
-
throw new
|
|
3025
|
+
throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
|
|
2588
3026
|
if (only.value === null) {
|
|
2589
3027
|
if (only.decoder !== undefined)
|
|
2590
3028
|
recordSqlBindingCheck({ collector, check: () => {
|
|
2591
3029
|
if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
|
|
2592
|
-
throw new
|
|
3030
|
+
throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
|
|
2593
3031
|
} });
|
|
2594
3032
|
return "NULL";
|
|
2595
3033
|
}
|
|
@@ -2653,14 +3091,14 @@ function sqlUsesIncoming(params) {
|
|
|
2653
3091
|
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
2654
3092
|
}
|
|
2655
3093
|
// packages/sql/src/query-plan.ts
|
|
2656
|
-
import { VibeError as
|
|
3094
|
+
import { VibeError as VibeError15 } from "@vibeorm/schema";
|
|
2657
3095
|
var queries = new WeakMap;
|
|
2658
3096
|
var commands = new WeakMap;
|
|
2659
3097
|
var reusableQueries = new WeakMap;
|
|
2660
3098
|
var reusableCommands = new WeakMap;
|
|
2661
3099
|
registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
|
|
2662
3100
|
function invalid5(message) {
|
|
2663
|
-
throw new
|
|
3101
|
+
throw new VibeError15({ code: "VIBE_VALIDATION", message });
|
|
2664
3102
|
}
|
|
2665
3103
|
function createSqlQuery(params) {
|
|
2666
3104
|
if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
|
|
@@ -2751,14 +3189,14 @@ function compileSqlCommand(params) {
|
|
|
2751
3189
|
return { text, values: collector.values };
|
|
2752
3190
|
}
|
|
2753
3191
|
// packages/sql/src/exclusion.ts
|
|
2754
|
-
import { VibeError as
|
|
3192
|
+
import { VibeError as VibeError16, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
2755
3193
|
function invalid6(rule) {
|
|
2756
|
-
throw new
|
|
3194
|
+
throw new VibeError16({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
2757
3195
|
}
|
|
2758
3196
|
function compileExclusion(params) {
|
|
2759
3197
|
const { exclusion: spec, model, dialect } = params;
|
|
2760
3198
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2761
|
-
throw
|
|
3199
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2762
3200
|
if (!spec.name || spec.name.includes("\x00"))
|
|
2763
3201
|
invalid6("name");
|
|
2764
3202
|
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
@@ -2811,13 +3249,13 @@ function compileExclusion(params) {
|
|
|
2811
3249
|
}
|
|
2812
3250
|
function renderExclusionSetup(params) {
|
|
2813
3251
|
if (!params.dialect.capabilities.exclusionConstraints)
|
|
2814
|
-
throw
|
|
3252
|
+
throw unsupportedCapability15({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
2815
3253
|
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
2816
3254
|
}
|
|
2817
3255
|
function renderExclusionDdl(params) {
|
|
2818
3256
|
const { dialect, table, exclusion, operation } = params;
|
|
2819
3257
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2820
|
-
throw
|
|
3258
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2821
3259
|
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
2822
3260
|
if (operation === "drop")
|
|
2823
3261
|
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
@@ -2826,111 +3264,18 @@ function renderExclusionDdl(params) {
|
|
|
2826
3264
|
function renderExclusionCatalog(params) {
|
|
2827
3265
|
const { dialect, probe } = params;
|
|
2828
3266
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2829
|
-
throw
|
|
3267
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2830
3268
|
const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
2831
3269
|
return `SELECT t.relname AS table_name, c.conname AS constraint_name, pg_catalog.pg_get_constraintdef(c.oid, true) AS definition, c.convalidated AS validated, i.indisvalid AS valid, i.indisready AS ready FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class t ON t.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace JOIN pg_catalog.pg_index i ON i.indexrelid = c.conindid WHERE c.contype = 'x' AND ${scope} ORDER BY t.relname, c.conname`;
|
|
2832
3270
|
}
|
|
2833
3271
|
function renderExclusionProbe(params) {
|
|
2834
3272
|
const { dialect, table, probe, definition } = params;
|
|
2835
3273
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2836
|
-
throw
|
|
3274
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2837
3275
|
return [`CREATE TEMP TABLE ${dialect.quoteIdent({ name: probe })} (LIKE ${dialect.quoteIdent({ name: params.namespace ?? "public" })}.${dialect.quoteIdent({ name: table })})`, `ALTER TABLE ${dialect.quoteIdent({ name: probe })} ADD CONSTRAINT ${dialect.quoteIdent({ name: `${probe}_x` })} ${definition}`];
|
|
2838
3276
|
}
|
|
2839
|
-
// packages/sql/src/ast.ts
|
|
2840
|
-
var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
|
|
2841
|
-
function isSetOperation(value) {
|
|
2842
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2843
|
-
return false;
|
|
2844
|
-
const keys = Object.keys(value);
|
|
2845
|
-
if (!keys.includes("__op") || !keys.includes("value"))
|
|
2846
|
-
return false;
|
|
2847
|
-
const candidate = value;
|
|
2848
|
-
if (keys.length !== 2 && !(keys.length === 3 && keys.includes("numericType") && candidate.numericType === "Decimal"))
|
|
2849
|
-
return false;
|
|
2850
|
-
return typeof candidate.__op === "string" && SET_OPERATORS.includes(candidate.__op) && (typeof candidate.value === "number" && Number.isFinite(candidate.value) || typeof candidate.value === "string" || typeof candidate.value === "bigint");
|
|
2851
|
-
}
|
|
2852
|
-
var LIST_SET_OPERATORS = ["append", "concat"];
|
|
2853
|
-
function isListSetOperation(value) {
|
|
2854
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2855
|
-
return false;
|
|
2856
|
-
const keys = Object.keys(value);
|
|
2857
|
-
if (keys.length !== 2 || !keys.includes("__listOp") || !keys.includes("value"))
|
|
2858
|
-
return false;
|
|
2859
|
-
const candidate = value;
|
|
2860
|
-
return typeof candidate.__listOp === "string" && LIST_SET_OPERATORS.includes(candidate.__listOp);
|
|
2861
|
-
}
|
|
2862
|
-
function dbNowMarker(params) {
|
|
2863
|
-
return { __dbNow: true, offsetSeconds: params.offsetSeconds };
|
|
2864
|
-
}
|
|
2865
|
-
function isDbNowMarker(value) {
|
|
2866
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2867
|
-
return false;
|
|
2868
|
-
if (!Object.hasOwn(value, "__dbNow"))
|
|
2869
|
-
return false;
|
|
2870
|
-
const keys = Object.keys(value);
|
|
2871
|
-
if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
|
|
2872
|
-
return false;
|
|
2873
|
-
const candidate = value;
|
|
2874
|
-
return candidate.__dbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
|
|
2875
|
-
}
|
|
2876
|
-
var SQL_DEFAULT_CELL = Object.freeze({ __sqlDefault: true });
|
|
2877
|
-
var SQL_KEEP_CELL = Object.freeze({ __sqlKeep: true });
|
|
2878
|
-
function isSqlKeepCell(value) {
|
|
2879
|
-
return value === SQL_KEEP_CELL;
|
|
2880
|
-
}
|
|
2881
|
-
function isSqlDefaultCell(value) {
|
|
2882
|
-
return value === SQL_DEFAULT_CELL;
|
|
2883
|
-
}
|
|
2884
|
-
function jsonParamCell(params) {
|
|
2885
|
-
return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
|
|
2886
|
-
}
|
|
2887
|
-
function isJsonParamCell(value) {
|
|
2888
|
-
return typeof value === "object" && value !== null && value.__jsonParam === true;
|
|
2889
|
-
}
|
|
2890
|
-
function excludedRef(params) {
|
|
2891
|
-
return { __excluded: true, column: params.column };
|
|
2892
|
-
}
|
|
2893
|
-
function isExcludedRef(value) {
|
|
2894
|
-
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
2895
|
-
}
|
|
2896
|
-
function returningColumnName(column) {
|
|
2897
|
-
return typeof column === "string" ? column : ("capture" in column) ? column.alias : column.name;
|
|
2898
|
-
}
|
|
2899
|
-
function isCaptureReturningColumn(column) {
|
|
2900
|
-
return typeof column !== "string" && "capture" in column;
|
|
2901
|
-
}
|
|
2902
|
-
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
2903
|
-
function stepColumnRef(params) {
|
|
2904
|
-
return { __stepColumn: true, step: params.step, column: params.column };
|
|
2905
|
-
}
|
|
2906
|
-
function sourceColumnRef(params) {
|
|
2907
|
-
return Object.freeze({ __sourceColumn: true, column: params.column });
|
|
2908
|
-
}
|
|
2909
|
-
function isSourceColumnRef(value) {
|
|
2910
|
-
if (typeof value !== "object" || value === null)
|
|
2911
|
-
return false;
|
|
2912
|
-
const candidate = value;
|
|
2913
|
-
return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
|
|
2914
|
-
}
|
|
2915
|
-
function isStepColumnRef(value) {
|
|
2916
|
-
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
2917
|
-
}
|
|
2918
|
-
var CHAIN_VETO_PREFIX = "__vibe_veto:";
|
|
2919
|
-
var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
2920
|
-
function chainVetoToken(params) {
|
|
2921
|
-
const { message, nonce } = params;
|
|
2922
|
-
if (nonce === "")
|
|
2923
|
-
return;
|
|
2924
|
-
for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
|
|
2925
|
-
const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
|
|
2926
|
-
const match = /^([a-z][a-z0-9_]*):/.exec(rest);
|
|
2927
|
-
if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
|
|
2928
|
-
return match[1];
|
|
2929
|
-
}
|
|
2930
|
-
return;
|
|
2931
|
-
}
|
|
2932
3277
|
// packages/sql/src/locks.ts
|
|
2933
|
-
import { internalError, unsupportedCapability as
|
|
3278
|
+
import { internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2934
3279
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
2935
3280
|
strengths: Object.freeze({
|
|
2936
3281
|
update: "FOR UPDATE",
|
|
@@ -2950,7 +3295,7 @@ function renderRowLockClause(params) {
|
|
|
2950
3295
|
if (syntax === null) {
|
|
2951
3296
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
2952
3297
|
return null;
|
|
2953
|
-
throw
|
|
3298
|
+
throw unsupportedCapability16({
|
|
2954
3299
|
dialect: dialect.name,
|
|
2955
3300
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
2956
3301
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -2958,7 +3303,7 @@ function renderRowLockClause(params) {
|
|
|
2958
3303
|
}
|
|
2959
3304
|
const strengthText = syntax.strengths[lock.strength];
|
|
2960
3305
|
if (strengthText === undefined) {
|
|
2961
|
-
throw
|
|
3306
|
+
throw unsupportedCapability16({
|
|
2962
3307
|
dialect: dialect.name,
|
|
2963
3308
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
2964
3309
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -2966,7 +3311,7 @@ function renderRowLockClause(params) {
|
|
|
2966
3311
|
}
|
|
2967
3312
|
const waitText = syntax.wait[lock.wait];
|
|
2968
3313
|
if (waitText === undefined) {
|
|
2969
|
-
throw
|
|
3314
|
+
throw unsupportedCapability16({
|
|
2970
3315
|
dialect: dialect.name,
|
|
2971
3316
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
2972
3317
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -2974,7 +3319,7 @@ function renderRowLockClause(params) {
|
|
|
2974
3319
|
}
|
|
2975
3320
|
if (lock.of !== undefined) {
|
|
2976
3321
|
if (lock.of.length === 0)
|
|
2977
|
-
throw
|
|
3322
|
+
throw internalError2({ message: "a row lock's `of` list cannot be empty" });
|
|
2978
3323
|
const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
2979
3324
|
return `${strengthText} OF ${tables}${waitText}`;
|
|
2980
3325
|
}
|
|
@@ -2991,7 +3336,7 @@ function renderAdvisoryLock(params) {
|
|
|
2991
3336
|
const { dialect, statement, collector } = params;
|
|
2992
3337
|
const functions = dialect.advisoryLockFunctions;
|
|
2993
3338
|
if (functions === null) {
|
|
2994
|
-
throw
|
|
3339
|
+
throw unsupportedCapability16({
|
|
2995
3340
|
dialect: dialect.name,
|
|
2996
3341
|
feature: "advisory locks",
|
|
2997
3342
|
hint: dialect.name === "mysql" ? "mysql's GET_LOCK is SESSION-scoped \u2014 it survives a rollback, so it cannot be released with the transaction" : `${dialect.name} has no advisory-lock functions`
|
|
@@ -3012,11 +3357,11 @@ function renderAdvisoryLocks(params) {
|
|
|
3012
3357
|
const { dialect, keys, mode } = params;
|
|
3013
3358
|
const functions = dialect.advisoryLockFunctions;
|
|
3014
3359
|
if (functions === null)
|
|
3015
|
-
throw
|
|
3360
|
+
throw unsupportedCapability16({ dialect: dialect.name, feature: "advisory locks" });
|
|
3016
3361
|
if (mode !== "exclusive" && mode !== "shared")
|
|
3017
|
-
throw
|
|
3362
|
+
throw internalError2({ message: "advisory lock mode must be exclusive or shared" });
|
|
3018
3363
|
if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
|
|
3019
|
-
throw
|
|
3364
|
+
throw internalError2({ message: "advisory lock keys must be signed 64-bit integers" });
|
|
3020
3365
|
}
|
|
3021
3366
|
const collector = new ParamCollector(dialect);
|
|
3022
3367
|
const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
|
|
@@ -3033,7 +3378,7 @@ function renderAdvisoryLocks(params) {
|
|
|
3033
3378
|
function assertInt32(params) {
|
|
3034
3379
|
const { value, part } = params;
|
|
3035
3380
|
if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
|
|
3036
|
-
throw
|
|
3381
|
+
throw internalError2({
|
|
3037
3382
|
message: `advisory lock ${part} must be a 32-bit integer, got ${String(value)}`,
|
|
3038
3383
|
meta: { part, value }
|
|
3039
3384
|
});
|
|
@@ -3079,6 +3424,7 @@ var postgresDialect = {
|
|
|
3079
3424
|
jsonPathMode: "operators",
|
|
3080
3425
|
supportedJsonOps: [
|
|
3081
3426
|
"equals",
|
|
3427
|
+
"exists",
|
|
3082
3428
|
"string_contains",
|
|
3083
3429
|
"string_starts_with",
|
|
3084
3430
|
"string_ends_with",
|
|
@@ -3173,6 +3519,7 @@ var mysqlDialect = {
|
|
|
3173
3519
|
jsonPathMode: "mysqlExtract",
|
|
3174
3520
|
supportedJsonOps: [
|
|
3175
3521
|
"equals",
|
|
3522
|
+
"exists",
|
|
3176
3523
|
"string_contains",
|
|
3177
3524
|
"string_starts_with",
|
|
3178
3525
|
"string_ends_with",
|
|
@@ -3203,11 +3550,12 @@ var mysqlDialect = {
|
|
|
3203
3550
|
renderEnumOrdinal({ column }) {
|
|
3204
3551
|
return `${column} + 0`;
|
|
3205
3552
|
},
|
|
3206
|
-
renderEnumMinMax({ fn, column, result }) {
|
|
3553
|
+
renderEnumMinMax({ fn, column, result, when }) {
|
|
3207
3554
|
const aggregate = fn === "min" ? "MIN" : "MAX";
|
|
3555
|
+
const only = (value) => when === undefined ? value : `CASE WHEN ${when} THEN ${value} END`;
|
|
3208
3556
|
if (result === "ordinal")
|
|
3209
|
-
return `${aggregate}(${column} + 0)`;
|
|
3210
|
-
return `SUBSTRING(${aggregate}(CONCAT(LPAD(${column} + 0, 5, '0'), ${column})), 6)`;
|
|
3557
|
+
return `${aggregate}(${only(`${column} + 0`)})`;
|
|
3558
|
+
return `SUBSTRING(${aggregate}(${only(`CONCAT(LPAD(${column} + 0, 5, '0'), ${column})`)}), 6)`;
|
|
3211
3559
|
},
|
|
3212
3560
|
placeholder() {
|
|
3213
3561
|
return "?";
|
|
@@ -3231,7 +3579,7 @@ function getSqlDialect(params) {
|
|
|
3231
3579
|
return SQL_DIALECTS[params.dialect];
|
|
3232
3580
|
}
|
|
3233
3581
|
// packages/sql/src/key-list.ts
|
|
3234
|
-
import { unsupportedCapability as
|
|
3582
|
+
import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
3235
3583
|
var SCALAR_ELEMENT_TYPES = Object.freeze({
|
|
3236
3584
|
String: "text",
|
|
3237
3585
|
Boolean: "bool",
|
|
@@ -3302,7 +3650,7 @@ function postgresKeyListElementType(params) {
|
|
|
3302
3650
|
const { dialect, member } = params;
|
|
3303
3651
|
const type = postgresKeyListElementTypeOf({ dialect, member });
|
|
3304
3652
|
if (type === undefined) {
|
|
3305
|
-
throw
|
|
3653
|
+
throw unsupportedCapability17({
|
|
3306
3654
|
dialect: dialect.name,
|
|
3307
3655
|
feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
|
|
3308
3656
|
hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
|
|
@@ -3323,7 +3671,7 @@ function postgresBulkInsertArrayCast(params) {
|
|
|
3323
3671
|
const { dialect, member } = params;
|
|
3324
3672
|
const cast = postgresBulkInsertArrayCastOf({ dialect, member });
|
|
3325
3673
|
if (cast === undefined) {
|
|
3326
|
-
throw
|
|
3674
|
+
throw unsupportedCapability17({
|
|
3327
3675
|
dialect: dialect.name,
|
|
3328
3676
|
feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
|
|
3329
3677
|
hint: "the query builder keeps VALUES for this column type"
|
|
@@ -3343,7 +3691,7 @@ function renderLocalDateCheck({ dialect, table, column }) {
|
|
|
3343
3691
|
};
|
|
3344
3692
|
}
|
|
3345
3693
|
// packages/sql/src/date-truncation.ts
|
|
3346
|
-
import { DATE_TRUNCATION_UNITS, VibeError as
|
|
3694
|
+
import { DATE_TRUNCATION_UNITS, VibeError as VibeError17, internalError as internalError3, unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
3347
3695
|
var TIME_ZONE_NAME = /^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/;
|
|
3348
3696
|
function isTimeZoneName(params) {
|
|
3349
3697
|
return typeof params.value === "string" && TIME_ZONE_NAME.test(params.value);
|
|
@@ -3351,17 +3699,17 @@ function isTimeZoneName(params) {
|
|
|
3351
3699
|
function renderDateTruncation(params) {
|
|
3352
3700
|
const { dialect, reference, truncation } = params;
|
|
3353
3701
|
if (!dialect.capabilities.dateTruncation) {
|
|
3354
|
-
throw
|
|
3702
|
+
throw unsupportedCapability18({
|
|
3355
3703
|
dialect: dialect.name,
|
|
3356
3704
|
feature: "a truncated groupBy key ({ field, truncate, timeZone })",
|
|
3357
3705
|
hint: "only PostgreSQL and PGlite truncate a timestamp in a named time zone \u2014 group by the plain field and bucket the rows in code instead"
|
|
3358
3706
|
});
|
|
3359
3707
|
}
|
|
3360
3708
|
if (!DATE_TRUNCATION_UNITS.includes(truncation.unit)) {
|
|
3361
|
-
throw
|
|
3709
|
+
throw internalError3({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
|
|
3362
3710
|
}
|
|
3363
3711
|
if (!isTimeZoneName({ value: truncation.timeZone })) {
|
|
3364
|
-
throw new
|
|
3712
|
+
throw new VibeError17({
|
|
3365
3713
|
code: "VIBE_VALIDATION",
|
|
3366
3714
|
message: `time zone ${JSON.stringify(truncation.timeZone)} is not a time-zone name \u2014 use an IANA name such as "Europe/Oslo" or "UTC"`,
|
|
3367
3715
|
meta: { timeZone: truncation.timeZone }
|
|
@@ -3375,20 +3723,129 @@ function renderDateTruncation(params) {
|
|
|
3375
3723
|
case "timestamp":
|
|
3376
3724
|
return `(date_trunc(${unit}, ${reference} AT TIME ZONE 'UTC', ${zone}) AT TIME ZONE 'UTC')`;
|
|
3377
3725
|
default:
|
|
3378
|
-
throw
|
|
3726
|
+
throw internalError3({ message: `unknown date truncation storage ${JSON.stringify(truncation.storage)}` });
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
// packages/sql/src/native-types.ts
|
|
3730
|
+
var POSTGRES_SCALAR_COLUMN_TYPES = Object.freeze({
|
|
3731
|
+
String: "text",
|
|
3732
|
+
Boolean: "boolean",
|
|
3733
|
+
Int: "integer",
|
|
3734
|
+
BigInt: "bigint",
|
|
3735
|
+
Float: "double precision",
|
|
3736
|
+
Decimal: "numeric",
|
|
3737
|
+
DateTime: "timestamptz",
|
|
3738
|
+
Json: "jsonb",
|
|
3739
|
+
Bytes: "bytea"
|
|
3740
|
+
});
|
|
3741
|
+
var MYSQL_SCALAR_COLUMN_TYPES = Object.freeze({
|
|
3742
|
+
String: `varchar(${mysqlDialect.conflictKeyEquality.defaultStringMaxLength})`,
|
|
3743
|
+
Boolean: "tinyint(1)",
|
|
3744
|
+
Int: "int",
|
|
3745
|
+
BigInt: "bigint",
|
|
3746
|
+
Float: "double",
|
|
3747
|
+
Decimal: "decimal(65,30)",
|
|
3748
|
+
DateTime: "datetime(3)",
|
|
3749
|
+
Json: "json",
|
|
3750
|
+
Bytes: "longblob"
|
|
3751
|
+
});
|
|
3752
|
+
var SQLITE_SCALAR_STORAGE_CLASSES = Object.freeze({
|
|
3753
|
+
String: "TEXT",
|
|
3754
|
+
Boolean: "INTEGER",
|
|
3755
|
+
Int: "INTEGER",
|
|
3756
|
+
BigInt: "INTEGER",
|
|
3757
|
+
Float: "REAL",
|
|
3758
|
+
Decimal: "TEXT",
|
|
3759
|
+
DateTime: "TEXT",
|
|
3760
|
+
Json: "TEXT",
|
|
3761
|
+
Bytes: "BLOB"
|
|
3762
|
+
});
|
|
3763
|
+
var POSTGRES_NATIVE_ALIASES = Object.freeze({
|
|
3764
|
+
charactervarying: "varchar",
|
|
3765
|
+
character: "char",
|
|
3766
|
+
bpchar: "char",
|
|
3767
|
+
int: "integer",
|
|
3768
|
+
int4: "integer",
|
|
3769
|
+
int2: "smallint",
|
|
3770
|
+
int8: "bigint",
|
|
3771
|
+
serial: "integer",
|
|
3772
|
+
bigserial: "bigint",
|
|
3773
|
+
bool: "boolean",
|
|
3774
|
+
float4: "real",
|
|
3775
|
+
float8: "doubleprecision",
|
|
3776
|
+
double: "doubleprecision",
|
|
3777
|
+
doubleprecision: "doubleprecision",
|
|
3778
|
+
decimal: "numeric",
|
|
3779
|
+
timestampwithtimezone: "timestamptz",
|
|
3780
|
+
timestampwithouttimezone: "timestamp",
|
|
3781
|
+
timewithtimezone: "timetz",
|
|
3782
|
+
timewithouttimezone: "time",
|
|
3783
|
+
bitvarying: "varbit"
|
|
3784
|
+
});
|
|
3785
|
+
var POSTGRES_IMPLICIT_MODIFIERS = Object.freeze({
|
|
3786
|
+
bit: "(1)"
|
|
3787
|
+
});
|
|
3788
|
+
var POSTGRES_DEFAULT_PRECISION_SIX = new Set(["timestamp", "time", "timetz"]);
|
|
3789
|
+
function normalizePostgresNativeType(params) {
|
|
3790
|
+
const compact = params.raw.toLowerCase().replace(/\s+/g, "");
|
|
3791
|
+
const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(\[\])?$/);
|
|
3792
|
+
if (match === null)
|
|
3793
|
+
return compact;
|
|
3794
|
+
const base = POSTGRES_NATIVE_ALIASES[match[1]] ?? match[1];
|
|
3795
|
+
const modifier = POSTGRES_DEFAULT_PRECISION_SIX.has(base) && match[2] === "(6)" ? "" : match[2] ?? POSTGRES_IMPLICIT_MODIFIERS[base] ?? "";
|
|
3796
|
+
return `${base}${modifier}${match[3] ?? ""}`;
|
|
3797
|
+
}
|
|
3798
|
+
var POSTGRES_EXACT_EQUIVALENTS = Object.freeze({
|
|
3799
|
+
DateTime: Object.freeze(["timestamptz(6)"])
|
|
3800
|
+
});
|
|
3801
|
+
var MYSQL_NATIVE_ALIASES = Object.freeze({
|
|
3802
|
+
integer: "int",
|
|
3803
|
+
dec: "decimal",
|
|
3804
|
+
numeric: "decimal",
|
|
3805
|
+
bool: "tinyint(1)",
|
|
3806
|
+
boolean: "tinyint(1)",
|
|
3807
|
+
character: "char"
|
|
3808
|
+
});
|
|
3809
|
+
function normalizeMysqlNativeType(params) {
|
|
3810
|
+
const compact = params.raw.toLowerCase().replace(/\s+/g, "");
|
|
3811
|
+
if (compact === "tinyint(1)" || compact === "bool" || compact === "boolean")
|
|
3812
|
+
return "tinyint(1)";
|
|
3813
|
+
const match = compact.match(/^([a-z_0-9]+)(\(.*\))?(.*)$/);
|
|
3814
|
+
if (match === null)
|
|
3815
|
+
return compact;
|
|
3816
|
+
const base = MYSQL_NATIVE_ALIASES[match[1]] ?? match[1];
|
|
3817
|
+
let modifier = match[2] ?? "";
|
|
3818
|
+
if (/^(tinyint|smallint|mediumint|int|bigint)$/.test(base))
|
|
3819
|
+
modifier = "";
|
|
3820
|
+
return `${base}${modifier}${match[3] ?? ""}`;
|
|
3821
|
+
}
|
|
3822
|
+
function canonicalColumnType(params) {
|
|
3823
|
+
const { dialect, scalar, nativeType } = params;
|
|
3824
|
+
switch (dialect) {
|
|
3825
|
+
case "postgres": {
|
|
3826
|
+
const canonical = normalizePostgresNativeType({ raw: POSTGRES_SCALAR_COLUMN_TYPES[scalar] });
|
|
3827
|
+
if (nativeType === undefined)
|
|
3828
|
+
return canonical;
|
|
3829
|
+
const declared = normalizePostgresNativeType({ raw: nativeType });
|
|
3830
|
+
return declared === canonical || POSTGRES_EXACT_EQUIVALENTS[scalar]?.includes(declared) === true ? canonical : declared;
|
|
3831
|
+
}
|
|
3832
|
+
case "mysql":
|
|
3833
|
+
return normalizeMysqlNativeType({ raw: nativeType ?? MYSQL_SCALAR_COLUMN_TYPES[scalar] });
|
|
3834
|
+
case "sqlite":
|
|
3835
|
+
return SQLITE_SCALAR_STORAGE_CLASSES[scalar];
|
|
3379
3836
|
}
|
|
3380
3837
|
}
|
|
3381
3838
|
// packages/sql/src/generated-columns.ts
|
|
3382
|
-
import { VibeError as
|
|
3839
|
+
import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
3383
3840
|
|
|
3384
3841
|
// packages/sql/src/dialects/postgres-generated-columns.ts
|
|
3385
|
-
import { unsupportedCapability as
|
|
3842
|
+
import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
3386
3843
|
function renderPostgresGeneratedColumnClause({ generated }) {
|
|
3387
3844
|
return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
|
|
3388
3845
|
}
|
|
3389
3846
|
function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
|
|
3390
3847
|
if (dialect.name !== "postgres")
|
|
3391
|
-
throw
|
|
3848
|
+
throw unsupportedCapability19({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
|
|
3392
3849
|
const q = ({ name }) => dialect.quoteIdent({ name });
|
|
3393
3850
|
let text;
|
|
3394
3851
|
switch (probe.operation) {
|
|
@@ -3409,20 +3866,20 @@ function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
|
|
|
3409
3866
|
}
|
|
3410
3867
|
function renderPostgresDropGeneratedExpression({ dialect, table, column }) {
|
|
3411
3868
|
if (dialect.name !== "postgres")
|
|
3412
|
-
throw
|
|
3869
|
+
throw unsupportedCapability19({ dialect: dialect.name, feature: "dropGeneratedExpression" });
|
|
3413
3870
|
return { text: `ALTER TABLE ${dialect.quoteIdent({ name: table.namespace })}.${dialect.quoteIdent({ name: table.name })} ALTER COLUMN ${dialect.quoteIdent({ name: column })} DROP EXPRESSION`, values: [], returning: "none" };
|
|
3414
3871
|
}
|
|
3415
3872
|
|
|
3416
3873
|
// packages/sql/src/generated-columns.ts
|
|
3417
3874
|
function renderGeneratedColumnClause({ dialect, generated }) {
|
|
3418
3875
|
if (!("generatedColumns" in dialect.capabilities) || dialect.capabilities.generatedColumns !== true) {
|
|
3419
|
-
throw
|
|
3876
|
+
throw unsupportedCapability20({ dialect: dialect.name, feature: "generatedColumns" });
|
|
3420
3877
|
}
|
|
3421
3878
|
if (generated.storage !== "stored") {
|
|
3422
|
-
throw
|
|
3879
|
+
throw unsupportedCapability20({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
|
|
3423
3880
|
}
|
|
3424
3881
|
if (typeof generated.expression !== "string" || generated.expression.trim().length === 0 || generated.expression.includes("\x00")) {
|
|
3425
|
-
throw new
|
|
3882
|
+
throw new VibeError18({ code: "VIBE_SCHEMA_INVALID", message: "A generated column requires non-empty, NUL-free SQL", meta: { reason: "generated-expression-invalid" } });
|
|
3426
3883
|
}
|
|
3427
3884
|
switch (dialect.name) {
|
|
3428
3885
|
case "postgres":
|
|
@@ -3434,10 +3891,10 @@ function renderGeneratedColumnClause({ dialect, generated }) {
|
|
|
3434
3891
|
}
|
|
3435
3892
|
}
|
|
3436
3893
|
// packages/sql/src/row-rules.ts
|
|
3437
|
-
import { VibeError as
|
|
3894
|
+
import { VibeError as VibeError22, internalError as internalError4, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3438
3895
|
|
|
3439
3896
|
// packages/sql/src/dialects/postgres-row-rules.ts
|
|
3440
|
-
import { VibeError as
|
|
3897
|
+
import { VibeError as VibeError19, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
3441
3898
|
function comparison({ dialect, field }) {
|
|
3442
3899
|
const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
3443
3900
|
const cast = field.type === "Json" ? `::pg_catalog.jsonb${field.isList ? "[]" : ""}` : "";
|
|
@@ -3445,7 +3902,7 @@ function comparison({ dialect, field }) {
|
|
|
3445
3902
|
}
|
|
3446
3903
|
function renderPostgresRowRule({ dialect, context }) {
|
|
3447
3904
|
if (dialect.name !== "postgres")
|
|
3448
|
-
throw
|
|
3905
|
+
throw unsupportedCapability21({ dialect: dialect.name, feature: "postgresRowRules" });
|
|
3449
3906
|
const { rule, table, functionIdentity, triggerIdentity, protectedFields, gateField } = context;
|
|
3450
3907
|
const qualify = ({ name }) => `${dialect.quoteIdent({ name: name.namespace })}.${dialect.quoteIdent({ name: name.name })}`;
|
|
3451
3908
|
const functionName = qualify({ name: functionIdentity.name });
|
|
@@ -3462,7 +3919,7 @@ END;`;
|
|
|
3462
3919
|
} else {
|
|
3463
3920
|
const terms = protectedFields.map((field) => comparison({ dialect, field }));
|
|
3464
3921
|
if (terms.length === 0) {
|
|
3465
|
-
throw new
|
|
3922
|
+
throw new VibeError19({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
|
|
3466
3923
|
}
|
|
3467
3924
|
const condition = gateField === undefined ? terms.join(`
|
|
3468
3925
|
OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
@@ -3497,19 +3954,19 @@ FOR EACH ROW EXECUTE FUNCTION ${functionName}();`;
|
|
|
3497
3954
|
}
|
|
3498
3955
|
|
|
3499
3956
|
// packages/sql/src/dialects/sqlite-row-rules.ts
|
|
3500
|
-
import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as
|
|
3957
|
+
import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as VibeError20, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
3501
3958
|
function renderSqliteRowRule({ dialect, context }) {
|
|
3502
3959
|
if (dialect.name !== "sqlite")
|
|
3503
|
-
throw
|
|
3960
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "sqliteRowRules" });
|
|
3504
3961
|
const { rule, table, triggers, protectedFields, gateField } = context;
|
|
3505
3962
|
if (table.namespace !== "main")
|
|
3506
|
-
throw
|
|
3963
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3507
3964
|
const terms = protectedFields.map((field) => {
|
|
3508
3965
|
const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
3509
3966
|
return `OLD.${column} IS NOT NEW.${column}`;
|
|
3510
3967
|
});
|
|
3511
3968
|
if (rule.kind !== "appendOnly" && terms.length === 0)
|
|
3512
|
-
throw new
|
|
3969
|
+
throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
|
|
3513
3970
|
const condition = rule.kind === "appendOnly" ? "" : gateField === undefined ? `WHEN ${terms.join(`
|
|
3514
3971
|
OR `)}
|
|
3515
3972
|
` : `WHEN OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
@@ -3534,11 +3991,11 @@ END;`,
|
|
|
3534
3991
|
}
|
|
3535
3992
|
|
|
3536
3993
|
// packages/sql/src/dialects/mysql-row-rules.ts
|
|
3537
|
-
import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as
|
|
3994
|
+
import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as VibeError21, mysqlRowRuleMessageByteLength, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
3538
3995
|
function diagnosticLiteral({ name, context, dialect }) {
|
|
3539
3996
|
const message = `Check constraint '${name}' is violated.`;
|
|
3540
3997
|
if (mysqlRowRuleMessageByteLength({ triggerName: name }) > MYSQL_ROW_RULE_MESSAGE_MAX_BYTES)
|
|
3541
|
-
throw new
|
|
3998
|
+
throw new VibeError21({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${context.rule.name} exceeds MySQL's ${MYSQL_ROW_RULE_MESSAGE_MAX_BYTES}-byte diagnostic limit; supply a shorter name stem`, meta: { model: context.model, rule: context.rule.name } });
|
|
3542
3999
|
if (message.includes("\\")) {
|
|
3543
4000
|
const hex = Array.from(new TextEncoder().encode(message), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3544
4001
|
return `_utf8mb4 X'${hex}'`;
|
|
@@ -3547,11 +4004,11 @@ function diagnosticLiteral({ name, context, dialect }) {
|
|
|
3547
4004
|
}
|
|
3548
4005
|
function renderMysqlRowRule({ dialect, context }) {
|
|
3549
4006
|
if (dialect.name !== "mysql")
|
|
3550
|
-
throw
|
|
4007
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "mysqlRowRules" });
|
|
3551
4008
|
const { rule, table, triggers, protectedFields, gateField, placement } = context;
|
|
3552
4009
|
for (const name of Object.keys(placement ?? {}))
|
|
3553
4010
|
if (!triggers.some(({ identity }) => identity.name === name))
|
|
3554
|
-
throw new
|
|
4011
|
+
throw new VibeError21({ code: "VIBE_INTERNAL", message: "A MySQL trigger placement names a trigger this rule does not own", meta: { model: context.model, rule: rule.name, trigger: name } });
|
|
3555
4012
|
const position = ({ name }) => {
|
|
3556
4013
|
const entry = placement?.[name];
|
|
3557
4014
|
if (entry === undefined)
|
|
@@ -3564,7 +4021,7 @@ function renderMysqlRowRule({ dialect, context }) {
|
|
|
3564
4021
|
return `NOT (OLD.${column} <=> NEW.${column})`;
|
|
3565
4022
|
});
|
|
3566
4023
|
if (rule.kind !== "appendOnly" && terms.length === 0)
|
|
3567
|
-
throw new
|
|
4024
|
+
throw new VibeError21({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
|
|
3568
4025
|
const condition = gateField === undefined ? terms.join(`
|
|
3569
4026
|
OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
3570
4027
|
${terms.join(`
|
|
@@ -3593,10 +4050,10 @@ END;`, values: [] };
|
|
|
3593
4050
|
function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
3594
4051
|
const issue = rowRuleShapeIssue({ value: rule });
|
|
3595
4052
|
if (issue !== undefined) {
|
|
3596
|
-
throw new
|
|
4053
|
+
throw new VibeError22({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { model: model.name } });
|
|
3597
4054
|
}
|
|
3598
4055
|
if (placement !== undefined && Object.keys(placement).length > 0 && dialect.name !== "mysql")
|
|
3599
|
-
throw
|
|
4056
|
+
throw unsupportedCapability24({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
|
|
3600
4057
|
const normalized = normalizeRowRules({ schema, model, rules: [rule], dialect: dialect.name })[0];
|
|
3601
4058
|
switch (dialect.name) {
|
|
3602
4059
|
case "postgres": {
|
|
@@ -3604,7 +4061,7 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
3604
4061
|
const functionIdentity = objects.find((object) => object.role === "function")?.identity;
|
|
3605
4062
|
const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
|
|
3606
4063
|
if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
|
|
3607
|
-
throw
|
|
4064
|
+
throw internalError4({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
3608
4065
|
const gate = rowRuleGateField({ rule: normalized });
|
|
3609
4066
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
3610
4067
|
return renderPostgresRowRule({
|
|
@@ -3625,15 +4082,15 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
3625
4082
|
const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
|
|
3626
4083
|
const triggers = objects.map(({ role, identity }) => {
|
|
3627
4084
|
if (role !== "trigger" || identity.kind !== "trigger")
|
|
3628
|
-
throw
|
|
4085
|
+
throw internalError4({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
|
|
3629
4086
|
const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
|
|
3630
4087
|
if (event === undefined)
|
|
3631
|
-
throw
|
|
4088
|
+
throw internalError4({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
|
|
3632
4089
|
return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
|
|
3633
4090
|
});
|
|
3634
4091
|
const table = triggers[0]?.identity.table;
|
|
3635
4092
|
if (table === undefined)
|
|
3636
|
-
throw
|
|
4093
|
+
throw internalError4({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
3637
4094
|
const gate = rowRuleGateField({ rule: normalized });
|
|
3638
4095
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
3639
4096
|
const context = {
|
|
@@ -3664,15 +4121,16 @@ function renderSqliteJsonEquality(params) {
|
|
|
3664
4121
|
}
|
|
3665
4122
|
|
|
3666
4123
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3667
|
-
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as
|
|
4124
|
+
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
|
|
3668
4125
|
var QUERIES = Object.freeze({
|
|
3669
4126
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
4127
|
+
postgresServerVersion: { dialect: "postgres", text: `SELECT pg_catalog.current_setting('server_version_num') AS "serverVersionNum"` },
|
|
3670
4128
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
3671
4129
|
});
|
|
3672
4130
|
function renderDiagnostic(params) {
|
|
3673
4131
|
const query = QUERIES[params.query];
|
|
3674
4132
|
if (params.dialect.name !== query.dialect) {
|
|
3675
|
-
throw
|
|
4133
|
+
throw unsupportedCapability25({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
3676
4134
|
}
|
|
3677
4135
|
return { text: query.text, values: [], returning: "none" };
|
|
3678
4136
|
}
|
|
@@ -3683,7 +4141,7 @@ var CAPTURE_READINESS_SQL = `SELECT pg_catalog.current_setting('server_version_n
|
|
|
3683
4141
|
function renderCaptureReadiness(params) {
|
|
3684
4142
|
const { dialect, tables } = params;
|
|
3685
4143
|
if (dialect.name !== "postgres") {
|
|
3686
|
-
throw
|
|
4144
|
+
throw unsupportedCapability25({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
|
|
3687
4145
|
}
|
|
3688
4146
|
const entries = tables.map((entry, index) => {
|
|
3689
4147
|
const { name, namespace } = entry.table;
|
|
@@ -3697,7 +4155,7 @@ function renderCaptureReadiness(params) {
|
|
|
3697
4155
|
}
|
|
3698
4156
|
|
|
3699
4157
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
3700
|
-
import { VibeError as
|
|
4158
|
+
import { VibeError as VibeError23, unsupportedCapability as unsupportedCapability26 } from "@vibeorm/schema";
|
|
3701
4159
|
function renderPostgresExplain(params) {
|
|
3702
4160
|
const { dialect, query, options } = params;
|
|
3703
4161
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -3732,7 +4190,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
3732
4190
|
});
|
|
3733
4191
|
function requirePostgres(params) {
|
|
3734
4192
|
if (params.dialect.name !== "postgres") {
|
|
3735
|
-
throw
|
|
4193
|
+
throw unsupportedCapability26({
|
|
3736
4194
|
dialect: params.dialect.name,
|
|
3737
4195
|
feature: params.feature,
|
|
3738
4196
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -3750,7 +4208,7 @@ function renderPostgresWorkload(params) {
|
|
|
3750
4208
|
if (query === "statementSnapshot") {
|
|
3751
4209
|
const bounded = limit ?? 20;
|
|
3752
4210
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
3753
|
-
throw new
|
|
4211
|
+
throw new VibeError23({
|
|
3754
4212
|
code: "VIBE_CONFIG",
|
|
3755
4213
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
3756
4214
|
meta: { limit }
|
|
@@ -3769,7 +4227,7 @@ function renderPostgresWorkload(params) {
|
|
|
3769
4227
|
function requireArg(params) {
|
|
3770
4228
|
const { value, name, query } = params;
|
|
3771
4229
|
if (value === undefined || value.length === 0) {
|
|
3772
|
-
throw new
|
|
4230
|
+
throw new VibeError23({
|
|
3773
4231
|
code: "VIBE_CONFIG",
|
|
3774
4232
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
3775
4233
|
meta: { query, argument: name }
|
|
@@ -3779,10 +4237,10 @@ function requireArg(params) {
|
|
|
3779
4237
|
}
|
|
3780
4238
|
|
|
3781
4239
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
3782
|
-
import { unsupportedCapability as
|
|
4240
|
+
import { unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
3783
4241
|
|
|
3784
4242
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
3785
|
-
import { RlsContextError, VibeError as
|
|
4243
|
+
import { RlsContextError, VibeError as VibeError24, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability27, validateRlsContextIR } from "@vibeorm/schema";
|
|
3786
4244
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
3787
4245
|
var COLUMN_TYPES = {
|
|
3788
4246
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -3795,18 +4253,18 @@ var COLUMN_TYPES = {
|
|
|
3795
4253
|
};
|
|
3796
4254
|
function requireRls(params) {
|
|
3797
4255
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
3798
|
-
throw
|
|
4256
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
3799
4257
|
}
|
|
3800
4258
|
}
|
|
3801
4259
|
function identifier2(params) {
|
|
3802
4260
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
3803
|
-
throw new
|
|
4261
|
+
throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
3804
4262
|
}
|
|
3805
4263
|
return params.dialect.quoteIdent({ name: params.name });
|
|
3806
4264
|
}
|
|
3807
4265
|
function tableRef(params) {
|
|
3808
4266
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
3809
|
-
throw
|
|
4267
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
3810
4268
|
}
|
|
3811
4269
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
3812
4270
|
}
|
|
@@ -3847,7 +4305,7 @@ function renderRlsColumnType(params) {
|
|
|
3847
4305
|
if (native !== undefined)
|
|
3848
4306
|
return native;
|
|
3849
4307
|
}
|
|
3850
|
-
throw
|
|
4308
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
3851
4309
|
}
|
|
3852
4310
|
function protectedModel(params) {
|
|
3853
4311
|
requireRls(params);
|
|
@@ -3855,7 +4313,7 @@ function protectedModel(params) {
|
|
|
3855
4313
|
assertValidSchemaIR({ schema: params.schema });
|
|
3856
4314
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
3857
4315
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
3858
|
-
throw new
|
|
4316
|
+
throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
3859
4317
|
}
|
|
3860
4318
|
return model;
|
|
3861
4319
|
}
|
|
@@ -3888,7 +4346,7 @@ function predicate(params) {
|
|
|
3888
4346
|
}).join(" AND ");
|
|
3889
4347
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
3890
4348
|
}
|
|
3891
|
-
throw new
|
|
4349
|
+
throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
3892
4350
|
}
|
|
3893
4351
|
function renderRlsPolicy(params) {
|
|
3894
4352
|
const model = protectedModel(params);
|
|
@@ -3929,7 +4387,7 @@ function renderRlsContext(params) {
|
|
|
3929
4387
|
}
|
|
3930
4388
|
function runtimeRoleOf(params) {
|
|
3931
4389
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
3932
|
-
throw new
|
|
4390
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
3933
4391
|
}
|
|
3934
4392
|
return params.role;
|
|
3935
4393
|
}
|
|
@@ -4086,7 +4544,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
4086
4544
|
});
|
|
4087
4545
|
function requireRls2(params) {
|
|
4088
4546
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
4089
|
-
throw
|
|
4547
|
+
throw unsupportedCapability28({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
4090
4548
|
}
|
|
4091
4549
|
}
|
|
4092
4550
|
function renderRlsReadinessQuery(params) {
|
|
@@ -4118,7 +4576,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4118
4576
|
}
|
|
4119
4577
|
|
|
4120
4578
|
// packages/sql/src/render.ts
|
|
4121
|
-
import { assertSqlNamespace as assertSqlNamespace3, internalError as
|
|
4579
|
+
import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError5, unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
|
|
4122
4580
|
|
|
4123
4581
|
// packages/sql/src/like.ts
|
|
4124
4582
|
function escapeLikePattern(params) {
|
|
@@ -4206,21 +4664,29 @@ function renderStatement(params) {
|
|
|
4206
4664
|
}
|
|
4207
4665
|
}
|
|
4208
4666
|
function quoteCol(dialect, column) {
|
|
4667
|
+
if (column.coalesce !== undefined) {
|
|
4668
|
+
if (column.coalesce.length < 2 || column.coalesce.some((member) => member.coalesce !== undefined)) {
|
|
4669
|
+
throw internalError5({ message: "ColRef.coalesce needs two or more plain member references" });
|
|
4670
|
+
}
|
|
4671
|
+
return `COALESCE(${column.coalesce.map((member) => quoteCol(dialect, member)).join(", ")})`;
|
|
4672
|
+
}
|
|
4209
4673
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4210
4674
|
const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
|
|
4211
4675
|
if (column.truncate !== undefined)
|
|
4212
4676
|
return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
|
|
4677
|
+
if (column.groupKey !== undefined)
|
|
4678
|
+
return renderGroupKey({ dialect, reference: ref, key: column.groupKey });
|
|
4213
4679
|
if (column.enumOrdinal === true)
|
|
4214
4680
|
return dialect.renderEnumOrdinal({ column: ref });
|
|
4215
4681
|
if (column.castReal === true) {
|
|
4216
4682
|
if (dialect.name !== "sqlite") {
|
|
4217
|
-
throw
|
|
4683
|
+
throw internalError5({ message: "ColRef.castReal is a sqlite Decimal construct" });
|
|
4218
4684
|
}
|
|
4219
4685
|
return `CAST(${ref} AS REAL)`;
|
|
4220
4686
|
}
|
|
4221
4687
|
if (column.castJsonb !== undefined) {
|
|
4222
4688
|
if (dialect.name !== "postgres") {
|
|
4223
|
-
throw
|
|
4689
|
+
throw internalError5({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
4224
4690
|
}
|
|
4225
4691
|
return `${ref}::${column.castJsonb}`;
|
|
4226
4692
|
}
|
|
@@ -4228,7 +4694,9 @@ function quoteCol(dialect, column) {
|
|
|
4228
4694
|
}
|
|
4229
4695
|
function typedOrderRef(params) {
|
|
4230
4696
|
const { statement, column } = params;
|
|
4231
|
-
if (column.
|
|
4697
|
+
if (column.coalesce !== undefined)
|
|
4698
|
+
return { ...column, coalesce: column.coalesce.map((member) => typedOrderRef({ statement, column: member })) };
|
|
4699
|
+
if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined || column.truncate !== undefined || column.groupKey !== undefined)
|
|
4232
4700
|
return column;
|
|
4233
4701
|
const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
|
|
4234
4702
|
return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
|
|
@@ -4237,14 +4705,14 @@ function jsonCellCast(dialect, cell) {
|
|
|
4237
4705
|
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
4238
4706
|
return dialect.jsonParamCast;
|
|
4239
4707
|
if (dialect.name !== "postgres") {
|
|
4240
|
-
throw
|
|
4708
|
+
throw internalError5({ message: "JsonParamCell storage json is a postgres construct" });
|
|
4241
4709
|
}
|
|
4242
4710
|
return "::text::json";
|
|
4243
4711
|
}
|
|
4244
4712
|
function quoteProjection(dialect, column) {
|
|
4245
4713
|
if (column.transform === undefined) {
|
|
4246
4714
|
const base = quoteCol(dialect, column);
|
|
4247
|
-
const alias = column.alias ?? (column.truncate === undefined ? undefined : column.name);
|
|
4715
|
+
const alias = column.alias ?? (column.truncate === undefined && column.groupKey === undefined ? undefined : column.name);
|
|
4248
4716
|
if (alias === undefined)
|
|
4249
4717
|
return base;
|
|
4250
4718
|
return `${base} AS ${dialect.quoteIdent({ name: alias })}`;
|
|
@@ -4261,7 +4729,7 @@ function quoteProjection(dialect, column) {
|
|
|
4261
4729
|
if (column.transform === "encodeBase64" && dialect.name === "mysql")
|
|
4262
4730
|
return `TO_BASE64(${ref}) AS ${alias}`;
|
|
4263
4731
|
if (dialect.name !== "postgres") {
|
|
4264
|
-
throw
|
|
4732
|
+
throw internalError5({ message: `${column.transform} is a postgres construct` });
|
|
4265
4733
|
}
|
|
4266
4734
|
if (column.transform === "encodeArrayBase64")
|
|
4267
4735
|
return `encode(array_send(${ref}), 'base64') AS ${alias}`;
|
|
@@ -4285,7 +4753,7 @@ function renderTable(dialect, table) {
|
|
|
4285
4753
|
}
|
|
4286
4754
|
function assertCount(value, what) {
|
|
4287
4755
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4288
|
-
throw
|
|
4756
|
+
throw internalError5({ message: `${what} must be a non-negative integer, got ${value}` });
|
|
4289
4757
|
}
|
|
4290
4758
|
}
|
|
4291
4759
|
var COMPARISON_SQL2 = {
|
|
@@ -4309,7 +4777,9 @@ var AGG_FN_SQL = {
|
|
|
4309
4777
|
min: "MIN",
|
|
4310
4778
|
max: "MAX"
|
|
4311
4779
|
};
|
|
4312
|
-
function renderAggRef(dialect, agg) {
|
|
4780
|
+
function renderAggRef(dialect, agg, collector) {
|
|
4781
|
+
if (agg.filter !== undefined)
|
|
4782
|
+
return renderFilteredAggregate({ dialect, agg, filter: agg.filter, collector });
|
|
4313
4783
|
if (agg.fn === "countAll")
|
|
4314
4784
|
return "COUNT(*)";
|
|
4315
4785
|
const column = quoteCol(dialect, agg.column);
|
|
@@ -4318,10 +4788,112 @@ function renderAggRef(dialect, agg) {
|
|
|
4318
4788
|
}
|
|
4319
4789
|
return `${AGG_FN_SQL[agg.fn]}(${column})`;
|
|
4320
4790
|
}
|
|
4791
|
+
function renderFilteredAggregate(params) {
|
|
4792
|
+
const { dialect, agg, filter, collector } = params;
|
|
4793
|
+
if (dialect.capabilities.filteredAggregates === "filterClause") {
|
|
4794
|
+
const { filter: _filter, ...plain } = agg;
|
|
4795
|
+
const aggregate = renderAggRef(dialect, plain, collector);
|
|
4796
|
+
return `${aggregate} FILTER (WHERE ${renderExpr({ dialect, expr: filter, collector })})`;
|
|
4797
|
+
}
|
|
4798
|
+
const when = renderExpr({ dialect, expr: filter, collector });
|
|
4799
|
+
if (agg.fn === "countAll")
|
|
4800
|
+
return `COUNT(CASE WHEN ${when} THEN 1 END)`;
|
|
4801
|
+
const column = quoteCol(dialect, agg.column);
|
|
4802
|
+
if ((agg.fn === "min" || agg.fn === "max") && agg.enumOrder !== undefined) {
|
|
4803
|
+
return dialect.renderEnumMinMax({ fn: agg.fn, column, result: agg.enumOrder, when });
|
|
4804
|
+
}
|
|
4805
|
+
return `${AGG_FN_SQL[agg.fn]}(CASE WHEN ${when} THEN ${column} END)`;
|
|
4806
|
+
}
|
|
4807
|
+
var MAX_GROUP_KEY_PREFIX = 1000;
|
|
4808
|
+
function renderGroupKey(params) {
|
|
4809
|
+
const { dialect, reference, key } = params;
|
|
4810
|
+
if (key.kind === "prefix") {
|
|
4811
|
+
if (!Number.isSafeInteger(key.length) || key.length < 1 || key.length > MAX_GROUP_KEY_PREFIX) {
|
|
4812
|
+
throw internalError5({ message: `a groupBy prefix length must be an integer from 1 to ${MAX_GROUP_KEY_PREFIX}, got ${String(key.length)}` });
|
|
4813
|
+
}
|
|
4814
|
+
if (dialect.name === "sqlite")
|
|
4815
|
+
return `substr(${reference}, 1, ${key.length})`;
|
|
4816
|
+
return `${dialect.name === "mysql" ? "LEFT" : "left"}(${reference}, ${key.length})`;
|
|
4817
|
+
}
|
|
4818
|
+
if (key.path.length === 0 || key.path.some((step) => typeof step !== "string" || step.length === 0 || step.includes("\x00"))) {
|
|
4819
|
+
throw internalError5({ message: "a groupBy JSON path needs one or more non-empty object keys without NUL characters" });
|
|
4820
|
+
}
|
|
4821
|
+
switch (dialect.jsonPathMode) {
|
|
4822
|
+
case "operators": {
|
|
4823
|
+
const steps = key.path.map((step) => dialect.quoteStringLiteral({ value: step }));
|
|
4824
|
+
if (key.storage === "json")
|
|
4825
|
+
return `((${reference} -> ${steps.join(" -> ")})::jsonb #>> '{}')`;
|
|
4826
|
+
const last = steps.length - 1;
|
|
4827
|
+
return `(${reference}${steps.map((step, index) => `${index === last ? " ->> " : " -> "}${step}`).join("")})`;
|
|
4828
|
+
}
|
|
4829
|
+
case "sqliteExtract": {
|
|
4830
|
+
const path = dialect.quoteStringLiteral({ value: jsonPathString(key.path) });
|
|
4831
|
+
const extracted = `json_extract(${reference}, ${path})`;
|
|
4832
|
+
return `CASE json_type(${reference}, ${path}) WHEN 'text' THEN ${extracted} WHEN 'true' THEN 'true' WHEN 'false' THEN 'false' WHEN 'null' THEN NULL ELSE CAST(${extracted} AS TEXT) END`;
|
|
4833
|
+
}
|
|
4834
|
+
case "mysqlExtract": {
|
|
4835
|
+
const extracted = `JSON_EXTRACT(${reference}, ${dialect.quoteStringLiteral({ value: jsonPathString(key.path) })})`;
|
|
4836
|
+
return `CASE WHEN JSON_TYPE(${extracted}) = 'NULL' THEN NULL ELSE JSON_UNQUOTE(${extracted}) END`;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
function renderGroupingSets(params) {
|
|
4841
|
+
const { dialect, keys, grouping } = params;
|
|
4842
|
+
const sets = groupingSetIndexes({ keys, grouping });
|
|
4843
|
+
const mode = dialect.capabilities.groupingSets;
|
|
4844
|
+
if (mode === "groupingSets") {
|
|
4845
|
+
return `GROUPING SETS (${sets.map((set) => `(${set.map((index) => keys[index]).join(", ")})`).join(", ")})`;
|
|
4846
|
+
}
|
|
4847
|
+
if (mode === "rollup" && isRollupChain({ count: keys.length, sets }))
|
|
4848
|
+
return `${keys.join(", ")} WITH ROLLUP`;
|
|
4849
|
+
throw unsupportedCapability29({
|
|
4850
|
+
dialect: dialect.name,
|
|
4851
|
+
feature: mode === "rollup" ? "groupBy grouping sets other than the rollup of `by` in order" : "groupBy grouping sets",
|
|
4852
|
+
hint: mode === "rollup" ? "MySQL groups by `WITH ROLLUP` only: list the sets [[a, b], [a], []] for by [a, b], or run one groupBy per set" : "run one groupBy per grouping set instead"
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
function groupingSetIndexes(params) {
|
|
4856
|
+
const { keys, grouping } = params;
|
|
4857
|
+
if (grouping.sets.length === 0 || keys.length === 0 || keys.length > 31) {
|
|
4858
|
+
throw internalError5({ message: "grouping sets need one or more sets over 1 to 31 groupBy keys" });
|
|
4859
|
+
}
|
|
4860
|
+
for (const set of grouping.sets) {
|
|
4861
|
+
if (set.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= keys.length)) {
|
|
4862
|
+
throw internalError5({ message: "a grouping set names a groupBy key that does not exist" });
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
return grouping.sets;
|
|
4866
|
+
}
|
|
4867
|
+
function isRollupChain(params) {
|
|
4868
|
+
const { count, sets } = params;
|
|
4869
|
+
if (sets.length !== count + 1)
|
|
4870
|
+
return false;
|
|
4871
|
+
const lengths = new Set;
|
|
4872
|
+
for (const set of sets) {
|
|
4873
|
+
const sorted = [...set].sort((left, right) => left - right);
|
|
4874
|
+
if (!sorted.every((index, position) => index === position))
|
|
4875
|
+
return false;
|
|
4876
|
+
lengths.add(set.length);
|
|
4877
|
+
}
|
|
4878
|
+
return lengths.size === count + 1;
|
|
4879
|
+
}
|
|
4880
|
+
function renderGroupingSetIndex(params) {
|
|
4881
|
+
const { keys } = params;
|
|
4882
|
+
const sets = groupingSetIndexes(params);
|
|
4883
|
+
const branches = sets.map((set, position) => {
|
|
4884
|
+
let mask = 0;
|
|
4885
|
+
for (let index = 0;index < keys.length; index++) {
|
|
4886
|
+
if (!set.includes(index))
|
|
4887
|
+
mask += 2 ** (keys.length - 1 - index);
|
|
4888
|
+
}
|
|
4889
|
+
return `WHEN ${mask} THEN ${position}`;
|
|
4890
|
+
});
|
|
4891
|
+
return `CASE GROUPING(${keys.join(", ")}) ${branches.join(" ")} END`;
|
|
4892
|
+
}
|
|
4321
4893
|
function assertScalarArrays(dialect, feature) {
|
|
4322
4894
|
if (dialect.capabilities.scalarArrays)
|
|
4323
4895
|
return;
|
|
4324
|
-
throw
|
|
4896
|
+
throw unsupportedCapability29({
|
|
4325
4897
|
dialect: dialect.name,
|
|
4326
4898
|
feature,
|
|
4327
4899
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -4333,7 +4905,7 @@ function renderExpr(params) {
|
|
|
4333
4905
|
case "cmp": {
|
|
4334
4906
|
const op = COMPARISON_SQL2[expr.op];
|
|
4335
4907
|
if (op === undefined) {
|
|
4336
|
-
throw
|
|
4908
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4337
4909
|
}
|
|
4338
4910
|
if (isDbNowMarker(expr.value)) {
|
|
4339
4911
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
@@ -4344,7 +4916,7 @@ function renderExpr(params) {
|
|
|
4344
4916
|
case "cmpCol": {
|
|
4345
4917
|
const op = COMPARISON_SQL2[expr.op];
|
|
4346
4918
|
if (op === undefined) {
|
|
4347
|
-
throw
|
|
4919
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4348
4920
|
}
|
|
4349
4921
|
return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
|
|
4350
4922
|
}
|
|
@@ -4368,11 +4940,11 @@ function renderExpr(params) {
|
|
|
4368
4940
|
}
|
|
4369
4941
|
case "tupleIn": {
|
|
4370
4942
|
if (expr.columns.length === 0) {
|
|
4371
|
-
throw
|
|
4943
|
+
throw internalError5({ message: "tupleIn needs at least one column" });
|
|
4372
4944
|
}
|
|
4373
4945
|
for (const row of expr.rows) {
|
|
4374
4946
|
if (row.length !== expr.columns.length) {
|
|
4375
|
-
throw
|
|
4947
|
+
throw internalError5({
|
|
4376
4948
|
message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
|
|
4377
4949
|
});
|
|
4378
4950
|
}
|
|
@@ -4398,7 +4970,7 @@ function renderExpr(params) {
|
|
|
4398
4970
|
case "rowCompare": {
|
|
4399
4971
|
const op = COMPARISON_SQL2[expr.op];
|
|
4400
4972
|
if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
|
|
4401
|
-
throw
|
|
4973
|
+
throw internalError5({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
|
|
4402
4974
|
}
|
|
4403
4975
|
const columns = expr.columns.map((column) => quoteCol(dialect, column));
|
|
4404
4976
|
const placeholders = expr.values.map((value) => collector.add(value));
|
|
@@ -4406,9 +4978,9 @@ function renderExpr(params) {
|
|
|
4406
4978
|
}
|
|
4407
4979
|
case "inSelect": {
|
|
4408
4980
|
if (expr.columns.length === 0)
|
|
4409
|
-
throw
|
|
4981
|
+
throw internalError5({ message: "inSelect needs at least one column" });
|
|
4410
4982
|
if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
|
|
4411
|
-
throw
|
|
4983
|
+
throw unsupportedCapability29({
|
|
4412
4984
|
dialect: dialect.name,
|
|
4413
4985
|
feature: "LIMIT inside an IN subquery",
|
|
4414
4986
|
hint: "mysql refuses LIMIT directly inside an IN subquery (error 1235); wrap the limited select in a derived table"
|
|
@@ -4479,9 +5051,9 @@ function renderExpr(params) {
|
|
|
4479
5051
|
case "aggCmp": {
|
|
4480
5052
|
const op = COMPARISON_SQL2[expr.op];
|
|
4481
5053
|
if (op === undefined) {
|
|
4482
|
-
throw
|
|
5054
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4483
5055
|
}
|
|
4484
|
-
return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
|
|
5056
|
+
return `${renderAggRef(dialect, expr.agg, collector)} ${op} ${collector.add(expr.value)}`;
|
|
4485
5057
|
}
|
|
4486
5058
|
case "arrayHas": {
|
|
4487
5059
|
assertScalarArrays(dialect, 'the scalar-list filter "has"');
|
|
@@ -4534,19 +5106,6 @@ function renderFragment(params) {
|
|
|
4534
5106
|
}
|
|
4535
5107
|
return text;
|
|
4536
5108
|
}
|
|
4537
|
-
function jsonPathString(path) {
|
|
4538
|
-
let out = "$";
|
|
4539
|
-
for (const segment of path) {
|
|
4540
|
-
if (typeof segment === "number") {
|
|
4541
|
-
out += `[${segment}]`;
|
|
4542
|
-
} else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
|
|
4543
|
-
out += `.${segment}`;
|
|
4544
|
-
} else {
|
|
4545
|
-
out += `."${segment.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
4546
|
-
}
|
|
4547
|
-
}
|
|
4548
|
-
return out;
|
|
4549
|
-
}
|
|
4550
5109
|
function listComparison(params) {
|
|
4551
5110
|
const { dialect, column, value, collector } = params;
|
|
4552
5111
|
if (isJsonListParameter(value) && value.storage === "json") {
|
|
@@ -4561,13 +5120,13 @@ function renderKeyList(params) {
|
|
|
4561
5120
|
const { dialect, expr, collector } = params;
|
|
4562
5121
|
const arity = expr.columns.length;
|
|
4563
5122
|
if (arity === 0)
|
|
4564
|
-
throw
|
|
5123
|
+
throw internalError5({ message: "keyList needs at least one column" });
|
|
4565
5124
|
if (expr.members.length !== arity) {
|
|
4566
|
-
throw
|
|
5125
|
+
throw internalError5({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
|
|
4567
5126
|
}
|
|
4568
5127
|
for (const row of expr.rows) {
|
|
4569
5128
|
if (row.length !== arity)
|
|
4570
|
-
throw
|
|
5129
|
+
throw internalError5({ message: `keyList row has ${row.length} values for ${arity} columns` });
|
|
4571
5130
|
}
|
|
4572
5131
|
if (expr.rows.length === 0)
|
|
4573
5132
|
return "1 = 0";
|
|
@@ -4599,25 +5158,25 @@ function renderKeyPage(params) {
|
|
|
4599
5158
|
const { select } = page;
|
|
4600
5159
|
const arity = page.keyColumns.length;
|
|
4601
5160
|
if (arity === 0 || page.members.length !== arity) {
|
|
4602
|
-
throw
|
|
5161
|
+
throw internalError5({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
|
|
4603
5162
|
}
|
|
4604
5163
|
if (page.rows.length === 0)
|
|
4605
|
-
throw
|
|
5164
|
+
throw internalError5({ message: "keyPage needs at least one key" });
|
|
4606
5165
|
for (const row of page.rows) {
|
|
4607
5166
|
if (row.length !== arity)
|
|
4608
|
-
throw
|
|
5167
|
+
throw internalError5({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
|
|
4609
5168
|
}
|
|
4610
5169
|
if (page.identity.length === 0)
|
|
4611
|
-
throw
|
|
5170
|
+
throw internalError5({ message: "keyPage needs the child's identity columns" });
|
|
4612
5171
|
const order = select.orderBy ?? [];
|
|
4613
5172
|
if (order.length === 0 || order.some((term) => !("column" in term))) {
|
|
4614
|
-
throw
|
|
5173
|
+
throw internalError5({ message: "keyPage needs a non-empty order of plain column terms" });
|
|
4615
5174
|
}
|
|
4616
5175
|
if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
|
|
4617
|
-
throw
|
|
5176
|
+
throw internalError5({ message: "keyPage select carries a member the per-key page cannot render" });
|
|
4618
5177
|
}
|
|
4619
5178
|
if (dialect.keyPageStrategy === "window") {
|
|
4620
|
-
throw
|
|
5179
|
+
throw unsupportedCapability29({
|
|
4621
5180
|
dialect: dialect.name,
|
|
4622
5181
|
feature: "a per-key page (keyPage)",
|
|
4623
5182
|
hint: "the runtime keeps the ROW_NUMBER window on this dialect"
|
|
@@ -4645,7 +5204,7 @@ function renderKeyPage(params) {
|
|
|
4645
5204
|
const keys = dialect.quoteIdent({ name: keyAlias });
|
|
4646
5205
|
if (dialect.keyPageStrategy === "lateral") {
|
|
4647
5206
|
if (keyListBindsUntypedRows({ members: page.members })) {
|
|
4648
|
-
throw
|
|
5207
|
+
throw unsupportedCapability29({
|
|
4649
5208
|
dialect: dialect.name,
|
|
4650
5209
|
feature: "a per-key page over a DateTime key without a declared native type",
|
|
4651
5210
|
hint: "the runtime keeps the ROW_NUMBER window for such keys"
|
|
@@ -4673,22 +5232,22 @@ function renderKeyPage(params) {
|
|
|
4673
5232
|
function renderKeyRows(params) {
|
|
4674
5233
|
const { dialect, statement, keys, collector } = params;
|
|
4675
5234
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined) {
|
|
4676
|
-
throw
|
|
5235
|
+
throw internalError5({ message: "fromKeys uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion and table.alias must be unset" });
|
|
4677
5236
|
}
|
|
4678
5237
|
if (statement.rowLock !== undefined)
|
|
4679
|
-
throw
|
|
5238
|
+
throw internalError5({ message: "a locking read cannot read from key rows" });
|
|
4680
5239
|
const arity = keys.columns.length;
|
|
4681
5240
|
if (arity === 0 || keys.members.length !== arity) {
|
|
4682
|
-
throw
|
|
5241
|
+
throw internalError5({ message: `fromKeys has ${keys.members.length} members for ${arity} columns` });
|
|
4683
5242
|
}
|
|
4684
5243
|
if (keys.rows.length === 0)
|
|
4685
|
-
throw
|
|
5244
|
+
throw internalError5({ message: "fromKeys needs at least one key row" });
|
|
4686
5245
|
for (const row of keys.rows) {
|
|
4687
5246
|
if (row.length !== arity)
|
|
4688
|
-
throw
|
|
5247
|
+
throw internalError5({ message: `fromKeys row has ${row.length} values for ${arity} columns` });
|
|
4689
5248
|
}
|
|
4690
5249
|
if (dialect.keyListStrategy !== "unnest" || keyListBindsUntypedRows({ members: keys.members })) {
|
|
4691
|
-
throw
|
|
5250
|
+
throw unsupportedCapability29({
|
|
4692
5251
|
dialect: dialect.name,
|
|
4693
5252
|
feature: dialect.keyListStrategy !== "unnest" ? "a key-rows FROM (fromKeys)" : "a key-rows FROM over a DateTime key without a declared native type",
|
|
4694
5253
|
hint: "the runtime keeps the per-relation loads for this write"
|
|
@@ -4704,20 +5263,20 @@ function renderUnionPage(params) {
|
|
|
4704
5263
|
const { dialect, statement, collector } = params;
|
|
4705
5264
|
const branches = statement.fromUnion ?? [];
|
|
4706
5265
|
if (branches.length < 2)
|
|
4707
|
-
throw
|
|
5266
|
+
throw internalError5({ message: "fromUnion needs at least two branches" });
|
|
4708
5267
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
|
|
4709
|
-
throw
|
|
5268
|
+
throw internalError5({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
|
|
4710
5269
|
}
|
|
4711
5270
|
if (statement.rowLock !== undefined)
|
|
4712
|
-
throw
|
|
5271
|
+
throw internalError5({ message: "a locking read cannot read from a union page" });
|
|
4713
5272
|
const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
|
|
4714
5273
|
const names = outputNames(branches[0]);
|
|
4715
5274
|
return branches.map((branch, index) => {
|
|
4716
5275
|
if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
|
|
4717
|
-
throw
|
|
5276
|
+
throw internalError5({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
|
|
4718
5277
|
}
|
|
4719
5278
|
if (outputNames(branch) !== names)
|
|
4720
|
-
throw
|
|
5279
|
+
throw internalError5({ message: "fromUnion branches must project the same output names in the same order" });
|
|
4721
5280
|
const inner = renderSelect({ dialect, statement: branch, collector });
|
|
4722
5281
|
return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
|
|
4723
5282
|
}).join(" UNION ALL ");
|
|
@@ -4725,7 +5284,7 @@ function renderUnionPage(params) {
|
|
|
4725
5284
|
function jsonStringPattern(params) {
|
|
4726
5285
|
const { expr } = params;
|
|
4727
5286
|
if (typeof expr.value !== "string") {
|
|
4728
|
-
throw
|
|
5287
|
+
throw internalError5({ message: `json ${expr.op} expects a string operand` });
|
|
4729
5288
|
}
|
|
4730
5289
|
if (expr.op === "string_contains")
|
|
4731
5290
|
return containsPattern({ raw: expr.value });
|
|
@@ -4746,10 +5305,14 @@ function renderJsonLike(params) {
|
|
|
4746
5305
|
base = `${base}${dialect.likeEscapeClause}`;
|
|
4747
5306
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
4748
5307
|
}
|
|
5308
|
+
function renderJsonExists(params) {
|
|
5309
|
+
const { present, column, exists } = params;
|
|
5310
|
+
return `CASE WHEN ${present} THEN ${exists ? "TRUE" : "FALSE"} WHEN ${column} IS NOT NULL THEN ${exists ? "FALSE" : "TRUE"} END`;
|
|
5311
|
+
}
|
|
4749
5312
|
function renderJsonFilter(params) {
|
|
4750
5313
|
const { dialect, expr, collector } = params;
|
|
4751
5314
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
4752
|
-
throw
|
|
5315
|
+
throw unsupportedCapability29({
|
|
4753
5316
|
dialect: dialect.name,
|
|
4754
5317
|
feature: `the json filter "${expr.op}"`,
|
|
4755
5318
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -4761,6 +5324,16 @@ function renderJsonFilter(params) {
|
|
|
4761
5324
|
const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
|
|
4762
5325
|
const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
|
|
4763
5326
|
switch (expr.op) {
|
|
5327
|
+
case "exists": {
|
|
5328
|
+
const typeOf = expr.storage === "json" ? "json_typeof" : "jsonb_typeof";
|
|
5329
|
+
const guards = expr.path.flatMap((segment, index) => {
|
|
5330
|
+
if (typeof segment !== "string" || !/^\s*[+-]?\d/.test(segment))
|
|
5331
|
+
return [];
|
|
5332
|
+
const container = index === 0 ? column : `${column} #> ${collector.add(expr.path.slice(0, index).map(String))}::text[]`;
|
|
5333
|
+
return [` AND ${typeOf}(${container}) = 'object'`];
|
|
5334
|
+
});
|
|
5335
|
+
return renderJsonExists({ present: `(${column} #> ${pathParam}) IS NOT NULL${guards.join("")}`, column, exists: expr.value === true });
|
|
5336
|
+
}
|
|
4764
5337
|
case "equals":
|
|
4765
5338
|
return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
4766
5339
|
case "string_contains":
|
|
@@ -4798,6 +5371,8 @@ function renderJsonFilter(params) {
|
|
|
4798
5371
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
4799
5372
|
};
|
|
4800
5373
|
switch (expr.op) {
|
|
5374
|
+
case "exists":
|
|
5375
|
+
return renderJsonExists({ present: `json_type(${column}, ${collector.add(jsonPathString(expr.path))}) IS NOT NULL`, column, exists: expr.value === true });
|
|
4801
5376
|
case "equals":
|
|
4802
5377
|
return compareExtracted(jsonPathString(expr.path), expr.value);
|
|
4803
5378
|
case "string_contains":
|
|
@@ -4816,7 +5391,7 @@ function renderJsonFilter(params) {
|
|
|
4816
5391
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
4817
5392
|
const tests = elements.map((element) => {
|
|
4818
5393
|
if (element === null || typeof element === "object" && element !== null) {
|
|
4819
|
-
throw
|
|
5394
|
+
throw unsupportedCapability29({
|
|
4820
5395
|
dialect: dialect.name,
|
|
4821
5396
|
feature: "json array_contains with object/array/null elements",
|
|
4822
5397
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -4830,12 +5405,16 @@ function renderJsonFilter(params) {
|
|
|
4830
5405
|
return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
|
|
4831
5406
|
}
|
|
4832
5407
|
default:
|
|
4833
|
-
throw
|
|
5408
|
+
throw internalError5({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
|
|
4834
5409
|
}
|
|
4835
5410
|
}
|
|
4836
5411
|
const column = quoteCol(dialect, expr.column);
|
|
4837
5412
|
const pathString = jsonPathString(expr.path);
|
|
4838
5413
|
switch (expr.op) {
|
|
5414
|
+
case "exists": {
|
|
5415
|
+
const present = `JSON_CONTAINS_PATH(${column}, 'one', ${collector.add(pathString)})`;
|
|
5416
|
+
return expr.value === true ? present : `NOT ${present}`;
|
|
5417
|
+
}
|
|
4839
5418
|
case "equals":
|
|
4840
5419
|
return `JSON_EXTRACT(${column}, ${collector.add(pathString)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
|
|
4841
5420
|
case "string_contains":
|
|
@@ -4852,10 +5431,18 @@ function renderJsonFilter(params) {
|
|
|
4852
5431
|
return `JSON_EXTRACT(${column}, ${collector.add(`${pathString}[last]`)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
|
|
4853
5432
|
}
|
|
4854
5433
|
}
|
|
5434
|
+
function groupKeyOutputName(params) {
|
|
5435
|
+
const { dialect, statement, column } = params;
|
|
5436
|
+
if (column.groupKey === undefined)
|
|
5437
|
+
return;
|
|
5438
|
+
const text = quoteCol(dialect, column);
|
|
5439
|
+
const projected = statement.columns.find((candidate) => candidate.groupKey !== undefined && candidate.transform === undefined && candidate.alias !== undefined && quoteCol(dialect, candidate) === text);
|
|
5440
|
+
return projected?.alias;
|
|
5441
|
+
}
|
|
4855
5442
|
function renderOrderTerm(params) {
|
|
4856
|
-
const { dialect, term, collector } = params;
|
|
5443
|
+
const { dialect, term, collector, nullsOutputName } = params;
|
|
4857
5444
|
if ("agg" in term) {
|
|
4858
|
-
return `${renderAggRef(dialect, term.agg)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
|
|
5445
|
+
return `${renderAggRef(dialect, term.agg, collector)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
|
|
4859
5446
|
}
|
|
4860
5447
|
const expression = "select" in term ? () => renderScalarSubquery({ dialect, subquery: term, collector }) : () => quoteCol(dialect, term.column);
|
|
4861
5448
|
const direction = term.direction === "asc" ? "ASC" : "DESC";
|
|
@@ -4868,7 +5455,7 @@ function renderOrderTerm(params) {
|
|
|
4868
5455
|
if (term.nulls === "first" === (term.direction === "asc")) {
|
|
4869
5456
|
return `${expression()} ${direction}`;
|
|
4870
5457
|
}
|
|
4871
|
-
const tested = expression();
|
|
5458
|
+
const tested = nullsOutputName === undefined ? expression() : dialect.quoteIdent({ name: nullsOutputName });
|
|
4872
5459
|
const nullsTerm = term.nulls === "first" ? `${tested} IS NULL DESC` : `${tested} IS NULL`;
|
|
4873
5460
|
return `${nullsTerm}, ${expression()} ${direction}`;
|
|
4874
5461
|
}
|
|
@@ -4885,11 +5472,11 @@ function renderPositionalRow(params) {
|
|
|
4885
5472
|
const position = join.select.rowNumber;
|
|
4886
5473
|
const positioned = position !== undefined && json.order === "positionSlot" && join.agg === "jsonArray" && position.partitionBy.length === 0 && position.orderBy.length > 0;
|
|
4887
5474
|
if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || position !== undefined && !positioned) {
|
|
4888
|
-
throw
|
|
5475
|
+
throw internalError5({ message: "positional lateral rows require an explicit column-only projection" });
|
|
4889
5476
|
}
|
|
4890
5477
|
const names = [...join.select.columns.map((column) => projectionOutputName({ column })), ...positioned ? [position.alias] : []];
|
|
4891
5478
|
if (new Set(names).size !== names.length) {
|
|
4892
|
-
throw
|
|
5479
|
+
throw internalError5({ message: "positional lateral projection has duplicate output names" });
|
|
4893
5480
|
}
|
|
4894
5481
|
const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
|
|
4895
5482
|
const chunks = [];
|
|
@@ -4897,14 +5484,14 @@ function renderPositionalRow(params) {
|
|
|
4897
5484
|
chunks.push(`${json.rowArray}(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
|
|
4898
5485
|
}
|
|
4899
5486
|
if (chunks.length > JSON_TRANSPORT.chunkSize) {
|
|
4900
|
-
throw
|
|
5487
|
+
throw internalError5({ message: "positional lateral projection exceeds the transport width limit" });
|
|
4901
5488
|
}
|
|
4902
5489
|
return chunks.length === 1 ? chunks[0] : `${json.rowArray}(${chunks.join(", ")})`;
|
|
4903
5490
|
}
|
|
4904
5491
|
function lateralJsonOf(params) {
|
|
4905
5492
|
const json = params.dialect.lateralJson;
|
|
4906
5493
|
if (json === null)
|
|
4907
|
-
throw
|
|
5494
|
+
throw internalError5({ message: "lateral JSON rendering on a dialect without LATERAL" });
|
|
4908
5495
|
return json;
|
|
4909
5496
|
}
|
|
4910
5497
|
function renderLateralJoin(params) {
|
|
@@ -4912,7 +5499,7 @@ function renderLateralJoin(params) {
|
|
|
4912
5499
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
4913
5500
|
if (join.agg === "count") {
|
|
4914
5501
|
if (join.rowFormat === "positional" || join.select.columns.length !== 0 || join.select.selectExpr?.kind !== "countAll" || join.select.selectExpr.alias !== join.columnAlias || join.select.groupBy !== undefined || join.select.having !== undefined || join.select.limit !== undefined || join.select.offset !== undefined || join.select.distinctOn !== undefined || join.select.distinct !== undefined) {
|
|
4915
|
-
throw
|
|
5502
|
+
throw internalError5({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
|
|
4916
5503
|
}
|
|
4917
5504
|
return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
|
|
4918
5505
|
}
|
|
@@ -4924,12 +5511,14 @@ function renderLateralJoin(params) {
|
|
|
4924
5511
|
const projected = new Set(join.select.columns.filter((column) => column.transform === undefined && column.castReal !== true).map((column) => column.name).concat((join.select.scalarSubqueries ?? []).map((subquery) => subquery.alias)));
|
|
4925
5512
|
const terms = join.select.orderBy;
|
|
4926
5513
|
const columnTerms = terms.filter((term) => ("column" in term));
|
|
4927
|
-
|
|
5514
|
+
const subRef = (column) => ({ table: "__sub", name: column.name, ...column.castJsonb === undefined ? {} : { castJsonb: column.castJsonb } });
|
|
5515
|
+
const readable = (column) => column.coalesce === undefined ? projected.has(column.name) : column.coalesce.every((member) => projected.has(member.name));
|
|
5516
|
+
if (columnTerms.length === terms.length && columnTerms.every((term) => readable(term.column))) {
|
|
4928
5517
|
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
4929
5518
|
dialect,
|
|
4930
5519
|
term: {
|
|
4931
5520
|
...term,
|
|
4932
|
-
column:
|
|
5521
|
+
column: term.column.coalesce === undefined ? subRef(term.column) : { ...term.column, coalesce: term.column.coalesce.map(subRef) }
|
|
4933
5522
|
},
|
|
4934
5523
|
collector
|
|
4935
5524
|
}));
|
|
@@ -4937,7 +5526,7 @@ function renderLateralJoin(params) {
|
|
|
4937
5526
|
}
|
|
4938
5527
|
}
|
|
4939
5528
|
if (join.rowFormat !== "positional" && json.order !== "inAggregate") {
|
|
4940
|
-
throw
|
|
5529
|
+
throw unsupportedCapability29({ dialect: dialect.name, feature: "object-format lateral rows", hint: 'lateral relation rows render positional (rowFormat "positional") on this dialect' });
|
|
4941
5530
|
}
|
|
4942
5531
|
const rowJson = join.rowFormat === "positional" ? renderPositionalRow({ dialect, join, subAlias }) : `to_json(${subAlias}.*)`;
|
|
4943
5532
|
const projection = join.agg === "jsonArray" ? `COALESCE(${json.arrayAgg}(${rowJson}${aggOrder}), ${json.emptyArray}) AS ${columnAlias}` : `${rowJson} AS ${columnAlias}`;
|
|
@@ -4948,7 +5537,7 @@ function renderCountSubquery(params) {
|
|
|
4948
5537
|
const { dialect, subquery, collector } = params;
|
|
4949
5538
|
const select = subquery.select;
|
|
4950
5539
|
if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined || select.scalarSubqueries !== undefined) {
|
|
4951
|
-
throw
|
|
5540
|
+
throw internalError5({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
4952
5541
|
}
|
|
4953
5542
|
const inner = renderSelect({ dialect, statement: select, collector });
|
|
4954
5543
|
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
@@ -4966,7 +5555,10 @@ function applyReturning(params) {
|
|
|
4966
5555
|
if (!dialect.capabilities.returning) {
|
|
4967
5556
|
const capture = requested.find(isCaptureReturningColumn);
|
|
4968
5557
|
if (capture !== undefined)
|
|
4969
|
-
renderCaptureReturning({ dialect, column: capture });
|
|
5558
|
+
renderCaptureReturning({ dialect, column: capture, collector: params.collector });
|
|
5559
|
+
const other = requested.find(isOtherRowReturningColumn);
|
|
5560
|
+
if (other !== undefined)
|
|
5561
|
+
renderOtherRowReturning({ dialect, column: other });
|
|
4970
5562
|
return { suffix: "", status: "omitted" };
|
|
4971
5563
|
}
|
|
4972
5564
|
const read = (name) => params.qualify === undefined ? name : `${params.qualify}.${name}`;
|
|
@@ -4974,7 +5566,9 @@ function applyReturning(params) {
|
|
|
4974
5566
|
if (typeof column === "string")
|
|
4975
5567
|
return read(dialect.quoteIdent({ name: column }));
|
|
4976
5568
|
if ("capture" in column)
|
|
4977
|
-
return renderCaptureReturning({ dialect, column });
|
|
5569
|
+
return renderCaptureReturning({ dialect, column, collector: params.collector });
|
|
5570
|
+
if ("from" in column)
|
|
5571
|
+
return renderOtherRowReturning({ dialect, column });
|
|
4978
5572
|
if ("transform" in column) {
|
|
4979
5573
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4980
5574
|
return `${dialect.jsonTextProjection({ expression: read(name) })} AS ${name}`;
|
|
@@ -4987,40 +5581,88 @@ function applyReturning(params) {
|
|
|
4987
5581
|
function renderCaptureReturning(params) {
|
|
4988
5582
|
const { dialect, column } = params;
|
|
4989
5583
|
if (!dialect.capabilities.rowCapture) {
|
|
4990
|
-
throw
|
|
5584
|
+
throw unsupportedCapability29({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
|
|
4991
5585
|
}
|
|
4992
|
-
const name = dialect.quoteIdent({ name: column.name });
|
|
4993
5586
|
const alias = dialect.quoteIdent({ name: column.alias });
|
|
5587
|
+
if (column.capture === "cascade") {
|
|
5588
|
+
if (params.collector === undefined)
|
|
5589
|
+
throw internalError5({ message: `capture cascade column "${column.alias}" rendered without a parameter collector` });
|
|
5590
|
+
const child = dialect.quoteIdent({ name: CAPTURE_CHILD_ALIAS });
|
|
5591
|
+
const match = column.cascade.on.map((pair) => `${child}.${dialect.quoteIdent({ name: pair.child })} = old.${dialect.quoteIdent({ name: pair.parent })}`).join(" AND ");
|
|
5592
|
+
return `${renderCascadeImages({ dialect, cascade: column.cascade, match, collector: params.collector })} AS ${alias}`;
|
|
5593
|
+
}
|
|
5594
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
4994
5595
|
if (column.capture === "inserted")
|
|
4995
5596
|
return `(old.${name} IS NULL) AS ${alias}`;
|
|
4996
|
-
|
|
5597
|
+
if (column.capture === "key")
|
|
5598
|
+
return `old.${name} AS ${alias}`;
|
|
5599
|
+
return `${renderCaptureCell({ dialect, row: column.capture, cell: column })} AS ${alias}`;
|
|
5600
|
+
}
|
|
5601
|
+
var CAPTURE_CHILD_ALIAS = "__vibe_c";
|
|
5602
|
+
var CAPTURE_HOP_ALIAS = "__vibe_h";
|
|
5603
|
+
function renderCaptureCell(params) {
|
|
5604
|
+
const { dialect, row, cell } = params;
|
|
5605
|
+
const name = dialect.quoteIdent({ name: cell.name });
|
|
5606
|
+
if (cell.hop === undefined)
|
|
5607
|
+
return textProjection({ dialect, expression: `${row}.${name}` });
|
|
5608
|
+
if (cell.hop.on.length === 0)
|
|
5609
|
+
throw internalError5({ message: `capture hop to "${cell.hop.table.name}" needs at least one key column` });
|
|
5610
|
+
const related = dialect.quoteIdent({ name: CAPTURE_HOP_ALIAS });
|
|
5611
|
+
const match = cell.hop.on.map((pair) => `${related}.${dialect.quoteIdent({ name: pair.target })} = ${row}.${dialect.quoteIdent({ name: pair.source })}`).join(" AND ");
|
|
5612
|
+
return `(SELECT ${textProjection({ dialect, expression: `${related}.${name}` })} FROM ${renderTable(dialect, cell.hop.table)} AS ${related} WHERE ${match})`;
|
|
5613
|
+
}
|
|
5614
|
+
function renderCascadeImages(params) {
|
|
5615
|
+
const { dialect, cascade, match, collector } = params;
|
|
5616
|
+
if (cascade.cells.length === 0 || cascade.on.length === 0)
|
|
5617
|
+
throw internalError5({ message: `capture cascade into "${cascade.table.name}" needs at least one cell and one key column` });
|
|
5618
|
+
if (!Number.isSafeInteger(cascade.limit) || cascade.limit < 1)
|
|
5619
|
+
throw internalError5({ message: `capture cascade into "${cascade.table.name}" needs a positive integer limit` });
|
|
5620
|
+
const child = dialect.quoteIdent({ name: CAPTURE_CHILD_ALIAS });
|
|
5621
|
+
const item = dialect.quoteIdent({ name: "__vibe_i" });
|
|
5622
|
+
const cells = cascade.cells.map((cell) => renderCaptureCell({ dialect, row: child, cell })).join(", ");
|
|
5623
|
+
return `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${cells}] AS ${item} FROM ${renderTable(dialect, cascade.table)} AS ${child} WHERE ${match} LIMIT ${collector.add(cascade.limit)}) AS ${dialect.quoteIdent({ name: "__vibe_d" })})`;
|
|
5624
|
+
}
|
|
5625
|
+
function renderOtherRowReturning(params) {
|
|
5626
|
+
const { dialect, column } = params;
|
|
5627
|
+
const fromOld = column.from === "old";
|
|
5628
|
+
if (fromOld ? !dialect.capabilities.rowCapture : !dialect.capabilities.returningReadsJoinedSource) {
|
|
5629
|
+
throw unsupportedCapability29({
|
|
5630
|
+
dialect: dialect.name,
|
|
5631
|
+
feature: fromOld ? "RETURNING old.\u2026 (the row before the write)" : "RETURNING a joined source's columns",
|
|
5632
|
+
hint: fromOld ? "needs PostgreSQL 18 or later" : "read the row inside the write's transaction first, then write it"
|
|
5633
|
+
});
|
|
5634
|
+
}
|
|
5635
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
5636
|
+
const expression = column.from === "old" ? `old.${name}` : `${dialect.quoteIdent({ name: column.from.source })}.${name}`;
|
|
5637
|
+
const projected = column.projection === "castText" ? textProjection({ dialect, expression }) : column.projection === "jsonText" ? dialect.jsonTextProjection({ expression }) : expression;
|
|
5638
|
+
return `${projected} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
4997
5639
|
}
|
|
4998
5640
|
function renderKeyTableJoin(params) {
|
|
4999
5641
|
const { dialect, join, collector } = params;
|
|
5000
5642
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
5001
5643
|
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
5002
5644
|
if (!admitted) {
|
|
5003
|
-
throw
|
|
5645
|
+
throw unsupportedCapability29({
|
|
5004
5646
|
dialect: dialect.name,
|
|
5005
5647
|
feature: "relation key table",
|
|
5006
5648
|
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
5007
5649
|
});
|
|
5008
5650
|
}
|
|
5009
5651
|
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
5010
|
-
throw
|
|
5652
|
+
throw internalError5({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
5011
5653
|
}
|
|
5012
5654
|
if (join.rows.length === 0)
|
|
5013
|
-
throw
|
|
5655
|
+
throw internalError5({ message: "key table needs at least one key row" });
|
|
5014
5656
|
for (const member of join.members) {
|
|
5015
5657
|
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
5016
|
-
throw
|
|
5658
|
+
throw internalError5({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
5017
5659
|
}
|
|
5018
5660
|
}
|
|
5019
5661
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
5020
5662
|
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
5021
5663
|
const rows = join.rows.map((row, ordinal) => {
|
|
5022
5664
|
if (row.length !== join.members.length) {
|
|
5023
|
-
throw
|
|
5665
|
+
throw internalError5({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
5024
5666
|
}
|
|
5025
5667
|
const cells = row.map((value, index) => {
|
|
5026
5668
|
const placeholder = collector.add(value);
|
|
@@ -5050,10 +5692,10 @@ function renderKeyTableJoin(params) {
|
|
|
5050
5692
|
function renderLeftJoin(params) {
|
|
5051
5693
|
const { dialect, join, collector } = params;
|
|
5052
5694
|
if (join.on.length === 0) {
|
|
5053
|
-
throw
|
|
5695
|
+
throw internalError5({ message: "left join needs at least one ON pair" });
|
|
5054
5696
|
}
|
|
5055
5697
|
if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
|
|
5056
|
-
throw
|
|
5698
|
+
throw internalError5({ message: "a left join's derived select must alias every projected column" });
|
|
5057
5699
|
}
|
|
5058
5700
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
5059
5701
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
@@ -5062,10 +5704,10 @@ function renderLeftJoin(params) {
|
|
|
5062
5704
|
function renderSelect(params) {
|
|
5063
5705
|
const { dialect, statement, collector } = params;
|
|
5064
5706
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
5065
|
-
throw
|
|
5707
|
+
throw internalError5({ message: "only countAll can combine with a column list" });
|
|
5066
5708
|
}
|
|
5067
5709
|
if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
|
|
5068
|
-
throw
|
|
5710
|
+
throw internalError5({ message: "selectExpr and aggregates cannot combine" });
|
|
5069
5711
|
}
|
|
5070
5712
|
const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
|
|
5071
5713
|
if (statement.selectExpr !== undefined) {
|
|
@@ -5073,11 +5715,15 @@ function renderSelect(params) {
|
|
|
5073
5715
|
}
|
|
5074
5716
|
if (statement.aggregates !== undefined) {
|
|
5075
5717
|
for (const selection of statement.aggregates) {
|
|
5076
|
-
const expression = renderAggRef(dialect, selection.agg);
|
|
5718
|
+
const expression = renderAggRef(dialect, selection.agg, collector);
|
|
5077
5719
|
const projected = selection.castText === true ? textProjection({ dialect, expression }) : expression;
|
|
5078
5720
|
columnList.push(`${projected} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
5079
5721
|
}
|
|
5080
5722
|
}
|
|
5723
|
+
const groupKeys = statement.groupBy?.map((column) => quoteCol(dialect, column)) ?? [];
|
|
5724
|
+
if (statement.groupingSets !== undefined) {
|
|
5725
|
+
columnList.push(`${renderGroupingSetIndex({ keys: groupKeys, grouping: statement.groupingSets })} AS ${dialect.quoteIdent({ name: statement.groupingSets.indexAlias })}`);
|
|
5726
|
+
}
|
|
5081
5727
|
if (statement.countSubqueries !== undefined) {
|
|
5082
5728
|
for (const subquery of statement.countSubqueries) {
|
|
5083
5729
|
columnList.push(renderCountSubquery({ dialect, subquery, collector }));
|
|
@@ -5091,7 +5737,7 @@ function renderSelect(params) {
|
|
|
5091
5737
|
if (statement.rowNumber !== undefined) {
|
|
5092
5738
|
const rowNumber = statement.rowNumber;
|
|
5093
5739
|
if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
|
|
5094
|
-
throw
|
|
5740
|
+
throw internalError5({ message: "rowNumber needs a partition column or an order" });
|
|
5095
5741
|
}
|
|
5096
5742
|
const partition = rowNumber.partitionBy.length === 0 ? "" : `PARTITION BY ${rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
|
|
5097
5743
|
const order = rowNumber.orderBy.length === 0 ? "" : `${partition === "" ? "" : " "}ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
@@ -5099,11 +5745,11 @@ function renderSelect(params) {
|
|
|
5099
5745
|
}
|
|
5100
5746
|
let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
|
|
5101
5747
|
if (statement.distinct === true && statement.distinctOn !== undefined) {
|
|
5102
|
-
throw
|
|
5748
|
+
throw internalError5({ message: "distinct and distinctOn cannot combine" });
|
|
5103
5749
|
}
|
|
5104
5750
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
5105
5751
|
if (!dialect.capabilities.distinctOn) {
|
|
5106
|
-
throw
|
|
5752
|
+
throw unsupportedCapability29({
|
|
5107
5753
|
dialect: dialect.name,
|
|
5108
5754
|
feature: "SELECT DISTINCT ON",
|
|
5109
5755
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -5119,12 +5765,12 @@ function renderSelect(params) {
|
|
|
5119
5765
|
from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5120
5766
|
} else if (statement.keyPage !== undefined) {
|
|
5121
5767
|
if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
|
|
5122
|
-
throw
|
|
5768
|
+
throw internalError5({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
|
|
5123
5769
|
}
|
|
5124
5770
|
from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5125
5771
|
} else if (statement.fromSelect !== undefined) {
|
|
5126
5772
|
if (statement.table.alias !== undefined) {
|
|
5127
|
-
throw
|
|
5773
|
+
throw internalError5({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
|
|
5128
5774
|
}
|
|
5129
5775
|
const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
|
|
5130
5776
|
from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
@@ -5135,7 +5781,7 @@ function renderSelect(params) {
|
|
|
5135
5781
|
if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
|
|
5136
5782
|
for (const join of statement.innerJoins) {
|
|
5137
5783
|
if (join.on.length === 0) {
|
|
5138
|
-
throw
|
|
5784
|
+
throw internalError5({ message: "inner join needs at least one ON pair" });
|
|
5139
5785
|
}
|
|
5140
5786
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
5141
5787
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
@@ -5146,7 +5792,7 @@ function renderSelect(params) {
|
|
|
5146
5792
|
}
|
|
5147
5793
|
if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
|
|
5148
5794
|
if (statement.rowLock !== undefined) {
|
|
5149
|
-
throw
|
|
5795
|
+
throw internalError5({ message: "a locking read cannot carry a left join \u2014 postgres refuses FOR UPDATE on the nullable side of an outer join" });
|
|
5150
5796
|
}
|
|
5151
5797
|
for (const join of statement.leftJoins) {
|
|
5152
5798
|
text += ` ${renderLeftJoin({ dialect, join, collector })}`;
|
|
@@ -5154,7 +5800,7 @@ function renderSelect(params) {
|
|
|
5154
5800
|
}
|
|
5155
5801
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
5156
5802
|
if (!dialect.capabilities.lateralJoin || dialect.lateralJson === null) {
|
|
5157
|
-
throw
|
|
5803
|
+
throw unsupportedCapability29({
|
|
5158
5804
|
dialect: dialect.name,
|
|
5159
5805
|
feature: "LATERAL JOIN relation loading",
|
|
5160
5806
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -5168,13 +5814,25 @@ function renderSelect(params) {
|
|
|
5168
5814
|
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5169
5815
|
}
|
|
5170
5816
|
if (statement.groupBy !== undefined && statement.groupBy.length > 0) {
|
|
5171
|
-
text += ` GROUP BY ${statement.
|
|
5817
|
+
text += ` GROUP BY ${statement.groupingSets === undefined ? groupKeys.join(", ") : renderGroupingSets({ dialect, keys: groupKeys, grouping: statement.groupingSets })}`;
|
|
5818
|
+
} else if (statement.groupingSets !== undefined) {
|
|
5819
|
+
throw internalError5({ message: "grouping sets need groupBy columns" });
|
|
5172
5820
|
}
|
|
5173
5821
|
if (statement.having !== undefined) {
|
|
5174
5822
|
text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
|
|
5175
5823
|
}
|
|
5176
5824
|
if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
|
|
5177
|
-
const terms = statement.orderBy.map((term) =>
|
|
5825
|
+
const terms = statement.orderBy.map((term) => {
|
|
5826
|
+
if (!("column" in term))
|
|
5827
|
+
return renderOrderTerm({ dialect, term, collector });
|
|
5828
|
+
const nullsOutputName = term.nulls === undefined || dialect.orderByNullsMode === "native" ? undefined : groupKeyOutputName({ dialect, statement, column: term.column });
|
|
5829
|
+
return renderOrderTerm({
|
|
5830
|
+
dialect,
|
|
5831
|
+
term: { ...term, column: typedOrderRef({ statement, column: term.column }) },
|
|
5832
|
+
collector,
|
|
5833
|
+
...nullsOutputName === undefined ? {} : { nullsOutputName }
|
|
5834
|
+
});
|
|
5835
|
+
});
|
|
5178
5836
|
text += ` ORDER BY ${terms.join(", ")}`;
|
|
5179
5837
|
}
|
|
5180
5838
|
if (statement.limit !== undefined) {
|
|
@@ -5191,10 +5849,10 @@ function renderSelect(params) {
|
|
|
5191
5849
|
if (statement.rowLock !== undefined) {
|
|
5192
5850
|
const target = statement.table.alias ?? statement.table.name;
|
|
5193
5851
|
if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
|
|
5194
|
-
throw
|
|
5852
|
+
throw internalError5({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
|
|
5195
5853
|
}
|
|
5196
5854
|
if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
|
|
5197
|
-
throw
|
|
5855
|
+
throw internalError5({ message: "a row lock's `of` list cannot name a derived table" });
|
|
5198
5856
|
}
|
|
5199
5857
|
const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
|
|
5200
5858
|
if (clause !== null)
|
|
@@ -5210,7 +5868,7 @@ function unnestElement(params) {
|
|
|
5210
5868
|
return null;
|
|
5211
5869
|
if (isJsonParamCell(value)) {
|
|
5212
5870
|
if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
|
|
5213
|
-
throw
|
|
5871
|
+
throw internalError5({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
|
|
5214
5872
|
}
|
|
5215
5873
|
return value.text;
|
|
5216
5874
|
}
|
|
@@ -5226,24 +5884,24 @@ function unnestElement(params) {
|
|
|
5226
5884
|
return value;
|
|
5227
5885
|
}
|
|
5228
5886
|
}
|
|
5229
|
-
throw
|
|
5887
|
+
throw internalError5({ message: `unnest insert column "${column}" got a cell that is not a plain value (DEFAULT, the database clock, a document) \u2014 such rows keep VALUES` });
|
|
5230
5888
|
}
|
|
5231
5889
|
function renderUnnestRows(params) {
|
|
5232
5890
|
const { dialect, statement, members, collector } = params;
|
|
5233
5891
|
if (dialect.bulkInsertForm !== "unnest") {
|
|
5234
|
-
throw
|
|
5892
|
+
throw unsupportedCapability29({
|
|
5235
5893
|
dialect: dialect.name,
|
|
5236
5894
|
feature: "a bulk insert bound as typed arrays (INSERT \u2026 SELECT FROM unnest)",
|
|
5237
5895
|
hint: "this dialect binds createMany rows as VALUES"
|
|
5238
5896
|
});
|
|
5239
5897
|
}
|
|
5240
5898
|
if (statement.columns.length === 0)
|
|
5241
|
-
throw
|
|
5899
|
+
throw internalError5({ message: "an unnest insert needs at least one column" });
|
|
5242
5900
|
if (members.length !== statement.columns.length) {
|
|
5243
|
-
throw
|
|
5901
|
+
throw internalError5({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
|
|
5244
5902
|
}
|
|
5245
5903
|
if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
|
|
5246
|
-
throw
|
|
5904
|
+
throw internalError5({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
|
|
5247
5905
|
}
|
|
5248
5906
|
const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
|
|
5249
5907
|
const arrays = members.map((member, index) => {
|
|
@@ -5259,14 +5917,14 @@ function renderUnnestRows(params) {
|
|
|
5259
5917
|
function renderInsert(params) {
|
|
5260
5918
|
const { dialect, statement, collector } = params;
|
|
5261
5919
|
if (statement.rows.length === 0) {
|
|
5262
|
-
throw
|
|
5920
|
+
throw internalError5({ message: "insert requires at least one row" });
|
|
5263
5921
|
}
|
|
5264
5922
|
const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
|
|
5265
5923
|
if (defaultValues && statement.rows.length !== 1) {
|
|
5266
|
-
throw
|
|
5924
|
+
throw internalError5({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
|
|
5267
5925
|
}
|
|
5268
5926
|
if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
|
|
5269
|
-
throw
|
|
5927
|
+
throw unsupportedCapability29({
|
|
5270
5928
|
dialect: dialect.name,
|
|
5271
5929
|
feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
|
|
5272
5930
|
hint: "supply at least one column value, or drop the conflict clause"
|
|
@@ -5274,7 +5932,7 @@ function renderInsert(params) {
|
|
|
5274
5932
|
}
|
|
5275
5933
|
for (const row of statement.rows) {
|
|
5276
5934
|
if (row.length !== statement.columns.length) {
|
|
5277
|
-
throw
|
|
5935
|
+
throw internalError5({
|
|
5278
5936
|
message: `insert row has ${row.length} values but ${statement.columns.length} columns`
|
|
5279
5937
|
});
|
|
5280
5938
|
}
|
|
@@ -5292,7 +5950,7 @@ function renderInsert(params) {
|
|
|
5292
5950
|
if (!isSqlDefaultCell(value))
|
|
5293
5951
|
return collector.add(value);
|
|
5294
5952
|
if (!dialect.rowDefaultKeyword) {
|
|
5295
|
-
throw
|
|
5953
|
+
throw internalError5({
|
|
5296
5954
|
message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
|
|
5297
5955
|
});
|
|
5298
5956
|
}
|
|
@@ -5308,7 +5966,7 @@ function renderInsert(params) {
|
|
|
5308
5966
|
let target = "";
|
|
5309
5967
|
if (onConflict.constraint !== undefined) {
|
|
5310
5968
|
if (dialect.name !== "postgres") {
|
|
5311
|
-
throw
|
|
5969
|
+
throw unsupportedCapability29({
|
|
5312
5970
|
dialect: dialect.name,
|
|
5313
5971
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
5314
5972
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -5324,7 +5982,7 @@ function renderInsert(params) {
|
|
|
5324
5982
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
5325
5983
|
} else {
|
|
5326
5984
|
if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
5327
|
-
throw
|
|
5985
|
+
throw unsupportedCapability29({
|
|
5328
5986
|
dialect: dialect.name,
|
|
5329
5987
|
feature: "a refined conflict target (constraint / partial-index predicate) on ON DUPLICATE KEY UPDATE",
|
|
5330
5988
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -5332,36 +5990,36 @@ function renderInsert(params) {
|
|
|
5332
5990
|
}
|
|
5333
5991
|
const keepName = onConflict.keepColumn ?? statement.columns[0];
|
|
5334
5992
|
if (keepName === undefined) {
|
|
5335
|
-
throw
|
|
5993
|
+
throw internalError5({ message: "a skip-duplicates insert of an all-default row must name its keepColumn" });
|
|
5336
5994
|
}
|
|
5337
5995
|
const keep = dialect.quoteIdent({ name: keepName });
|
|
5338
5996
|
const current = `${dialect.quoteIdent({ name: statement.table.name })}.${keep}`;
|
|
5339
5997
|
const flag = onConflict.duplicateFlag;
|
|
5340
5998
|
if (flag !== undefined && !/^\d{1,20}$/.test(flag)) {
|
|
5341
|
-
throw
|
|
5999
|
+
throw internalError5({ message: `duplicateFlag must be a decimal literal, got ${JSON.stringify(flag)}` });
|
|
5342
6000
|
}
|
|
5343
6001
|
text += flag === undefined ? ` ON DUPLICATE KEY UPDATE ${keep} = ${current}` : ` ON DUPLICATE KEY UPDATE ${keep} = IF(LAST_INSERT_ID(${flag}), ${current}, ${current})`;
|
|
5344
6002
|
}
|
|
5345
6003
|
} else {
|
|
5346
6004
|
const updates = Object.entries(onConflict.update);
|
|
5347
6005
|
if (updates.length === 0) {
|
|
5348
|
-
throw
|
|
6006
|
+
throw internalError5({ message: "upsert update must set at least one column" });
|
|
5349
6007
|
}
|
|
5350
6008
|
const selfRef = dialect.quoteIdent({ name: statement.table.name });
|
|
5351
6009
|
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
|
|
5352
6010
|
if (dialect.upsertForm === "onConflict") {
|
|
5353
6011
|
if (onConflict.lastInsertIdColumn !== undefined) {
|
|
5354
|
-
throw
|
|
6012
|
+
throw internalError5({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
5355
6013
|
}
|
|
5356
6014
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
5357
|
-
throw
|
|
6015
|
+
throw unsupportedCapability29({
|
|
5358
6016
|
dialect: dialect.name,
|
|
5359
6017
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
5360
6018
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
5361
6019
|
});
|
|
5362
6020
|
}
|
|
5363
6021
|
if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
|
|
5364
|
-
throw
|
|
6022
|
+
throw internalError5({
|
|
5365
6023
|
message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
|
|
5366
6024
|
});
|
|
5367
6025
|
}
|
|
@@ -5370,7 +6028,7 @@ function renderInsert(params) {
|
|
|
5370
6028
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
5371
6029
|
if (onConflict.updateWhere !== undefined) {
|
|
5372
6030
|
if (!dialect.capabilities.conflictUpdateWhere) {
|
|
5373
|
-
throw
|
|
6031
|
+
throw unsupportedCapability29({
|
|
5374
6032
|
dialect: dialect.name,
|
|
5375
6033
|
feature: "a guarded conflict update (ON CONFLICT \u2026 DO UPDATE \u2026 WHERE)",
|
|
5376
6034
|
hint: "run the guarded upsert as a find-then-write in a transaction instead"
|
|
@@ -5380,21 +6038,21 @@ function renderInsert(params) {
|
|
|
5380
6038
|
}
|
|
5381
6039
|
} else {
|
|
5382
6040
|
if (onConflict.updateWhere !== undefined) {
|
|
5383
|
-
throw
|
|
6041
|
+
throw unsupportedCapability29({
|
|
5384
6042
|
dialect: dialect.name,
|
|
5385
6043
|
feature: "a guarded conflict update (ON DUPLICATE KEY UPDATE has no WHERE)",
|
|
5386
6044
|
hint: "run the guarded upsert as a find-then-write in a transaction instead \u2014 the runtime does this when the dialect lacks conflictUpdateWhere"
|
|
5387
6045
|
});
|
|
5388
6046
|
}
|
|
5389
6047
|
if (onConflict.constraint !== undefined) {
|
|
5390
|
-
throw
|
|
6048
|
+
throw unsupportedCapability29({
|
|
5391
6049
|
dialect: dialect.name,
|
|
5392
6050
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
5393
6051
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
5394
6052
|
});
|
|
5395
6053
|
}
|
|
5396
6054
|
if (onConflict.conflictWhere !== undefined) {
|
|
5397
|
-
throw
|
|
6055
|
+
throw unsupportedCapability29({
|
|
5398
6056
|
dialect: dialect.name,
|
|
5399
6057
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
5400
6058
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -5405,25 +6063,35 @@ function renderInsert(params) {
|
|
|
5405
6063
|
}
|
|
5406
6064
|
}
|
|
5407
6065
|
}
|
|
5408
|
-
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
6066
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector });
|
|
5409
6067
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5410
6068
|
}
|
|
5411
6069
|
function renderUpdate(params) {
|
|
5412
6070
|
const { dialect, statement, collector } = params;
|
|
5413
6071
|
const assignments = Object.entries(statement.set);
|
|
5414
6072
|
if (assignments.length === 0) {
|
|
5415
|
-
throw
|
|
6073
|
+
throw internalError5({ message: "update must set at least one column" });
|
|
5416
6074
|
}
|
|
5417
6075
|
const source = statement.from;
|
|
5418
6076
|
if (source !== undefined) {
|
|
5419
6077
|
if (statement.alias === undefined || statement.alias === source.alias)
|
|
5420
|
-
throw
|
|
6078
|
+
throw internalError5({ message: "UPDATE \u2026 FROM needs distinct target and source aliases" });
|
|
5421
6079
|
const target = dialect.quoteIdent({ name: statement.alias });
|
|
5422
6080
|
const joined = dialect.quoteIdent({ name: source.alias });
|
|
5423
6081
|
const assign = () => renderAssignments({ dialect, entries: assignments, collector, selfRef: target, source: joined, ...dialect.name === "mysql" ? { qualifyWrite: target } : {}, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
5424
6082
|
const where = () => statement.where === undefined ? "" : ` AND ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
6083
|
+
if ("select" in source) {
|
|
6084
|
+
if (!dialect.capabilities.returningReadsJoinedSource) {
|
|
6085
|
+
throw unsupportedCapability29({ dialect: dialect.name, feature: "UPDATE \u2026 FROM (SELECT \u2026) with RETURNING of the joined source", hint: "read the row inside the write's transaction first, then write it" });
|
|
6086
|
+
}
|
|
6087
|
+
const set = assign();
|
|
6088
|
+
const joinedSelect = renderSelect({ dialect, statement: source.select, collector }).text;
|
|
6089
|
+
const text = `UPDATE ${renderTable(dialect, statement.table)} AS ${target} SET ${set} FROM (${joinedSelect}) AS ${joined} WHERE ${renderExpr({ dialect, expr: source.on, collector })}${where()}`;
|
|
6090
|
+
const returning = applyReturning({ dialect, requested: statement.returning, qualify: target });
|
|
6091
|
+
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
6092
|
+
}
|
|
5425
6093
|
const text = dialect.name === "mysql" ? `UPDATE ${renderTable(dialect, statement.table)} AS ${target} JOIN ${renderTable(dialect, source.table)} AS ${joined} ON ${renderExpr({ dialect, expr: source.on, collector })} SET ${assign()}${statement.where === undefined ? "" : ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`}` : `UPDATE ${renderTable(dialect, statement.table)} AS ${target} SET ${assign()} FROM ${renderTable(dialect, source.table)} AS ${joined} WHERE ${renderExpr({ dialect, expr: source.on, collector })}${where()}`;
|
|
5426
|
-
const returning = applyReturning({ dialect, requested: statement.returning, ...dialect.name === "postgres" ? { qualify: target } : {} });
|
|
6094
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector, ...dialect.name === "postgres" ? { qualify: target } : {} });
|
|
5427
6095
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5428
6096
|
}
|
|
5429
6097
|
const setSql = renderAssignments({ dialect, entries: assignments, collector, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
@@ -5431,7 +6099,7 @@ function renderUpdate(params) {
|
|
|
5431
6099
|
if (statement.where !== undefined) {
|
|
5432
6100
|
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5433
6101
|
}
|
|
5434
|
-
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
6102
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector });
|
|
5435
6103
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5436
6104
|
}
|
|
5437
6105
|
function renderIncomingRowColumn(params) {
|
|
@@ -5447,23 +6115,23 @@ function renderAssignments(params) {
|
|
|
5447
6115
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
5448
6116
|
if (isSourceColumnRef(value)) {
|
|
5449
6117
|
if (params.source === undefined)
|
|
5450
|
-
throw
|
|
6118
|
+
throw internalError5({ message: `an UPDATE set value reads the joined source's column ${value.column}, but the statement joins no source` });
|
|
5451
6119
|
return `${column} = ${params.source}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5452
6120
|
}
|
|
5453
6121
|
if (isStepColumnRef(value)) {
|
|
5454
6122
|
if (params.steps === undefined || !params.steps.has(value.step)) {
|
|
5455
|
-
throw
|
|
6123
|
+
throw internalError5({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
|
|
5456
6124
|
}
|
|
5457
6125
|
return `${column} = (SELECT ${dialect.quoteIdent({ name: value.column })} FROM ${dialect.quoteIdent({ name: value.step })})`;
|
|
5458
6126
|
}
|
|
5459
6127
|
if (isSqlFragment(value)) {
|
|
5460
6128
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5461
|
-
throw
|
|
6129
|
+
throw internalError5({ message: "SQL assignment fragments require a conflict update" });
|
|
5462
6130
|
return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
|
|
5463
6131
|
}
|
|
5464
6132
|
if (isExcludedRef(value)) {
|
|
5465
6133
|
if (params.allowExcluded !== true) {
|
|
5466
|
-
throw
|
|
6134
|
+
throw internalError5({
|
|
5467
6135
|
message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
|
|
5468
6136
|
});
|
|
5469
6137
|
}
|
|
@@ -5471,9 +6139,12 @@ function renderAssignments(params) {
|
|
|
5471
6139
|
}
|
|
5472
6140
|
if (isSqlKeepCell(value)) {
|
|
5473
6141
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5474
|
-
throw
|
|
6142
|
+
throw internalError5({ message: "a keep-column cell is only meaningful inside an upsert's update arm" });
|
|
5475
6143
|
return `${column} = ${read}`;
|
|
5476
6144
|
}
|
|
6145
|
+
if (isJsonPatchOperation(value)) {
|
|
6146
|
+
return `${column} = ${renderJsonPatch({ dialect, patch: value, read, collector })}`;
|
|
6147
|
+
}
|
|
5477
6148
|
if (isJsonParamCell(value)) {
|
|
5478
6149
|
return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
5479
6150
|
}
|
|
@@ -5501,7 +6172,7 @@ function renderDelete(params) {
|
|
|
5501
6172
|
if (statement.where !== undefined) {
|
|
5502
6173
|
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5503
6174
|
}
|
|
5504
|
-
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
6175
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector });
|
|
5505
6176
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5506
6177
|
}
|
|
5507
6178
|
var CHAIN_ROWS_ALIAS = "__vibe_rows";
|
|
@@ -5509,19 +6180,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
|
5509
6180
|
function renderChain(params) {
|
|
5510
6181
|
const { dialect, statement, collector } = params;
|
|
5511
6182
|
if (dialect.statementChain === null) {
|
|
5512
|
-
throw
|
|
6183
|
+
throw unsupportedCapability29({
|
|
5513
6184
|
dialect: dialect.name,
|
|
5514
6185
|
feature: "a statement chain (data-modifying WITH steps)",
|
|
5515
6186
|
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
5516
6187
|
});
|
|
5517
6188
|
}
|
|
5518
6189
|
if (statement.steps.length === 0)
|
|
5519
|
-
throw
|
|
6190
|
+
throw internalError5({ message: "a statement chain needs at least one step" });
|
|
5520
6191
|
const defined = new Set;
|
|
5521
6192
|
const steps = [];
|
|
5522
6193
|
for (const step of statement.steps) {
|
|
5523
6194
|
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
5524
|
-
throw
|
|
6195
|
+
throw internalError5({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
5525
6196
|
}
|
|
5526
6197
|
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
5527
6198
|
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
@@ -5530,11 +6201,11 @@ function renderChain(params) {
|
|
|
5530
6201
|
const result = statement.result;
|
|
5531
6202
|
const captures = result.capture ?? [];
|
|
5532
6203
|
if (captures.length > 0 && !dialect.capabilities.rowCapture) {
|
|
5533
|
-
throw
|
|
6204
|
+
throw unsupportedCapability29({ dialect: dialect.name, feature: "row capture (RETURNING old./new.)", hint: "row capture needs PostgreSQL 18 or later; the runtime refuses $transaction({ capture }) on this dialect" });
|
|
5534
6205
|
}
|
|
5535
6206
|
for (const alias of captureAliases({ captures })) {
|
|
5536
6207
|
if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
|
|
5537
|
-
throw
|
|
6208
|
+
throw internalError5({ message: `chain capture alias "${alias}" collides with a result column` });
|
|
5538
6209
|
}
|
|
5539
6210
|
}
|
|
5540
6211
|
if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
|
|
@@ -5542,11 +6213,11 @@ function renderChain(params) {
|
|
|
5542
6213
|
return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
|
|
5543
6214
|
}
|
|
5544
6215
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
5545
|
-
throw
|
|
6216
|
+
throw internalError5({ message: "a chain result needs at least one source step and one column" });
|
|
5546
6217
|
}
|
|
5547
6218
|
for (const name of result.rowsFrom) {
|
|
5548
6219
|
if (!defined.has(name))
|
|
5549
|
-
throw
|
|
6220
|
+
throw internalError5({ message: `chain result reads undefined step "${name}"` });
|
|
5550
6221
|
}
|
|
5551
6222
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
5552
6223
|
const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
|
|
@@ -5557,7 +6228,7 @@ function renderChain(params) {
|
|
|
5557
6228
|
const extra = [];
|
|
5558
6229
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
5559
6230
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
5560
|
-
throw
|
|
6231
|
+
throw internalError5({ message: `chain result alias "${alias}" collides with a result column` });
|
|
5561
6232
|
}
|
|
5562
6233
|
}
|
|
5563
6234
|
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
@@ -5602,6 +6273,8 @@ function captureAliases(params) {
|
|
|
5602
6273
|
if (entry.inserted !== undefined)
|
|
5603
6274
|
out.push(`${entry.alias}_ia`);
|
|
5604
6275
|
}
|
|
6276
|
+
for (const index of (entry.cascades ?? []).keys())
|
|
6277
|
+
out.push(`${entry.alias}_c${index}`);
|
|
5605
6278
|
}
|
|
5606
6279
|
return out;
|
|
5607
6280
|
}
|
|
@@ -5611,7 +6284,7 @@ function renderChainCaptures(params) {
|
|
|
5611
6284
|
const out = [];
|
|
5612
6285
|
for (const entry of captures) {
|
|
5613
6286
|
if (!defined.has(entry.step))
|
|
5614
|
-
throw
|
|
6287
|
+
throw internalError5({ message: `chain capture reads undefined step "${entry.step}"` });
|
|
5615
6288
|
const step = quote(entry.step);
|
|
5616
6289
|
const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
|
|
5617
6290
|
out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
|
|
@@ -5619,23 +6292,36 @@ function renderChainCaptures(params) {
|
|
|
5619
6292
|
out.push(`(SELECT count(*) FROM ${step} WHERE ${marker}) AS ${quote(`${entry.alias}_i`)}`);
|
|
5620
6293
|
const before = entry.before ?? [];
|
|
5621
6294
|
const after = entry.after ?? [];
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
const updatedRows = marker === undefined ? "" : ` WHERE NOT ${marker}`;
|
|
5630
|
-
if (before.length > 0)
|
|
5631
|
-
out.push(`${image(before, updatedRows)} AS ${quote(`${entry.alias}_b`)}`);
|
|
5632
|
-
if (after.length > 0 && (marker === undefined || entry.insertedOnly !== true))
|
|
5633
|
-
out.push(`${image(after, updatedRows)} AS ${quote(`${entry.alias}_a`)}`);
|
|
5634
|
-
if (after.length > 0 && marker !== undefined)
|
|
5635
|
-
out.push(`${image(after, ` WHERE ${marker}`)} AS ${quote(`${entry.alias}_ia`)}`);
|
|
6295
|
+
out.push(...renderImageAggregates({ dialect, entry, step, marker, before, after, collector }));
|
|
6296
|
+
for (const [index, cascade] of (entry.cascades ?? []).entries()) {
|
|
6297
|
+
const keys = quote("__vibe_k");
|
|
6298
|
+
const child = quote(CAPTURE_CHILD_ALIAS);
|
|
6299
|
+
const match = `EXISTS (SELECT 1 FROM ${step} AS ${keys} WHERE ${cascade.on.map((pair) => `${keys}.${quote(pair.parent)} = ${child}.${quote(pair.child)}`).join(" AND ")})`;
|
|
6300
|
+
out.push(`${renderCascadeImages({ dialect, cascade, match, collector })} AS ${quote(`${entry.alias}_c${index}`)}`);
|
|
6301
|
+
}
|
|
5636
6302
|
}
|
|
5637
6303
|
return out;
|
|
5638
6304
|
}
|
|
6305
|
+
function renderImageAggregates(params) {
|
|
6306
|
+
const { dialect, entry, step, marker, before, after, collector } = params;
|
|
6307
|
+
const quote = (name) => dialect.quoteIdent({ name });
|
|
6308
|
+
const out = [];
|
|
6309
|
+
if (before.length === 0 && after.length === 0)
|
|
6310
|
+
return out;
|
|
6311
|
+
if (!Number.isSafeInteger(entry.limit) || entry.limit < 1)
|
|
6312
|
+
throw internalError5({ message: `chain capture "${entry.alias}" needs a positive integer limit` });
|
|
6313
|
+
const limit = collector.add(entry.limit);
|
|
6314
|
+
const item = quote("__vibe_i");
|
|
6315
|
+
const image = (columns, filter) => `(SELECT array_agg(${item})::text FROM (SELECT DISTINCT ARRAY[${columns.map(quote).join(", ")}] AS ${item} FROM ${step}${filter} LIMIT ${limit}) AS ${quote("__vibe_d")})`;
|
|
6316
|
+
const updatedRows = marker === undefined ? "" : ` WHERE NOT ${marker}`;
|
|
6317
|
+
if (before.length > 0)
|
|
6318
|
+
out.push(`${image(before, updatedRows)} AS ${quote(`${entry.alias}_b`)}`);
|
|
6319
|
+
if (after.length > 0 && (marker === undefined || entry.insertedOnly !== true))
|
|
6320
|
+
out.push(`${image(after, updatedRows)} AS ${quote(`${entry.alias}_a`)}`);
|
|
6321
|
+
if (after.length > 0 && marker !== undefined)
|
|
6322
|
+
out.push(`${image(after, ` WHERE ${marker}`)} AS ${quote(`${entry.alias}_ia`)}`);
|
|
6323
|
+
return out;
|
|
6324
|
+
}
|
|
5639
6325
|
function renderSelectCell(params) {
|
|
5640
6326
|
const { dialect, value, collector } = params;
|
|
5641
6327
|
if (isJsonParamCell(value))
|
|
@@ -5643,21 +6329,21 @@ function renderSelectCell(params) {
|
|
|
5643
6329
|
if (isDbNowMarker(value))
|
|
5644
6330
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
5645
6331
|
if (isSqlDefaultCell(value)) {
|
|
5646
|
-
throw
|
|
6332
|
+
throw internalError5({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
5647
6333
|
}
|
|
5648
6334
|
return collector.add(value);
|
|
5649
6335
|
}
|
|
5650
6336
|
function renderRowSource(params) {
|
|
5651
6337
|
const { dialect, source, collector } = params;
|
|
5652
6338
|
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
5653
|
-
throw
|
|
6339
|
+
throw internalError5({ message: "a row source needs at least one column and one row" });
|
|
5654
6340
|
}
|
|
5655
6341
|
const typedBy = renderTable(dialect, source.typedBy);
|
|
5656
6342
|
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
5657
6343
|
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
5658
6344
|
for (const row of source.rows) {
|
|
5659
6345
|
if (row.length !== source.columns.length) {
|
|
5660
|
-
throw
|
|
6346
|
+
throw internalError5({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
5661
6347
|
}
|
|
5662
6348
|
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
5663
6349
|
}
|
|
@@ -5669,12 +6355,12 @@ function renderTallyColumn(params) {
|
|
|
5669
6355
|
const { dialect, result, defined, collector } = params;
|
|
5670
6356
|
const tally = result.tally;
|
|
5671
6357
|
if (tally === undefined)
|
|
5672
|
-
throw
|
|
6358
|
+
throw internalError5({ message: "a chain veto needs a tally column to guard" });
|
|
5673
6359
|
if (tally.steps.length === 0)
|
|
5674
|
-
throw
|
|
6360
|
+
throw internalError5({ message: "a chain tally needs at least one step" });
|
|
5675
6361
|
const step = (name) => {
|
|
5676
6362
|
if (!defined.has(name))
|
|
5677
|
-
throw
|
|
6363
|
+
throw internalError5({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
5678
6364
|
return dialect.quoteIdent({ name });
|
|
5679
6365
|
};
|
|
5680
6366
|
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
@@ -5684,14 +6370,14 @@ function renderTallyColumn(params) {
|
|
|
5684
6370
|
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
5685
6371
|
const arms = vetoes.map((veto) => {
|
|
5686
6372
|
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
5687
|
-
throw
|
|
6373
|
+
throw internalError5({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
5688
6374
|
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
5689
|
-
throw
|
|
6375
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
5690
6376
|
const minRows = veto.minRows ?? 1;
|
|
5691
6377
|
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
5692
|
-
throw
|
|
6378
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
5693
6379
|
if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
|
|
5694
|
-
throw
|
|
6380
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
|
|
5695
6381
|
}
|
|
5696
6382
|
const when = veto.exactRows !== undefined ? `WHEN ${count(veto.step)} <> ${veto.exactRows}` : minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
|
|
5697
6383
|
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
@@ -5701,25 +6387,25 @@ function renderTallyColumn(params) {
|
|
|
5701
6387
|
function renderInsertSelect(params) {
|
|
5702
6388
|
const { dialect, statement, collector } = params;
|
|
5703
6389
|
if (statement.columns.length === 0)
|
|
5704
|
-
throw
|
|
6390
|
+
throw internalError5({ message: "insert requires at least one column" });
|
|
5705
6391
|
if (statement.cells.length !== statement.columns.length) {
|
|
5706
|
-
throw
|
|
6392
|
+
throw internalError5({
|
|
5707
6393
|
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
5708
6394
|
});
|
|
5709
6395
|
}
|
|
5710
6396
|
const from = statement.from ?? [];
|
|
5711
6397
|
for (const name of from) {
|
|
5712
6398
|
if (!params.earlier.has(name))
|
|
5713
|
-
throw
|
|
6399
|
+
throw internalError5({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
5714
6400
|
}
|
|
5715
6401
|
const source = statement.source;
|
|
5716
6402
|
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
5717
|
-
throw
|
|
6403
|
+
throw internalError5({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
5718
6404
|
}
|
|
5719
6405
|
const cells = statement.cells.map((value) => {
|
|
5720
6406
|
if (isStepColumnRef(value)) {
|
|
5721
6407
|
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
5722
|
-
throw
|
|
6408
|
+
throw internalError5({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
5723
6409
|
}
|
|
5724
6410
|
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5725
6411
|
}
|
|
@@ -5738,9 +6424,9 @@ function renderInsertSelect(params) {
|
|
|
5738
6424
|
const conflict = statement.conflictDoNothing;
|
|
5739
6425
|
if (conflict !== undefined) {
|
|
5740
6426
|
if (conflict.columns.length === 0)
|
|
5741
|
-
throw
|
|
6427
|
+
throw internalError5({ message: "an INSERT \u2026 SELECT conflict clause needs at least one column" });
|
|
5742
6428
|
if (dialect.doNothingForm !== "onConflictDoNothing") {
|
|
5743
|
-
throw
|
|
6429
|
+
throw unsupportedCapability29({
|
|
5744
6430
|
dialect: dialect.name,
|
|
5745
6431
|
feature: "ON CONFLICT DO NOTHING on an INSERT \u2026 SELECT chain step",
|
|
5746
6432
|
hint: "gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
@@ -5748,10 +6434,10 @@ function renderInsertSelect(params) {
|
|
|
5748
6434
|
}
|
|
5749
6435
|
text += ` ON CONFLICT (${conflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")}) DO NOTHING`;
|
|
5750
6436
|
}
|
|
5751
|
-
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
6437
|
+
return text + applyReturning({ dialect, requested: statement.returning, collector }).suffix;
|
|
5752
6438
|
}
|
|
5753
6439
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
5754
|
-
import { unsupportedCapability as
|
|
6440
|
+
import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
|
|
5755
6441
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
5756
6442
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
5757
6443
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -5778,13 +6464,13 @@ function parsePostgresTypeName(params) {
|
|
|
5778
6464
|
}
|
|
5779
6465
|
function renderPostgresTypeName(params) {
|
|
5780
6466
|
if (params.dialect.name !== "postgres")
|
|
5781
|
-
throw
|
|
6467
|
+
throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
5782
6468
|
const { dialect, type } = params;
|
|
5783
6469
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
5784
6470
|
}
|
|
5785
6471
|
function renderPostgresTypeIdentity(params) {
|
|
5786
6472
|
if (params.dialect.name !== "postgres")
|
|
5787
|
-
throw
|
|
6473
|
+
throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
5788
6474
|
const collector = new ParamCollector(params.dialect);
|
|
5789
6475
|
const { source } = params;
|
|
5790
6476
|
let text;
|
|
@@ -5797,11 +6483,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
5797
6483
|
return { text, values: collector.values, returning: "none" };
|
|
5798
6484
|
}
|
|
5799
6485
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
5800
|
-
import { unsupportedCapability as
|
|
6486
|
+
import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
|
|
5801
6487
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
5802
6488
|
const { dialect, table, column } = params;
|
|
5803
6489
|
if (dialect.name !== "postgres")
|
|
5804
|
-
throw
|
|
6490
|
+
throw unsupportedCapability31({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
5805
6491
|
const collector = new ParamCollector(dialect);
|
|
5806
6492
|
const namespace = collector.add(table.namespace);
|
|
5807
6493
|
const name = collector.add(table.name);
|
|
@@ -5825,28 +6511,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
5825
6511
|
};
|
|
5826
6512
|
}
|
|
5827
6513
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
5828
|
-
import { unsupportedCapability as
|
|
6514
|
+
import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
|
|
5829
6515
|
function renderConstraintOwnedIndexDrop(params) {
|
|
5830
6516
|
if (params.dialect.name !== "postgres")
|
|
5831
|
-
throw
|
|
6517
|
+
throw unsupportedCapability32({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
5832
6518
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
5833
6519
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
5834
6520
|
}
|
|
5835
6521
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
5836
|
-
import { unsupportedCapability as
|
|
6522
|
+
import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
|
|
5837
6523
|
function renderMysqlCheckProbe(params) {
|
|
5838
6524
|
if (params.dialect.name !== "mysql")
|
|
5839
|
-
throw
|
|
6525
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
5840
6526
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
5841
6527
|
const probe = q(params.probe);
|
|
5842
6528
|
switch (params.operation) {
|
|
5843
6529
|
case "clone":
|
|
5844
6530
|
if (params.table === undefined)
|
|
5845
|
-
throw
|
|
6531
|
+
throw unsupportedCapability33({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
5846
6532
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
5847
6533
|
case "add": {
|
|
5848
6534
|
if (params.expression === undefined || params.definition === undefined)
|
|
5849
|
-
throw
|
|
6535
|
+
throw unsupportedCapability33({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
5850
6536
|
const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: true });
|
|
5851
6537
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
5852
6538
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -5892,33 +6578,33 @@ function sqliteTableConstraints(params) {
|
|
|
5892
6578
|
return result;
|
|
5893
6579
|
}
|
|
5894
6580
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
5895
|
-
import { unsupportedCapability as
|
|
6581
|
+
import { unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
|
|
5896
6582
|
|
|
5897
6583
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
5898
|
-
import { unsupportedCapability as
|
|
6584
|
+
import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
|
|
5899
6585
|
var MYSQL_NO_ROW_LIMIT = " LIMIT 18446744073709551615";
|
|
5900
6586
|
function renderSqliteColumnInventory(params) {
|
|
5901
6587
|
if (params.dialect.name !== "sqlite")
|
|
5902
|
-
throw
|
|
6588
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
5903
6589
|
const collector = new ParamCollector(params.dialect);
|
|
5904
6590
|
return { text: `SELECT name, hidden, pk, dflt_value AS definition FROM pragma_table_xinfo(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY cid`, values: collector.values, returning: "none" };
|
|
5905
6591
|
}
|
|
5906
6592
|
function renderSqliteForeignKeyInventory(params) {
|
|
5907
6593
|
if (params.dialect.name !== "sqlite")
|
|
5908
|
-
throw
|
|
6594
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
5909
6595
|
const collector = new ParamCollector(params.dialect);
|
|
5910
6596
|
return { text: `SELECT id, seq, "from", "to", "table", on_update, on_delete, match FROM pragma_foreign_key_list(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY id, seq`, values: collector.values, returning: "none" };
|
|
5911
6597
|
}
|
|
5912
6598
|
function renderSqliteColumnInventoryBatch(params) {
|
|
5913
6599
|
if (params.dialect.name !== "sqlite")
|
|
5914
|
-
throw
|
|
6600
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
5915
6601
|
const collector = new ParamCollector(params.dialect);
|
|
5916
6602
|
const request = params.dialect.quoteIdent({ name: "vibe_request" });
|
|
5917
6603
|
return { text: `SELECT j.key AS ${request}, x.name AS name, x.hidden AS hidden, x.pk AS pk, x.dflt_value AS definition FROM json_each(${collector.add(JSON.stringify(params.tables))}) j LEFT JOIN pragma_table_xinfo(j.value, ${collector.add(params.namespace)}) x ORDER BY j.key, x.cid`, values: collector.values, returning: "none" };
|
|
5918
6604
|
}
|
|
5919
6605
|
function renderSqliteForeignKeyInventoryBatch(params) {
|
|
5920
6606
|
if (params.dialect.name !== "sqlite")
|
|
5921
|
-
throw
|
|
6607
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
5922
6608
|
const collector = new ParamCollector(params.dialect);
|
|
5923
6609
|
const request = params.dialect.quoteIdent({ name: "vibe_request" });
|
|
5924
6610
|
return { text: `SELECT j.key AS ${request}, f.id AS id, f.seq AS seq, f."from" AS "from", f."to" AS "to", f."table" AS "table", f.on_update AS on_update, f.on_delete AS on_delete, f.match AS match FROM json_each(${collector.add(JSON.stringify(params.tables))}) j LEFT JOIN pragma_foreign_key_list(j.value, ${collector.add(params.namespace)}) f ORDER BY j.key, f.id, f.seq`, values: collector.values, returning: "none" };
|
|
@@ -5931,15 +6617,15 @@ var SQLITE_INTROSPECTION_PRAGMA_ORDER = Object.freeze({
|
|
|
5931
6617
|
});
|
|
5932
6618
|
function renderSqliteIntrospectionPragmaBatch(params) {
|
|
5933
6619
|
if (params.dialect.name !== "sqlite")
|
|
5934
|
-
throw
|
|
6620
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
|
|
5935
6621
|
const order = SQLITE_INTROSPECTION_PRAGMA_ORDER[params.pragma];
|
|
5936
6622
|
if (order === undefined)
|
|
5937
|
-
throw
|
|
6623
|
+
throw unsupportedCapability34({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
|
|
5938
6624
|
return { text: `SELECT j.key AS ${params.dialect.quoteIdent({ name: "vibe_request" })}, p.* FROM json_each(${params.dialect.quoteStringLiteral({ value: JSON.stringify(params.names) })}) j LEFT JOIN pragma_${params.pragma}(j.value) p ORDER BY j.key, ${order}`, values: [], returning: "none" };
|
|
5939
6625
|
}
|
|
5940
6626
|
function renderSqliteRebuildObjects(params) {
|
|
5941
6627
|
if (params.dialect.name !== "sqlite")
|
|
5942
|
-
throw
|
|
6628
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
5943
6629
|
return { text: "SELECT name, tbl_name, type, sql FROM main.sqlite_schema WHERE type IN ('index', 'trigger') AND sql IS NOT NULL ORDER BY type, name", values: [], returning: "none" };
|
|
5944
6630
|
}
|
|
5945
6631
|
function renderMigrationInventory(params) {
|
|
@@ -5981,13 +6667,13 @@ ORDER BY kind, parent, name${MYSQL_NO_ROW_LIMIT}`, values: collector.values, ret
|
|
|
5981
6667
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
5982
6668
|
function renderMigrationVisibility(params) {
|
|
5983
6669
|
if (params.dialect.name !== "mysql")
|
|
5984
|
-
throw
|
|
6670
|
+
throw unsupportedCapability35({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
5985
6671
|
const collector = new ParamCollector(params.dialect);
|
|
5986
6672
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
5987
6673
|
return { text: `SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.USER_PRIVILEGES WHERE GRANTEE = ${grantee} UNION SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE = ${grantee} AND ${collector.add(params.namespace)} LIKE TABLE_SCHEMA${MYSQL_NO_ROW_LIMIT}`, values: collector.values, returning: "none" };
|
|
5988
6674
|
}
|
|
5989
6675
|
// packages/sql/src/dialects/migration-objects.ts
|
|
5990
|
-
import { internalError as
|
|
6676
|
+
import { internalError as internalError6, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
|
|
5991
6677
|
function renderMigrationObjectQuery(params) {
|
|
5992
6678
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
5993
6679
|
}
|
|
@@ -5995,7 +6681,7 @@ function objectQuery(params) {
|
|
|
5995
6681
|
const { dialect, identity: object, collector } = params;
|
|
5996
6682
|
if (object.kind === "namespace") {
|
|
5997
6683
|
if (dialect.name !== "postgres")
|
|
5998
|
-
throw
|
|
6684
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
5999
6685
|
return { text: `SELECT n.nspname::text AS "name" FROM pg_catalog.pg_namespace n WHERE n.nspname = ${collector.add(object.name)}`, values: collector.values, returning: "none" };
|
|
6000
6686
|
}
|
|
6001
6687
|
const physical = "table" in object ? object.table : object.name;
|
|
@@ -6043,18 +6729,18 @@ function objectQuery(params) {
|
|
|
6043
6729
|
}
|
|
6044
6730
|
} else if (dialect.name === "mysql") {
|
|
6045
6731
|
if (object.kind === "constraint" && object.constraintType === "exclusion")
|
|
6046
|
-
throw
|
|
6732
|
+
throw unsupportedCapability36({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
6047
6733
|
const mysql = mysqlFactsText({ dialect, identity: object, namespace, name, collector });
|
|
6048
6734
|
if (mysql === undefined)
|
|
6049
|
-
throw
|
|
6735
|
+
throw unsupportedCapability36({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
6050
6736
|
text = mysql;
|
|
6051
6737
|
} else {
|
|
6052
6738
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
6053
|
-
throw
|
|
6739
|
+
throw unsupportedCapability36({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
6054
6740
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
6055
6741
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
6056
6742
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
6057
|
-
throw
|
|
6743
|
+
throw unsupportedCapability36({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
6058
6744
|
const sqliteParams = new ParamCollector(dialect);
|
|
6059
6745
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
6060
6746
|
text = `SELECT x.type AS type, x."notnull" AS "notNull", x.pk AS primaryKeyPosition, x.hidden AS generated, x.dflt_value AS definition, (SELECT sql FROM ${catalog} WHERE name = ${sqliteParams.add(physical.name)} AND type = 'table') AS tableDefinition FROM pragma_table_xinfo(${sqliteParams.add(physical.name)}, ${sqliteParams.add(physical.namespace)}) x WHERE x.name = ${sqliteParams.add(object.column)}`;
|
|
@@ -6129,7 +6815,7 @@ function mysqlBatches(params) {
|
|
|
6129
6815
|
function renderMysqlObjectBatch(params) {
|
|
6130
6816
|
const { dialect } = params;
|
|
6131
6817
|
if (dialect.name !== "mysql")
|
|
6132
|
-
throw
|
|
6818
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
6133
6819
|
const position = dialect.quoteIdent({ name: "vibe_position" });
|
|
6134
6820
|
const groups = new Map;
|
|
6135
6821
|
for (const [index, identity] of params.identities.entries()) {
|
|
@@ -6149,7 +6835,7 @@ function renderMysqlObjectBatch(params) {
|
|
|
6149
6835
|
function renderMysqlObjectMembersBatch(params) {
|
|
6150
6836
|
const { dialect } = params;
|
|
6151
6837
|
if (dialect.name !== "mysql")
|
|
6152
|
-
throw
|
|
6838
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
6153
6839
|
const position = dialect.quoteIdent({ name: "vibe_position" });
|
|
6154
6840
|
const ordinal = dialect.quoteIdent({ name: "vibe_ordinal" });
|
|
6155
6841
|
const groups = new Map;
|
|
@@ -6169,7 +6855,7 @@ function renderMigrationDependencies(params) {
|
|
|
6169
6855
|
}
|
|
6170
6856
|
function dependencyQuery(params) {
|
|
6171
6857
|
if (params.dialect.name !== "postgres")
|
|
6172
|
-
throw
|
|
6858
|
+
throw unsupportedCapability36({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
6173
6859
|
const object = params.identity;
|
|
6174
6860
|
const collector = params.collector;
|
|
6175
6861
|
if (object.kind === "namespace") {
|
|
@@ -6226,7 +6912,7 @@ function inspectionReferences(params) {
|
|
|
6226
6912
|
function renderMigrationObjectBatch(params) {
|
|
6227
6913
|
const { dialect } = params;
|
|
6228
6914
|
if (dialect.name !== "postgres")
|
|
6229
|
-
throw
|
|
6915
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
6230
6916
|
const groups = new Map;
|
|
6231
6917
|
for (const [position, identity] of params.identities.entries()) {
|
|
6232
6918
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -6239,7 +6925,7 @@ function renderMigrationObjectBatch(params) {
|
|
|
6239
6925
|
const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
6240
6926
|
const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
6241
6927
|
if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
|
|
6242
|
-
throw
|
|
6928
|
+
throw internalError6({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
|
|
6243
6929
|
const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
|
|
6244
6930
|
const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
|
|
6245
6931
|
const collector = new ParamCollector(dialect);
|
|
@@ -6259,16 +6945,16 @@ function renderMigrationNamespace(params) {
|
|
|
6259
6945
|
}
|
|
6260
6946
|
function renderCheckProbe(params) {
|
|
6261
6947
|
if (params.dialect.name !== "postgres")
|
|
6262
|
-
throw
|
|
6948
|
+
throw unsupportedCapability36({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
6263
6949
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
6264
6950
|
return { text: `ALTER TABLE ${q(params.table.namespace)}.${q(params.table.name)} ADD CONSTRAINT ${q(params.name)} CHECK (${params.expression}) NOT VALID`, values: [], returning: "none" };
|
|
6265
6951
|
}
|
|
6266
6952
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
6267
|
-
import { VibeError as
|
|
6953
|
+
import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability37 } from "@vibeorm/schema";
|
|
6268
6954
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
6269
6955
|
function requirePostgres2(params) {
|
|
6270
6956
|
if (params.dialect.name !== "postgres") {
|
|
6271
|
-
throw
|
|
6957
|
+
throw unsupportedCapability37({ dialect: params.dialect.name, feature: params.feature });
|
|
6272
6958
|
}
|
|
6273
6959
|
}
|
|
6274
6960
|
function renderMigrationSessionRead(params) {
|
|
@@ -6283,7 +6969,7 @@ function renderMigrationSessionSet(params) {
|
|
|
6283
6969
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
6284
6970
|
const { milliseconds, setting } = params;
|
|
6285
6971
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
6286
|
-
throw new
|
|
6972
|
+
throw new VibeError25({
|
|
6287
6973
|
code: "VIBE_MIGRATION",
|
|
6288
6974
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
6289
6975
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -6378,34 +7064,45 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
6378
7064
|
export {
|
|
6379
7065
|
ADVISORY_KEY_BOUNDS,
|
|
6380
7066
|
ADVISORY_LOCK_RESULT_COLUMN,
|
|
7067
|
+
AUTOMATIC_SHADOW_NAME_PATTERN,
|
|
6381
7068
|
CHAIN_VETO_PREFIX,
|
|
6382
7069
|
CHAIN_VETO_TOKEN_PATTERN,
|
|
7070
|
+
COALESCE_REF_NAME,
|
|
6383
7071
|
DIALECT_CAPABILITIES,
|
|
6384
7072
|
DIALECT_CODECS,
|
|
6385
7073
|
JSON_TRANSPORT,
|
|
6386
7074
|
MODULE_JOURNAL_COLUMN,
|
|
6387
7075
|
MYSQL_CAPABILITIES,
|
|
6388
7076
|
MYSQL_CODECS,
|
|
7077
|
+
MYSQL_SCALAR_COLUMN_TYPES,
|
|
6389
7078
|
NATIVE_RLS,
|
|
6390
7079
|
NATIVE_RLS_PROBE_POLICY,
|
|
6391
7080
|
POSTGRES_CAPABILITIES,
|
|
6392
7081
|
POSTGRES_CODECS,
|
|
6393
7082
|
POSTGRES_DIAGNOSTIC_SQL,
|
|
7083
|
+
POSTGRES_EXACT_EQUIVALENTS,
|
|
7084
|
+
POSTGRES_NATIVE_ALIASES,
|
|
7085
|
+
POSTGRES_SCALAR_COLUMN_TYPES,
|
|
6394
7086
|
POSTGRES_WORKLOAD_LIMITS,
|
|
6395
7087
|
ParamCollector,
|
|
6396
7088
|
RLS_READINESS_SQL,
|
|
6397
7089
|
SHADOW_CAPABILITIES,
|
|
6398
7090
|
SHADOW_DATABASE,
|
|
7091
|
+
SHADOW_MARKER_CAPABILITIES,
|
|
6399
7092
|
SQLITE_CAPABILITIES,
|
|
6400
7093
|
SQLITE_CODECS,
|
|
7094
|
+
SQLITE_SCALAR_STORAGE_CLASSES,
|
|
6401
7095
|
SQLITE_SESSION_SQL,
|
|
6402
7096
|
SQL_DEFAULT_CELL,
|
|
6403
7097
|
SQL_DIALECTS,
|
|
6404
7098
|
SQL_KEEP_CELL,
|
|
6405
7099
|
assertIndexExpressionVersion,
|
|
6406
7100
|
assertSqlParamBuilding,
|
|
7101
|
+
automaticShadowMarkerText,
|
|
7102
|
+
canonicalColumnType,
|
|
6407
7103
|
chainVetoToken,
|
|
6408
7104
|
cloneTimestampDate,
|
|
7105
|
+
coalesceRef,
|
|
6409
7106
|
compareTimestampDates,
|
|
6410
7107
|
compileExclusion,
|
|
6411
7108
|
compileReusableSqlCommand,
|
|
@@ -6442,10 +7139,13 @@ export {
|
|
|
6442
7139
|
isFixedDiagnostic,
|
|
6443
7140
|
isJsonListParameter,
|
|
6444
7141
|
isJsonParamCell,
|
|
7142
|
+
isJsonPatchOperation,
|
|
6445
7143
|
isListSetOperation,
|
|
6446
7144
|
isNativeEnumArrayParameter,
|
|
7145
|
+
isOtherRowReturningColumn,
|
|
6447
7146
|
isReusableSqlCommand,
|
|
6448
7147
|
isReusableSqlQuery,
|
|
7148
|
+
isRollupChain,
|
|
6449
7149
|
isSetOperation,
|
|
6450
7150
|
isSourceColumnRef,
|
|
6451
7151
|
isSqlBindingCollector,
|
|
@@ -6459,17 +7159,22 @@ export {
|
|
|
6459
7159
|
isTimeZoneName,
|
|
6460
7160
|
jsonListParameter,
|
|
6461
7161
|
jsonParamCell,
|
|
7162
|
+
jsonPatchVetoOf,
|
|
6462
7163
|
keyListBindsUntypedRows,
|
|
6463
7164
|
localDateCodec,
|
|
6464
7165
|
mapConflictColumns,
|
|
6465
7166
|
mysqlDialect,
|
|
6466
7167
|
nativeEnumArrayParameter,
|
|
7168
|
+
normalizeMysqlNativeType,
|
|
7169
|
+
normalizePostgresNativeType,
|
|
7170
|
+
parseAutomaticShadowMarker,
|
|
6467
7171
|
parseDateTimeText,
|
|
6468
7172
|
parseExtendedDateTimeText,
|
|
6469
7173
|
parseJsonText,
|
|
6470
7174
|
parsePgArrayLiteral,
|
|
6471
7175
|
parsePgArrayLiteralNested,
|
|
6472
7176
|
parsePostgresTypeName,
|
|
7177
|
+
planJsonPatch,
|
|
6473
7178
|
postgresArrayElementFromText,
|
|
6474
7179
|
postgresBulkInsertArrayCastOf,
|
|
6475
7180
|
postgresDialect,
|
|
@@ -6480,6 +7185,7 @@ export {
|
|
|
6480
7185
|
registerSqlHandleCheck,
|
|
6481
7186
|
renderAdvisoryLock,
|
|
6482
7187
|
renderAdvisoryLocks,
|
|
7188
|
+
renderAutomaticShadowOperation,
|
|
6483
7189
|
renderCaptureReadiness,
|
|
6484
7190
|
renderCheckProbe,
|
|
6485
7191
|
renderColumnStateQuery,
|
|
@@ -6587,4 +7293,4 @@ export {
|
|
|
6587
7293
|
toPgArrayLiteral
|
|
6588
7294
|
};
|
|
6589
7295
|
|
|
6590
|
-
//# debugId=
|
|
7296
|
+
//# debugId=B21A2ED0EE62B5B964756E2164756E21
|