@vibeorm/sql 5.8.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 +1072 -388
- package/dist/index.js.map +17 -13
- 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/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) {
|
|
@@ -1193,7 +1547,7 @@ function renderPostgresPhysicalCatalog(params) {
|
|
|
1193
1547
|
let text;
|
|
1194
1548
|
switch (params.query) {
|
|
1195
1549
|
case "tables":
|
|
1196
|
-
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`;
|
|
1197
1551
|
break;
|
|
1198
1552
|
case "views":
|
|
1199
1553
|
text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
@@ -1214,7 +1568,7 @@ function renderPostgresPhysicalCatalog(params) {
|
|
|
1214
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`;
|
|
1215
1569
|
break;
|
|
1216
1570
|
case "indexes":
|
|
1217
|
-
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`;
|
|
1218
1572
|
break;
|
|
1219
1573
|
}
|
|
1220
1574
|
return { text, values: collector.values, returning: "none" };
|
|
@@ -1810,7 +2164,7 @@ function extractIndexKeyList({ sql, open, close, tokens, wrapped = false, remove
|
|
|
1810
2164
|
}
|
|
1811
2165
|
return keys.length === 0 ? undefined : keys;
|
|
1812
2166
|
}
|
|
1813
|
-
function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
|
|
2167
|
+
function extractStandaloneIndexKeys({ definition, index, postgres = false, included }) {
|
|
1814
2168
|
const tokens = tableDefinitionTokens({ sql: definition });
|
|
1815
2169
|
const top = tokens.filter((token) => token.depth === 0);
|
|
1816
2170
|
let position = 0;
|
|
@@ -1851,6 +2205,16 @@ function extractStandaloneIndexKeys({ definition, index, postgres = false }) {
|
|
|
1851
2205
|
const close = top[position++];
|
|
1852
2206
|
if (open?.kind !== "symbol" || open.value !== "(" || close?.kind !== "symbol" || close.value !== ")")
|
|
1853
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;
|
|
1854
2218
|
if (postgres && word({ value: "NULLS" }) && (!word({ value: "NOT" }) || !word({ value: "DISTINCT" })))
|
|
1855
2219
|
return;
|
|
1856
2220
|
const tail = top[position];
|
|
@@ -1877,8 +2241,8 @@ function extractSqliteIndexKeys({ definition, index }) {
|
|
|
1877
2241
|
}
|
|
1878
2242
|
|
|
1879
2243
|
// packages/sql/src/dialects/postgres-index-expressions.ts
|
|
1880
|
-
function extractPostgresIndexKeys({ definition, index }) {
|
|
1881
|
-
return extractStandaloneIndexKeys({ definition, index, postgres: true });
|
|
2244
|
+
function extractPostgresIndexKeys({ definition, index, included }) {
|
|
2245
|
+
return extractStandaloneIndexKeys({ definition, index, postgres: true, ...included === undefined ? {} : { included } });
|
|
1882
2246
|
}
|
|
1883
2247
|
function renderPostgresIndexExpressionProbe({ ...params }) {
|
|
1884
2248
|
const { dialect, operation, probe } = params;
|
|
@@ -2147,10 +2511,10 @@ function renderIndexKeys({ dialect, keys, unique }) {
|
|
|
2147
2511
|
return `${expression}${key.desc ? " DESC" : ""}`;
|
|
2148
2512
|
});
|
|
2149
2513
|
}
|
|
2150
|
-
function extractIndexKeys({ dialect, definition, index, backslashEscapes = true }) {
|
|
2514
|
+
function extractIndexKeys({ dialect, definition, index, backslashEscapes = true, included }) {
|
|
2151
2515
|
switch (dialect) {
|
|
2152
2516
|
case "postgres":
|
|
2153
|
-
return extractPostgresIndexKeys({ definition, index });
|
|
2517
|
+
return extractPostgresIndexKeys({ definition, index, ...included === undefined ? {} : { included } });
|
|
2154
2518
|
case "sqlite":
|
|
2155
2519
|
return extractSqliteIndexKeys({ definition, index });
|
|
2156
2520
|
case "mysql":
|
|
@@ -2318,6 +2682,57 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
|
|
|
2318
2682
|
}
|
|
2319
2683
|
}
|
|
2320
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
|
+
}
|
|
2321
2736
|
// packages/sql/src/module-journal.ts
|
|
2322
2737
|
var MODULE_JOURNAL_COLUMN = "module_origin";
|
|
2323
2738
|
function renderModuleJournal(params) {
|
|
@@ -2355,10 +2770,10 @@ import {
|
|
|
2355
2770
|
} from "@vibeorm/schema";
|
|
2356
2771
|
|
|
2357
2772
|
// packages/sql/src/identifiers.ts
|
|
2358
|
-
import { VibeError as
|
|
2773
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
2359
2774
|
function assertNoNul(params) {
|
|
2360
2775
|
if (params.name.includes("\x00")) {
|
|
2361
|
-
throw new
|
|
2776
|
+
throw new VibeError13({
|
|
2362
2777
|
code: "VIBE_VALIDATION",
|
|
2363
2778
|
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
2364
2779
|
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
@@ -2400,6 +2815,7 @@ var sqliteDialect = {
|
|
|
2400
2815
|
jsonPathMode: "sqliteExtract",
|
|
2401
2816
|
supportedJsonOps: [
|
|
2402
2817
|
"equals",
|
|
2818
|
+
"exists",
|
|
2403
2819
|
"string_contains",
|
|
2404
2820
|
"string_starts_with",
|
|
2405
2821
|
"string_ends_with",
|
|
@@ -2504,11 +2920,11 @@ var SQLITE_SESSION_SQL = Object.freeze({
|
|
|
2504
2920
|
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
2505
2921
|
});
|
|
2506
2922
|
// packages/sql/src/composable.ts
|
|
2507
|
-
import { VibeError as
|
|
2923
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
2508
2924
|
var fragments = new WeakMap;
|
|
2509
2925
|
registerSqlHandleCheck({ check: (value) => fragments.has(value) });
|
|
2510
2926
|
function invalid4(message) {
|
|
2511
|
-
throw new
|
|
2927
|
+
throw new VibeError14({ code: "VIBE_VALIDATION", message });
|
|
2512
2928
|
}
|
|
2513
2929
|
function construct(parts) {
|
|
2514
2930
|
const snapshot = Object.freeze([...parts]);
|
|
@@ -2606,12 +3022,12 @@ function renderSqlFragment(params) {
|
|
|
2606
3022
|
const only = parts.length === 1 ? parts[0] : undefined;
|
|
2607
3023
|
if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
|
|
2608
3024
|
if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
|
|
2609
|
-
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" });
|
|
2610
3026
|
if (only.value === null) {
|
|
2611
3027
|
if (only.decoder !== undefined)
|
|
2612
3028
|
recordSqlBindingCheck({ collector, check: () => {
|
|
2613
3029
|
if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
|
|
2614
|
-
throw new
|
|
3030
|
+
throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
|
|
2615
3031
|
} });
|
|
2616
3032
|
return "NULL";
|
|
2617
3033
|
}
|
|
@@ -2675,14 +3091,14 @@ function sqlUsesIncoming(params) {
|
|
|
2675
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 }))));
|
|
2676
3092
|
}
|
|
2677
3093
|
// packages/sql/src/query-plan.ts
|
|
2678
|
-
import { VibeError as
|
|
3094
|
+
import { VibeError as VibeError15 } from "@vibeorm/schema";
|
|
2679
3095
|
var queries = new WeakMap;
|
|
2680
3096
|
var commands = new WeakMap;
|
|
2681
3097
|
var reusableQueries = new WeakMap;
|
|
2682
3098
|
var reusableCommands = new WeakMap;
|
|
2683
3099
|
registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
|
|
2684
3100
|
function invalid5(message) {
|
|
2685
|
-
throw new
|
|
3101
|
+
throw new VibeError15({ code: "VIBE_VALIDATION", message });
|
|
2686
3102
|
}
|
|
2687
3103
|
function createSqlQuery(params) {
|
|
2688
3104
|
if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
|
|
@@ -2773,14 +3189,14 @@ function compileSqlCommand(params) {
|
|
|
2773
3189
|
return { text, values: collector.values };
|
|
2774
3190
|
}
|
|
2775
3191
|
// packages/sql/src/exclusion.ts
|
|
2776
|
-
import { VibeError as
|
|
3192
|
+
import { VibeError as VibeError16, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
2777
3193
|
function invalid6(rule) {
|
|
2778
|
-
throw new
|
|
3194
|
+
throw new VibeError16({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
2779
3195
|
}
|
|
2780
3196
|
function compileExclusion(params) {
|
|
2781
3197
|
const { exclusion: spec, model, dialect } = params;
|
|
2782
3198
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2783
|
-
throw
|
|
3199
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2784
3200
|
if (!spec.name || spec.name.includes("\x00"))
|
|
2785
3201
|
invalid6("name");
|
|
2786
3202
|
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
@@ -2833,13 +3249,13 @@ function compileExclusion(params) {
|
|
|
2833
3249
|
}
|
|
2834
3250
|
function renderExclusionSetup(params) {
|
|
2835
3251
|
if (!params.dialect.capabilities.exclusionConstraints)
|
|
2836
|
-
throw
|
|
3252
|
+
throw unsupportedCapability15({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
2837
3253
|
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
2838
3254
|
}
|
|
2839
3255
|
function renderExclusionDdl(params) {
|
|
2840
3256
|
const { dialect, table, exclusion, operation } = params;
|
|
2841
3257
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2842
|
-
throw
|
|
3258
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2843
3259
|
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
2844
3260
|
if (operation === "drop")
|
|
2845
3261
|
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
@@ -2848,111 +3264,18 @@ function renderExclusionDdl(params) {
|
|
|
2848
3264
|
function renderExclusionCatalog(params) {
|
|
2849
3265
|
const { dialect, probe } = params;
|
|
2850
3266
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2851
|
-
throw
|
|
3267
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2852
3268
|
const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
2853
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`;
|
|
2854
3270
|
}
|
|
2855
3271
|
function renderExclusionProbe(params) {
|
|
2856
3272
|
const { dialect, table, probe, definition } = params;
|
|
2857
3273
|
if (!dialect.capabilities.exclusionConstraints)
|
|
2858
|
-
throw
|
|
3274
|
+
throw unsupportedCapability15({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
2859
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}`];
|
|
2860
3276
|
}
|
|
2861
|
-
// packages/sql/src/ast.ts
|
|
2862
|
-
var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
|
|
2863
|
-
function isSetOperation(value) {
|
|
2864
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2865
|
-
return false;
|
|
2866
|
-
const keys = Object.keys(value);
|
|
2867
|
-
if (!keys.includes("__op") || !keys.includes("value"))
|
|
2868
|
-
return false;
|
|
2869
|
-
const candidate = value;
|
|
2870
|
-
if (keys.length !== 2 && !(keys.length === 3 && keys.includes("numericType") && candidate.numericType === "Decimal"))
|
|
2871
|
-
return false;
|
|
2872
|
-
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");
|
|
2873
|
-
}
|
|
2874
|
-
var LIST_SET_OPERATORS = ["append", "concat"];
|
|
2875
|
-
function isListSetOperation(value) {
|
|
2876
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2877
|
-
return false;
|
|
2878
|
-
const keys = Object.keys(value);
|
|
2879
|
-
if (keys.length !== 2 || !keys.includes("__listOp") || !keys.includes("value"))
|
|
2880
|
-
return false;
|
|
2881
|
-
const candidate = value;
|
|
2882
|
-
return typeof candidate.__listOp === "string" && LIST_SET_OPERATORS.includes(candidate.__listOp);
|
|
2883
|
-
}
|
|
2884
|
-
function dbNowMarker(params) {
|
|
2885
|
-
return { __dbNow: true, offsetSeconds: params.offsetSeconds };
|
|
2886
|
-
}
|
|
2887
|
-
function isDbNowMarker(value) {
|
|
2888
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2889
|
-
return false;
|
|
2890
|
-
if (!Object.hasOwn(value, "__dbNow"))
|
|
2891
|
-
return false;
|
|
2892
|
-
const keys = Object.keys(value);
|
|
2893
|
-
if (keys.length !== 2 || !keys.includes("__dbNow") || !keys.includes("offsetSeconds"))
|
|
2894
|
-
return false;
|
|
2895
|
-
const candidate = value;
|
|
2896
|
-
return candidate.__dbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
|
|
2897
|
-
}
|
|
2898
|
-
var SQL_DEFAULT_CELL = Object.freeze({ __sqlDefault: true });
|
|
2899
|
-
var SQL_KEEP_CELL = Object.freeze({ __sqlKeep: true });
|
|
2900
|
-
function isSqlKeepCell(value) {
|
|
2901
|
-
return value === SQL_KEEP_CELL;
|
|
2902
|
-
}
|
|
2903
|
-
function isSqlDefaultCell(value) {
|
|
2904
|
-
return value === SQL_DEFAULT_CELL;
|
|
2905
|
-
}
|
|
2906
|
-
function jsonParamCell(params) {
|
|
2907
|
-
return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
|
|
2908
|
-
}
|
|
2909
|
-
function isJsonParamCell(value) {
|
|
2910
|
-
return typeof value === "object" && value !== null && value.__jsonParam === true;
|
|
2911
|
-
}
|
|
2912
|
-
function excludedRef(params) {
|
|
2913
|
-
return { __excluded: true, column: params.column };
|
|
2914
|
-
}
|
|
2915
|
-
function isExcludedRef(value) {
|
|
2916
|
-
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
2917
|
-
}
|
|
2918
|
-
function returningColumnName(column) {
|
|
2919
|
-
return typeof column === "string" ? column : ("capture" in column) ? column.alias : column.name;
|
|
2920
|
-
}
|
|
2921
|
-
function isCaptureReturningColumn(column) {
|
|
2922
|
-
return typeof column !== "string" && "capture" in column;
|
|
2923
|
-
}
|
|
2924
|
-
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
2925
|
-
function stepColumnRef(params) {
|
|
2926
|
-
return { __stepColumn: true, step: params.step, column: params.column };
|
|
2927
|
-
}
|
|
2928
|
-
function sourceColumnRef(params) {
|
|
2929
|
-
return Object.freeze({ __sourceColumn: true, column: params.column });
|
|
2930
|
-
}
|
|
2931
|
-
function isSourceColumnRef(value) {
|
|
2932
|
-
if (typeof value !== "object" || value === null)
|
|
2933
|
-
return false;
|
|
2934
|
-
const candidate = value;
|
|
2935
|
-
return candidate.__sourceColumn === true && typeof candidate.column === "string" && Object.keys(value).length === 2;
|
|
2936
|
-
}
|
|
2937
|
-
function isStepColumnRef(value) {
|
|
2938
|
-
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
2939
|
-
}
|
|
2940
|
-
var CHAIN_VETO_PREFIX = "__vibe_veto:";
|
|
2941
|
-
var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
2942
|
-
function chainVetoToken(params) {
|
|
2943
|
-
const { message, nonce } = params;
|
|
2944
|
-
if (nonce === "")
|
|
2945
|
-
return;
|
|
2946
|
-
for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
|
|
2947
|
-
const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
|
|
2948
|
-
const match = /^([a-z][a-z0-9_]*):/.exec(rest);
|
|
2949
|
-
if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
|
|
2950
|
-
return match[1];
|
|
2951
|
-
}
|
|
2952
|
-
return;
|
|
2953
|
-
}
|
|
2954
3277
|
// packages/sql/src/locks.ts
|
|
2955
|
-
import { internalError, unsupportedCapability as
|
|
3278
|
+
import { internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2956
3279
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
2957
3280
|
strengths: Object.freeze({
|
|
2958
3281
|
update: "FOR UPDATE",
|
|
@@ -2972,7 +3295,7 @@ function renderRowLockClause(params) {
|
|
|
2972
3295
|
if (syntax === null) {
|
|
2973
3296
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
2974
3297
|
return null;
|
|
2975
|
-
throw
|
|
3298
|
+
throw unsupportedCapability16({
|
|
2976
3299
|
dialect: dialect.name,
|
|
2977
3300
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
2978
3301
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -2980,7 +3303,7 @@ function renderRowLockClause(params) {
|
|
|
2980
3303
|
}
|
|
2981
3304
|
const strengthText = syntax.strengths[lock.strength];
|
|
2982
3305
|
if (strengthText === undefined) {
|
|
2983
|
-
throw
|
|
3306
|
+
throw unsupportedCapability16({
|
|
2984
3307
|
dialect: dialect.name,
|
|
2985
3308
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
2986
3309
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -2988,7 +3311,7 @@ function renderRowLockClause(params) {
|
|
|
2988
3311
|
}
|
|
2989
3312
|
const waitText = syntax.wait[lock.wait];
|
|
2990
3313
|
if (waitText === undefined) {
|
|
2991
|
-
throw
|
|
3314
|
+
throw unsupportedCapability16({
|
|
2992
3315
|
dialect: dialect.name,
|
|
2993
3316
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
2994
3317
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -2996,7 +3319,7 @@ function renderRowLockClause(params) {
|
|
|
2996
3319
|
}
|
|
2997
3320
|
if (lock.of !== undefined) {
|
|
2998
3321
|
if (lock.of.length === 0)
|
|
2999
|
-
throw
|
|
3322
|
+
throw internalError2({ message: "a row lock's `of` list cannot be empty" });
|
|
3000
3323
|
const tables = lock.of.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3001
3324
|
return `${strengthText} OF ${tables}${waitText}`;
|
|
3002
3325
|
}
|
|
@@ -3013,7 +3336,7 @@ function renderAdvisoryLock(params) {
|
|
|
3013
3336
|
const { dialect, statement, collector } = params;
|
|
3014
3337
|
const functions = dialect.advisoryLockFunctions;
|
|
3015
3338
|
if (functions === null) {
|
|
3016
|
-
throw
|
|
3339
|
+
throw unsupportedCapability16({
|
|
3017
3340
|
dialect: dialect.name,
|
|
3018
3341
|
feature: "advisory locks",
|
|
3019
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`
|
|
@@ -3034,11 +3357,11 @@ function renderAdvisoryLocks(params) {
|
|
|
3034
3357
|
const { dialect, keys, mode } = params;
|
|
3035
3358
|
const functions = dialect.advisoryLockFunctions;
|
|
3036
3359
|
if (functions === null)
|
|
3037
|
-
throw
|
|
3360
|
+
throw unsupportedCapability16({ dialect: dialect.name, feature: "advisory locks" });
|
|
3038
3361
|
if (mode !== "exclusive" && mode !== "shared")
|
|
3039
|
-
throw
|
|
3362
|
+
throw internalError2({ message: "advisory lock mode must be exclusive or shared" });
|
|
3040
3363
|
if (keys.some((key) => typeof key !== "bigint" || key < ADVISORY_KEY_BOUNDS.single.min || key > ADVISORY_KEY_BOUNDS.single.max)) {
|
|
3041
|
-
throw
|
|
3364
|
+
throw internalError2({ message: "advisory lock keys must be signed 64-bit integers" });
|
|
3042
3365
|
}
|
|
3043
3366
|
const collector = new ParamCollector(dialect);
|
|
3044
3367
|
const binding = collector.add(JSON.stringify(keys.map((key) => key.toString())));
|
|
@@ -3055,7 +3378,7 @@ function renderAdvisoryLocks(params) {
|
|
|
3055
3378
|
function assertInt32(params) {
|
|
3056
3379
|
const { value, part } = params;
|
|
3057
3380
|
if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
|
|
3058
|
-
throw
|
|
3381
|
+
throw internalError2({
|
|
3059
3382
|
message: `advisory lock ${part} must be a 32-bit integer, got ${String(value)}`,
|
|
3060
3383
|
meta: { part, value }
|
|
3061
3384
|
});
|
|
@@ -3101,6 +3424,7 @@ var postgresDialect = {
|
|
|
3101
3424
|
jsonPathMode: "operators",
|
|
3102
3425
|
supportedJsonOps: [
|
|
3103
3426
|
"equals",
|
|
3427
|
+
"exists",
|
|
3104
3428
|
"string_contains",
|
|
3105
3429
|
"string_starts_with",
|
|
3106
3430
|
"string_ends_with",
|
|
@@ -3195,6 +3519,7 @@ var mysqlDialect = {
|
|
|
3195
3519
|
jsonPathMode: "mysqlExtract",
|
|
3196
3520
|
supportedJsonOps: [
|
|
3197
3521
|
"equals",
|
|
3522
|
+
"exists",
|
|
3198
3523
|
"string_contains",
|
|
3199
3524
|
"string_starts_with",
|
|
3200
3525
|
"string_ends_with",
|
|
@@ -3225,11 +3550,12 @@ var mysqlDialect = {
|
|
|
3225
3550
|
renderEnumOrdinal({ column }) {
|
|
3226
3551
|
return `${column} + 0`;
|
|
3227
3552
|
},
|
|
3228
|
-
renderEnumMinMax({ fn, column, result }) {
|
|
3553
|
+
renderEnumMinMax({ fn, column, result, when }) {
|
|
3229
3554
|
const aggregate = fn === "min" ? "MIN" : "MAX";
|
|
3555
|
+
const only = (value) => when === undefined ? value : `CASE WHEN ${when} THEN ${value} END`;
|
|
3230
3556
|
if (result === "ordinal")
|
|
3231
|
-
return `${aggregate}(${column} + 0)`;
|
|
3232
|
-
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)`;
|
|
3233
3559
|
},
|
|
3234
3560
|
placeholder() {
|
|
3235
3561
|
return "?";
|
|
@@ -3253,7 +3579,7 @@ function getSqlDialect(params) {
|
|
|
3253
3579
|
return SQL_DIALECTS[params.dialect];
|
|
3254
3580
|
}
|
|
3255
3581
|
// packages/sql/src/key-list.ts
|
|
3256
|
-
import { unsupportedCapability as
|
|
3582
|
+
import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
3257
3583
|
var SCALAR_ELEMENT_TYPES = Object.freeze({
|
|
3258
3584
|
String: "text",
|
|
3259
3585
|
Boolean: "bool",
|
|
@@ -3324,7 +3650,7 @@ function postgresKeyListElementType(params) {
|
|
|
3324
3650
|
const { dialect, member } = params;
|
|
3325
3651
|
const type = postgresKeyListElementTypeOf({ dialect, member });
|
|
3326
3652
|
if (type === undefined) {
|
|
3327
|
-
throw
|
|
3653
|
+
throw unsupportedCapability17({
|
|
3328
3654
|
dialect: dialect.name,
|
|
3329
3655
|
feature: `the key-list filter (IN) on a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} member`,
|
|
3330
3656
|
hint: "IN binds every member as a typed array; filter this field with equality inside OR instead"
|
|
@@ -3345,7 +3671,7 @@ function postgresBulkInsertArrayCast(params) {
|
|
|
3345
3671
|
const { dialect, member } = params;
|
|
3346
3672
|
const cast = postgresBulkInsertArrayCastOf({ dialect, member });
|
|
3347
3673
|
if (cast === undefined) {
|
|
3348
|
-
throw
|
|
3674
|
+
throw unsupportedCapability17({
|
|
3349
3675
|
dialect: dialect.name,
|
|
3350
3676
|
feature: `a bulk insert binding a ${member.type}${member.nativeType === undefined ? "" : ` @db.${member.nativeType}`} column as a typed array`,
|
|
3351
3677
|
hint: "the query builder keeps VALUES for this column type"
|
|
@@ -3365,7 +3691,7 @@ function renderLocalDateCheck({ dialect, table, column }) {
|
|
|
3365
3691
|
};
|
|
3366
3692
|
}
|
|
3367
3693
|
// packages/sql/src/date-truncation.ts
|
|
3368
|
-
import { DATE_TRUNCATION_UNITS, VibeError as
|
|
3694
|
+
import { DATE_TRUNCATION_UNITS, VibeError as VibeError17, internalError as internalError3, unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
3369
3695
|
var TIME_ZONE_NAME = /^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/;
|
|
3370
3696
|
function isTimeZoneName(params) {
|
|
3371
3697
|
return typeof params.value === "string" && TIME_ZONE_NAME.test(params.value);
|
|
@@ -3373,17 +3699,17 @@ function isTimeZoneName(params) {
|
|
|
3373
3699
|
function renderDateTruncation(params) {
|
|
3374
3700
|
const { dialect, reference, truncation } = params;
|
|
3375
3701
|
if (!dialect.capabilities.dateTruncation) {
|
|
3376
|
-
throw
|
|
3702
|
+
throw unsupportedCapability18({
|
|
3377
3703
|
dialect: dialect.name,
|
|
3378
3704
|
feature: "a truncated groupBy key ({ field, truncate, timeZone })",
|
|
3379
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"
|
|
3380
3706
|
});
|
|
3381
3707
|
}
|
|
3382
3708
|
if (!DATE_TRUNCATION_UNITS.includes(truncation.unit)) {
|
|
3383
|
-
throw
|
|
3709
|
+
throw internalError3({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
|
|
3384
3710
|
}
|
|
3385
3711
|
if (!isTimeZoneName({ value: truncation.timeZone })) {
|
|
3386
|
-
throw new
|
|
3712
|
+
throw new VibeError17({
|
|
3387
3713
|
code: "VIBE_VALIDATION",
|
|
3388
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"`,
|
|
3389
3715
|
meta: { timeZone: truncation.timeZone }
|
|
@@ -3397,20 +3723,129 @@ function renderDateTruncation(params) {
|
|
|
3397
3723
|
case "timestamp":
|
|
3398
3724
|
return `(date_trunc(${unit}, ${reference} AT TIME ZONE 'UTC', ${zone}) AT TIME ZONE 'UTC')`;
|
|
3399
3725
|
default:
|
|
3400
|
-
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];
|
|
3401
3836
|
}
|
|
3402
3837
|
}
|
|
3403
3838
|
// packages/sql/src/generated-columns.ts
|
|
3404
|
-
import { VibeError as
|
|
3839
|
+
import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
3405
3840
|
|
|
3406
3841
|
// packages/sql/src/dialects/postgres-generated-columns.ts
|
|
3407
|
-
import { unsupportedCapability as
|
|
3842
|
+
import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
3408
3843
|
function renderPostgresGeneratedColumnClause({ generated }) {
|
|
3409
3844
|
return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
|
|
3410
3845
|
}
|
|
3411
3846
|
function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
|
|
3412
3847
|
if (dialect.name !== "postgres")
|
|
3413
|
-
throw
|
|
3848
|
+
throw unsupportedCapability19({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
|
|
3414
3849
|
const q = ({ name }) => dialect.quoteIdent({ name });
|
|
3415
3850
|
let text;
|
|
3416
3851
|
switch (probe.operation) {
|
|
@@ -3431,20 +3866,20 @@ function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
|
|
|
3431
3866
|
}
|
|
3432
3867
|
function renderPostgresDropGeneratedExpression({ dialect, table, column }) {
|
|
3433
3868
|
if (dialect.name !== "postgres")
|
|
3434
|
-
throw
|
|
3869
|
+
throw unsupportedCapability19({ dialect: dialect.name, feature: "dropGeneratedExpression" });
|
|
3435
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" };
|
|
3436
3871
|
}
|
|
3437
3872
|
|
|
3438
3873
|
// packages/sql/src/generated-columns.ts
|
|
3439
3874
|
function renderGeneratedColumnClause({ dialect, generated }) {
|
|
3440
3875
|
if (!("generatedColumns" in dialect.capabilities) || dialect.capabilities.generatedColumns !== true) {
|
|
3441
|
-
throw
|
|
3876
|
+
throw unsupportedCapability20({ dialect: dialect.name, feature: "generatedColumns" });
|
|
3442
3877
|
}
|
|
3443
3878
|
if (generated.storage !== "stored") {
|
|
3444
|
-
throw
|
|
3879
|
+
throw unsupportedCapability20({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
|
|
3445
3880
|
}
|
|
3446
3881
|
if (typeof generated.expression !== "string" || generated.expression.trim().length === 0 || generated.expression.includes("\x00")) {
|
|
3447
|
-
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" } });
|
|
3448
3883
|
}
|
|
3449
3884
|
switch (dialect.name) {
|
|
3450
3885
|
case "postgres":
|
|
@@ -3456,10 +3891,10 @@ function renderGeneratedColumnClause({ dialect, generated }) {
|
|
|
3456
3891
|
}
|
|
3457
3892
|
}
|
|
3458
3893
|
// packages/sql/src/row-rules.ts
|
|
3459
|
-
import { VibeError as
|
|
3894
|
+
import { VibeError as VibeError22, internalError as internalError4, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3460
3895
|
|
|
3461
3896
|
// packages/sql/src/dialects/postgres-row-rules.ts
|
|
3462
|
-
import { VibeError as
|
|
3897
|
+
import { VibeError as VibeError19, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
3463
3898
|
function comparison({ dialect, field }) {
|
|
3464
3899
|
const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
3465
3900
|
const cast = field.type === "Json" ? `::pg_catalog.jsonb${field.isList ? "[]" : ""}` : "";
|
|
@@ -3467,7 +3902,7 @@ function comparison({ dialect, field }) {
|
|
|
3467
3902
|
}
|
|
3468
3903
|
function renderPostgresRowRule({ dialect, context }) {
|
|
3469
3904
|
if (dialect.name !== "postgres")
|
|
3470
|
-
throw
|
|
3905
|
+
throw unsupportedCapability21({ dialect: dialect.name, feature: "postgresRowRules" });
|
|
3471
3906
|
const { rule, table, functionIdentity, triggerIdentity, protectedFields, gateField } = context;
|
|
3472
3907
|
const qualify = ({ name }) => `${dialect.quoteIdent({ name: name.namespace })}.${dialect.quoteIdent({ name: name.name })}`;
|
|
3473
3908
|
const functionName = qualify({ name: functionIdentity.name });
|
|
@@ -3484,7 +3919,7 @@ END;`;
|
|
|
3484
3919
|
} else {
|
|
3485
3920
|
const terms = protectedFields.map((field) => comparison({ dialect, field }));
|
|
3486
3921
|
if (terms.length === 0) {
|
|
3487
|
-
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 } });
|
|
3488
3923
|
}
|
|
3489
3924
|
const condition = gateField === undefined ? terms.join(`
|
|
3490
3925
|
OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
@@ -3519,19 +3954,19 @@ FOR EACH ROW EXECUTE FUNCTION ${functionName}();`;
|
|
|
3519
3954
|
}
|
|
3520
3955
|
|
|
3521
3956
|
// packages/sql/src/dialects/sqlite-row-rules.ts
|
|
3522
|
-
import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as
|
|
3957
|
+
import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as VibeError20, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
3523
3958
|
function renderSqliteRowRule({ dialect, context }) {
|
|
3524
3959
|
if (dialect.name !== "sqlite")
|
|
3525
|
-
throw
|
|
3960
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "sqliteRowRules" });
|
|
3526
3961
|
const { rule, table, triggers, protectedFields, gateField } = context;
|
|
3527
3962
|
if (table.namespace !== "main")
|
|
3528
|
-
throw
|
|
3963
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3529
3964
|
const terms = protectedFields.map((field) => {
|
|
3530
3965
|
const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
3531
3966
|
return `OLD.${column} IS NOT NEW.${column}`;
|
|
3532
3967
|
});
|
|
3533
3968
|
if (rule.kind !== "appendOnly" && terms.length === 0)
|
|
3534
|
-
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 } });
|
|
3535
3970
|
const condition = rule.kind === "appendOnly" ? "" : gateField === undefined ? `WHEN ${terms.join(`
|
|
3536
3971
|
OR `)}
|
|
3537
3972
|
` : `WHEN OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
@@ -3556,11 +3991,11 @@ END;`,
|
|
|
3556
3991
|
}
|
|
3557
3992
|
|
|
3558
3993
|
// packages/sql/src/dialects/mysql-row-rules.ts
|
|
3559
|
-
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";
|
|
3560
3995
|
function diagnosticLiteral({ name, context, dialect }) {
|
|
3561
3996
|
const message = `Check constraint '${name}' is violated.`;
|
|
3562
3997
|
if (mysqlRowRuleMessageByteLength({ triggerName: name }) > MYSQL_ROW_RULE_MESSAGE_MAX_BYTES)
|
|
3563
|
-
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 } });
|
|
3564
3999
|
if (message.includes("\\")) {
|
|
3565
4000
|
const hex = Array.from(new TextEncoder().encode(message), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3566
4001
|
return `_utf8mb4 X'${hex}'`;
|
|
@@ -3569,11 +4004,11 @@ function diagnosticLiteral({ name, context, dialect }) {
|
|
|
3569
4004
|
}
|
|
3570
4005
|
function renderMysqlRowRule({ dialect, context }) {
|
|
3571
4006
|
if (dialect.name !== "mysql")
|
|
3572
|
-
throw
|
|
4007
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "mysqlRowRules" });
|
|
3573
4008
|
const { rule, table, triggers, protectedFields, gateField, placement } = context;
|
|
3574
4009
|
for (const name of Object.keys(placement ?? {}))
|
|
3575
4010
|
if (!triggers.some(({ identity }) => identity.name === name))
|
|
3576
|
-
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 } });
|
|
3577
4012
|
const position = ({ name }) => {
|
|
3578
4013
|
const entry = placement?.[name];
|
|
3579
4014
|
if (entry === undefined)
|
|
@@ -3586,7 +4021,7 @@ function renderMysqlRowRule({ dialect, context }) {
|
|
|
3586
4021
|
return `NOT (OLD.${column} <=> NEW.${column})`;
|
|
3587
4022
|
});
|
|
3588
4023
|
if (rule.kind !== "appendOnly" && terms.length === 0)
|
|
3589
|
-
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 } });
|
|
3590
4025
|
const condition = gateField === undefined ? terms.join(`
|
|
3591
4026
|
OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
3592
4027
|
${terms.join(`
|
|
@@ -3615,10 +4050,10 @@ END;`, values: [] };
|
|
|
3615
4050
|
function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
3616
4051
|
const issue = rowRuleShapeIssue({ value: rule });
|
|
3617
4052
|
if (issue !== undefined) {
|
|
3618
|
-
throw new
|
|
4053
|
+
throw new VibeError22({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { model: model.name } });
|
|
3619
4054
|
}
|
|
3620
4055
|
if (placement !== undefined && Object.keys(placement).length > 0 && dialect.name !== "mysql")
|
|
3621
|
-
throw
|
|
4056
|
+
throw unsupportedCapability24({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
|
|
3622
4057
|
const normalized = normalizeRowRules({ schema, model, rules: [rule], dialect: dialect.name })[0];
|
|
3623
4058
|
switch (dialect.name) {
|
|
3624
4059
|
case "postgres": {
|
|
@@ -3626,7 +4061,7 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
3626
4061
|
const functionIdentity = objects.find((object) => object.role === "function")?.identity;
|
|
3627
4062
|
const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
|
|
3628
4063
|
if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
|
|
3629
|
-
throw
|
|
4064
|
+
throw internalError4({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
3630
4065
|
const gate = rowRuleGateField({ rule: normalized });
|
|
3631
4066
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
3632
4067
|
return renderPostgresRowRule({
|
|
@@ -3647,15 +4082,15 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
3647
4082
|
const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
|
|
3648
4083
|
const triggers = objects.map(({ role, identity }) => {
|
|
3649
4084
|
if (role !== "trigger" || identity.kind !== "trigger")
|
|
3650
|
-
throw
|
|
4085
|
+
throw internalError4({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
|
|
3651
4086
|
const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
|
|
3652
4087
|
if (event === undefined)
|
|
3653
|
-
throw
|
|
4088
|
+
throw internalError4({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
|
|
3654
4089
|
return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
|
|
3655
4090
|
});
|
|
3656
4091
|
const table = triggers[0]?.identity.table;
|
|
3657
4092
|
if (table === undefined)
|
|
3658
|
-
throw
|
|
4093
|
+
throw internalError4({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
3659
4094
|
const gate = rowRuleGateField({ rule: normalized });
|
|
3660
4095
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
3661
4096
|
const context = {
|
|
@@ -3686,15 +4121,16 @@ function renderSqliteJsonEquality(params) {
|
|
|
3686
4121
|
}
|
|
3687
4122
|
|
|
3688
4123
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3689
|
-
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as
|
|
4124
|
+
import { assertSqlNamespace as assertSqlNamespace2, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
|
|
3690
4125
|
var QUERIES = Object.freeze({
|
|
3691
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"` },
|
|
3692
4128
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
3693
4129
|
});
|
|
3694
4130
|
function renderDiagnostic(params) {
|
|
3695
4131
|
const query = QUERIES[params.query];
|
|
3696
4132
|
if (params.dialect.name !== query.dialect) {
|
|
3697
|
-
throw
|
|
4133
|
+
throw unsupportedCapability25({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
3698
4134
|
}
|
|
3699
4135
|
return { text: query.text, values: [], returning: "none" };
|
|
3700
4136
|
}
|
|
@@ -3705,7 +4141,7 @@ var CAPTURE_READINESS_SQL = `SELECT pg_catalog.current_setting('server_version_n
|
|
|
3705
4141
|
function renderCaptureReadiness(params) {
|
|
3706
4142
|
const { dialect, tables } = params;
|
|
3707
4143
|
if (dialect.name !== "postgres") {
|
|
3708
|
-
throw
|
|
4144
|
+
throw unsupportedCapability25({ dialect: dialect.name, feature: "postgresCaptureReadiness", hint: "row capture reads RETURNING old/new, PostgreSQL 18 or newer" });
|
|
3709
4145
|
}
|
|
3710
4146
|
const entries = tables.map((entry, index) => {
|
|
3711
4147
|
const { name, namespace } = entry.table;
|
|
@@ -3719,7 +4155,7 @@ function renderCaptureReadiness(params) {
|
|
|
3719
4155
|
}
|
|
3720
4156
|
|
|
3721
4157
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
3722
|
-
import { VibeError as
|
|
4158
|
+
import { VibeError as VibeError23, unsupportedCapability as unsupportedCapability26 } from "@vibeorm/schema";
|
|
3723
4159
|
function renderPostgresExplain(params) {
|
|
3724
4160
|
const { dialect, query, options } = params;
|
|
3725
4161
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -3754,7 +4190,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
3754
4190
|
});
|
|
3755
4191
|
function requirePostgres(params) {
|
|
3756
4192
|
if (params.dialect.name !== "postgres") {
|
|
3757
|
-
throw
|
|
4193
|
+
throw unsupportedCapability26({
|
|
3758
4194
|
dialect: params.dialect.name,
|
|
3759
4195
|
feature: params.feature,
|
|
3760
4196
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -3772,7 +4208,7 @@ function renderPostgresWorkload(params) {
|
|
|
3772
4208
|
if (query === "statementSnapshot") {
|
|
3773
4209
|
const bounded = limit ?? 20;
|
|
3774
4210
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
3775
|
-
throw new
|
|
4211
|
+
throw new VibeError23({
|
|
3776
4212
|
code: "VIBE_CONFIG",
|
|
3777
4213
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
3778
4214
|
meta: { limit }
|
|
@@ -3791,7 +4227,7 @@ function renderPostgresWorkload(params) {
|
|
|
3791
4227
|
function requireArg(params) {
|
|
3792
4228
|
const { value, name, query } = params;
|
|
3793
4229
|
if (value === undefined || value.length === 0) {
|
|
3794
|
-
throw new
|
|
4230
|
+
throw new VibeError23({
|
|
3795
4231
|
code: "VIBE_CONFIG",
|
|
3796
4232
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
3797
4233
|
meta: { query, argument: name }
|
|
@@ -3801,10 +4237,10 @@ function requireArg(params) {
|
|
|
3801
4237
|
}
|
|
3802
4238
|
|
|
3803
4239
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
3804
|
-
import { unsupportedCapability as
|
|
4240
|
+
import { unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
3805
4241
|
|
|
3806
4242
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
3807
|
-
import { RlsContextError, VibeError as
|
|
4243
|
+
import { RlsContextError, VibeError as VibeError24, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability27, validateRlsContextIR } from "@vibeorm/schema";
|
|
3808
4244
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
3809
4245
|
var COLUMN_TYPES = {
|
|
3810
4246
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -3817,18 +4253,18 @@ var COLUMN_TYPES = {
|
|
|
3817
4253
|
};
|
|
3818
4254
|
function requireRls(params) {
|
|
3819
4255
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
3820
|
-
throw
|
|
4256
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
3821
4257
|
}
|
|
3822
4258
|
}
|
|
3823
4259
|
function identifier2(params) {
|
|
3824
4260
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
3825
|
-
throw new
|
|
4261
|
+
throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
3826
4262
|
}
|
|
3827
4263
|
return params.dialect.quoteIdent({ name: params.name });
|
|
3828
4264
|
}
|
|
3829
4265
|
function tableRef(params) {
|
|
3830
4266
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
3831
|
-
throw
|
|
4267
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
3832
4268
|
}
|
|
3833
4269
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
3834
4270
|
}
|
|
@@ -3869,7 +4305,7 @@ function renderRlsColumnType(params) {
|
|
|
3869
4305
|
if (native !== undefined)
|
|
3870
4306
|
return native;
|
|
3871
4307
|
}
|
|
3872
|
-
throw
|
|
4308
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
3873
4309
|
}
|
|
3874
4310
|
function protectedModel(params) {
|
|
3875
4311
|
requireRls(params);
|
|
@@ -3877,7 +4313,7 @@ function protectedModel(params) {
|
|
|
3877
4313
|
assertValidSchemaIR({ schema: params.schema });
|
|
3878
4314
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
3879
4315
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
3880
|
-
throw new
|
|
4316
|
+
throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
3881
4317
|
}
|
|
3882
4318
|
return model;
|
|
3883
4319
|
}
|
|
@@ -3910,7 +4346,7 @@ function predicate(params) {
|
|
|
3910
4346
|
}).join(" AND ");
|
|
3911
4347
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
3912
4348
|
}
|
|
3913
|
-
throw new
|
|
4349
|
+
throw new VibeError24({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
3914
4350
|
}
|
|
3915
4351
|
function renderRlsPolicy(params) {
|
|
3916
4352
|
const model = protectedModel(params);
|
|
@@ -3951,7 +4387,7 @@ function renderRlsContext(params) {
|
|
|
3951
4387
|
}
|
|
3952
4388
|
function runtimeRoleOf(params) {
|
|
3953
4389
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
3954
|
-
throw new
|
|
4390
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
3955
4391
|
}
|
|
3956
4392
|
return params.role;
|
|
3957
4393
|
}
|
|
@@ -4108,7 +4544,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
4108
4544
|
});
|
|
4109
4545
|
function requireRls2(params) {
|
|
4110
4546
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
4111
|
-
throw
|
|
4547
|
+
throw unsupportedCapability28({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
4112
4548
|
}
|
|
4113
4549
|
}
|
|
4114
4550
|
function renderRlsReadinessQuery(params) {
|
|
@@ -4140,7 +4576,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4140
4576
|
}
|
|
4141
4577
|
|
|
4142
4578
|
// packages/sql/src/render.ts
|
|
4143
|
-
import { assertSqlNamespace as assertSqlNamespace3, internalError as
|
|
4579
|
+
import { assertSqlNamespace as assertSqlNamespace3, internalError as internalError5, unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
|
|
4144
4580
|
|
|
4145
4581
|
// packages/sql/src/like.ts
|
|
4146
4582
|
function escapeLikePattern(params) {
|
|
@@ -4228,21 +4664,29 @@ function renderStatement(params) {
|
|
|
4228
4664
|
}
|
|
4229
4665
|
}
|
|
4230
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
|
+
}
|
|
4231
4673
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4232
4674
|
const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
|
|
4233
4675
|
if (column.truncate !== undefined)
|
|
4234
4676
|
return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
|
|
4677
|
+
if (column.groupKey !== undefined)
|
|
4678
|
+
return renderGroupKey({ dialect, reference: ref, key: column.groupKey });
|
|
4235
4679
|
if (column.enumOrdinal === true)
|
|
4236
4680
|
return dialect.renderEnumOrdinal({ column: ref });
|
|
4237
4681
|
if (column.castReal === true) {
|
|
4238
4682
|
if (dialect.name !== "sqlite") {
|
|
4239
|
-
throw
|
|
4683
|
+
throw internalError5({ message: "ColRef.castReal is a sqlite Decimal construct" });
|
|
4240
4684
|
}
|
|
4241
4685
|
return `CAST(${ref} AS REAL)`;
|
|
4242
4686
|
}
|
|
4243
4687
|
if (column.castJsonb !== undefined) {
|
|
4244
4688
|
if (dialect.name !== "postgres") {
|
|
4245
|
-
throw
|
|
4689
|
+
throw internalError5({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
4246
4690
|
}
|
|
4247
4691
|
return `${ref}::${column.castJsonb}`;
|
|
4248
4692
|
}
|
|
@@ -4250,7 +4694,9 @@ function quoteCol(dialect, column) {
|
|
|
4250
4694
|
}
|
|
4251
4695
|
function typedOrderRef(params) {
|
|
4252
4696
|
const { statement, column } = params;
|
|
4253
|
-
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)
|
|
4254
4700
|
return column;
|
|
4255
4701
|
const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
|
|
4256
4702
|
return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
|
|
@@ -4259,14 +4705,14 @@ function jsonCellCast(dialect, cell) {
|
|
|
4259
4705
|
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
4260
4706
|
return dialect.jsonParamCast;
|
|
4261
4707
|
if (dialect.name !== "postgres") {
|
|
4262
|
-
throw
|
|
4708
|
+
throw internalError5({ message: "JsonParamCell storage json is a postgres construct" });
|
|
4263
4709
|
}
|
|
4264
4710
|
return "::text::json";
|
|
4265
4711
|
}
|
|
4266
4712
|
function quoteProjection(dialect, column) {
|
|
4267
4713
|
if (column.transform === undefined) {
|
|
4268
4714
|
const base = quoteCol(dialect, column);
|
|
4269
|
-
const alias = column.alias ?? (column.truncate === undefined ? undefined : column.name);
|
|
4715
|
+
const alias = column.alias ?? (column.truncate === undefined && column.groupKey === undefined ? undefined : column.name);
|
|
4270
4716
|
if (alias === undefined)
|
|
4271
4717
|
return base;
|
|
4272
4718
|
return `${base} AS ${dialect.quoteIdent({ name: alias })}`;
|
|
@@ -4283,7 +4729,7 @@ function quoteProjection(dialect, column) {
|
|
|
4283
4729
|
if (column.transform === "encodeBase64" && dialect.name === "mysql")
|
|
4284
4730
|
return `TO_BASE64(${ref}) AS ${alias}`;
|
|
4285
4731
|
if (dialect.name !== "postgres") {
|
|
4286
|
-
throw
|
|
4732
|
+
throw internalError5({ message: `${column.transform} is a postgres construct` });
|
|
4287
4733
|
}
|
|
4288
4734
|
if (column.transform === "encodeArrayBase64")
|
|
4289
4735
|
return `encode(array_send(${ref}), 'base64') AS ${alias}`;
|
|
@@ -4307,7 +4753,7 @@ function renderTable(dialect, table) {
|
|
|
4307
4753
|
}
|
|
4308
4754
|
function assertCount(value, what) {
|
|
4309
4755
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4310
|
-
throw
|
|
4756
|
+
throw internalError5({ message: `${what} must be a non-negative integer, got ${value}` });
|
|
4311
4757
|
}
|
|
4312
4758
|
}
|
|
4313
4759
|
var COMPARISON_SQL2 = {
|
|
@@ -4331,7 +4777,9 @@ var AGG_FN_SQL = {
|
|
|
4331
4777
|
min: "MIN",
|
|
4332
4778
|
max: "MAX"
|
|
4333
4779
|
};
|
|
4334
|
-
function renderAggRef(dialect, agg) {
|
|
4780
|
+
function renderAggRef(dialect, agg, collector) {
|
|
4781
|
+
if (agg.filter !== undefined)
|
|
4782
|
+
return renderFilteredAggregate({ dialect, agg, filter: agg.filter, collector });
|
|
4335
4783
|
if (agg.fn === "countAll")
|
|
4336
4784
|
return "COUNT(*)";
|
|
4337
4785
|
const column = quoteCol(dialect, agg.column);
|
|
@@ -4340,10 +4788,112 @@ function renderAggRef(dialect, agg) {
|
|
|
4340
4788
|
}
|
|
4341
4789
|
return `${AGG_FN_SQL[agg.fn]}(${column})`;
|
|
4342
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
|
+
}
|
|
4343
4893
|
function assertScalarArrays(dialect, feature) {
|
|
4344
4894
|
if (dialect.capabilities.scalarArrays)
|
|
4345
4895
|
return;
|
|
4346
|
-
throw
|
|
4896
|
+
throw unsupportedCapability29({
|
|
4347
4897
|
dialect: dialect.name,
|
|
4348
4898
|
feature,
|
|
4349
4899
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -4355,7 +4905,7 @@ function renderExpr(params) {
|
|
|
4355
4905
|
case "cmp": {
|
|
4356
4906
|
const op = COMPARISON_SQL2[expr.op];
|
|
4357
4907
|
if (op === undefined) {
|
|
4358
|
-
throw
|
|
4908
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4359
4909
|
}
|
|
4360
4910
|
if (isDbNowMarker(expr.value)) {
|
|
4361
4911
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
@@ -4366,7 +4916,7 @@ function renderExpr(params) {
|
|
|
4366
4916
|
case "cmpCol": {
|
|
4367
4917
|
const op = COMPARISON_SQL2[expr.op];
|
|
4368
4918
|
if (op === undefined) {
|
|
4369
|
-
throw
|
|
4919
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4370
4920
|
}
|
|
4371
4921
|
return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
|
|
4372
4922
|
}
|
|
@@ -4390,11 +4940,11 @@ function renderExpr(params) {
|
|
|
4390
4940
|
}
|
|
4391
4941
|
case "tupleIn": {
|
|
4392
4942
|
if (expr.columns.length === 0) {
|
|
4393
|
-
throw
|
|
4943
|
+
throw internalError5({ message: "tupleIn needs at least one column" });
|
|
4394
4944
|
}
|
|
4395
4945
|
for (const row of expr.rows) {
|
|
4396
4946
|
if (row.length !== expr.columns.length) {
|
|
4397
|
-
throw
|
|
4947
|
+
throw internalError5({
|
|
4398
4948
|
message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
|
|
4399
4949
|
});
|
|
4400
4950
|
}
|
|
@@ -4420,7 +4970,7 @@ function renderExpr(params) {
|
|
|
4420
4970
|
case "rowCompare": {
|
|
4421
4971
|
const op = COMPARISON_SQL2[expr.op];
|
|
4422
4972
|
if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
|
|
4423
|
-
throw
|
|
4973
|
+
throw internalError5({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
|
|
4424
4974
|
}
|
|
4425
4975
|
const columns = expr.columns.map((column) => quoteCol(dialect, column));
|
|
4426
4976
|
const placeholders = expr.values.map((value) => collector.add(value));
|
|
@@ -4428,9 +4978,9 @@ function renderExpr(params) {
|
|
|
4428
4978
|
}
|
|
4429
4979
|
case "inSelect": {
|
|
4430
4980
|
if (expr.columns.length === 0)
|
|
4431
|
-
throw
|
|
4981
|
+
throw internalError5({ message: "inSelect needs at least one column" });
|
|
4432
4982
|
if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
|
|
4433
|
-
throw
|
|
4983
|
+
throw unsupportedCapability29({
|
|
4434
4984
|
dialect: dialect.name,
|
|
4435
4985
|
feature: "LIMIT inside an IN subquery",
|
|
4436
4986
|
hint: "mysql refuses LIMIT directly inside an IN subquery (error 1235); wrap the limited select in a derived table"
|
|
@@ -4501,9 +5051,9 @@ function renderExpr(params) {
|
|
|
4501
5051
|
case "aggCmp": {
|
|
4502
5052
|
const op = COMPARISON_SQL2[expr.op];
|
|
4503
5053
|
if (op === undefined) {
|
|
4504
|
-
throw
|
|
5054
|
+
throw internalError5({ message: `unknown comparison operator "${expr.op}"` });
|
|
4505
5055
|
}
|
|
4506
|
-
return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
|
|
5056
|
+
return `${renderAggRef(dialect, expr.agg, collector)} ${op} ${collector.add(expr.value)}`;
|
|
4507
5057
|
}
|
|
4508
5058
|
case "arrayHas": {
|
|
4509
5059
|
assertScalarArrays(dialect, 'the scalar-list filter "has"');
|
|
@@ -4556,19 +5106,6 @@ function renderFragment(params) {
|
|
|
4556
5106
|
}
|
|
4557
5107
|
return text;
|
|
4558
5108
|
}
|
|
4559
|
-
function jsonPathString(path) {
|
|
4560
|
-
let out = "$";
|
|
4561
|
-
for (const segment of path) {
|
|
4562
|
-
if (typeof segment === "number") {
|
|
4563
|
-
out += `[${segment}]`;
|
|
4564
|
-
} else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
|
|
4565
|
-
out += `.${segment}`;
|
|
4566
|
-
} else {
|
|
4567
|
-
out += `."${segment.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
return out;
|
|
4571
|
-
}
|
|
4572
5109
|
function listComparison(params) {
|
|
4573
5110
|
const { dialect, column, value, collector } = params;
|
|
4574
5111
|
if (isJsonListParameter(value) && value.storage === "json") {
|
|
@@ -4583,13 +5120,13 @@ function renderKeyList(params) {
|
|
|
4583
5120
|
const { dialect, expr, collector } = params;
|
|
4584
5121
|
const arity = expr.columns.length;
|
|
4585
5122
|
if (arity === 0)
|
|
4586
|
-
throw
|
|
5123
|
+
throw internalError5({ message: "keyList needs at least one column" });
|
|
4587
5124
|
if (expr.members.length !== arity) {
|
|
4588
|
-
throw
|
|
5125
|
+
throw internalError5({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
|
|
4589
5126
|
}
|
|
4590
5127
|
for (const row of expr.rows) {
|
|
4591
5128
|
if (row.length !== arity)
|
|
4592
|
-
throw
|
|
5129
|
+
throw internalError5({ message: `keyList row has ${row.length} values for ${arity} columns` });
|
|
4593
5130
|
}
|
|
4594
5131
|
if (expr.rows.length === 0)
|
|
4595
5132
|
return "1 = 0";
|
|
@@ -4621,25 +5158,25 @@ function renderKeyPage(params) {
|
|
|
4621
5158
|
const { select } = page;
|
|
4622
5159
|
const arity = page.keyColumns.length;
|
|
4623
5160
|
if (arity === 0 || page.members.length !== arity) {
|
|
4624
|
-
throw
|
|
5161
|
+
throw internalError5({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
|
|
4625
5162
|
}
|
|
4626
5163
|
if (page.rows.length === 0)
|
|
4627
|
-
throw
|
|
5164
|
+
throw internalError5({ message: "keyPage needs at least one key" });
|
|
4628
5165
|
for (const row of page.rows) {
|
|
4629
5166
|
if (row.length !== arity)
|
|
4630
|
-
throw
|
|
5167
|
+
throw internalError5({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
|
|
4631
5168
|
}
|
|
4632
5169
|
if (page.identity.length === 0)
|
|
4633
|
-
throw
|
|
5170
|
+
throw internalError5({ message: "keyPage needs the child's identity columns" });
|
|
4634
5171
|
const order = select.orderBy ?? [];
|
|
4635
5172
|
if (order.length === 0 || order.some((term) => !("column" in term))) {
|
|
4636
|
-
throw
|
|
5173
|
+
throw internalError5({ message: "keyPage needs a non-empty order of plain column terms" });
|
|
4637
5174
|
}
|
|
4638
5175
|
if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
|
|
4639
|
-
throw
|
|
5176
|
+
throw internalError5({ message: "keyPage select carries a member the per-key page cannot render" });
|
|
4640
5177
|
}
|
|
4641
5178
|
if (dialect.keyPageStrategy === "window") {
|
|
4642
|
-
throw
|
|
5179
|
+
throw unsupportedCapability29({
|
|
4643
5180
|
dialect: dialect.name,
|
|
4644
5181
|
feature: "a per-key page (keyPage)",
|
|
4645
5182
|
hint: "the runtime keeps the ROW_NUMBER window on this dialect"
|
|
@@ -4667,7 +5204,7 @@ function renderKeyPage(params) {
|
|
|
4667
5204
|
const keys = dialect.quoteIdent({ name: keyAlias });
|
|
4668
5205
|
if (dialect.keyPageStrategy === "lateral") {
|
|
4669
5206
|
if (keyListBindsUntypedRows({ members: page.members })) {
|
|
4670
|
-
throw
|
|
5207
|
+
throw unsupportedCapability29({
|
|
4671
5208
|
dialect: dialect.name,
|
|
4672
5209
|
feature: "a per-key page over a DateTime key without a declared native type",
|
|
4673
5210
|
hint: "the runtime keeps the ROW_NUMBER window for such keys"
|
|
@@ -4695,22 +5232,22 @@ function renderKeyPage(params) {
|
|
|
4695
5232
|
function renderKeyRows(params) {
|
|
4696
5233
|
const { dialect, statement, keys, collector } = params;
|
|
4697
5234
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined) {
|
|
4698
|
-
throw
|
|
5235
|
+
throw internalError5({ message: "fromKeys uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion and table.alias must be unset" });
|
|
4699
5236
|
}
|
|
4700
5237
|
if (statement.rowLock !== undefined)
|
|
4701
|
-
throw
|
|
5238
|
+
throw internalError5({ message: "a locking read cannot read from key rows" });
|
|
4702
5239
|
const arity = keys.columns.length;
|
|
4703
5240
|
if (arity === 0 || keys.members.length !== arity) {
|
|
4704
|
-
throw
|
|
5241
|
+
throw internalError5({ message: `fromKeys has ${keys.members.length} members for ${arity} columns` });
|
|
4705
5242
|
}
|
|
4706
5243
|
if (keys.rows.length === 0)
|
|
4707
|
-
throw
|
|
5244
|
+
throw internalError5({ message: "fromKeys needs at least one key row" });
|
|
4708
5245
|
for (const row of keys.rows) {
|
|
4709
5246
|
if (row.length !== arity)
|
|
4710
|
-
throw
|
|
5247
|
+
throw internalError5({ message: `fromKeys row has ${row.length} values for ${arity} columns` });
|
|
4711
5248
|
}
|
|
4712
5249
|
if (dialect.keyListStrategy !== "unnest" || keyListBindsUntypedRows({ members: keys.members })) {
|
|
4713
|
-
throw
|
|
5250
|
+
throw unsupportedCapability29({
|
|
4714
5251
|
dialect: dialect.name,
|
|
4715
5252
|
feature: dialect.keyListStrategy !== "unnest" ? "a key-rows FROM (fromKeys)" : "a key-rows FROM over a DateTime key without a declared native type",
|
|
4716
5253
|
hint: "the runtime keeps the per-relation loads for this write"
|
|
@@ -4726,20 +5263,20 @@ function renderUnionPage(params) {
|
|
|
4726
5263
|
const { dialect, statement, collector } = params;
|
|
4727
5264
|
const branches = statement.fromUnion ?? [];
|
|
4728
5265
|
if (branches.length < 2)
|
|
4729
|
-
throw
|
|
5266
|
+
throw internalError5({ message: "fromUnion needs at least two branches" });
|
|
4730
5267
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
|
|
4731
|
-
throw
|
|
5268
|
+
throw internalError5({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
|
|
4732
5269
|
}
|
|
4733
5270
|
if (statement.rowLock !== undefined)
|
|
4734
|
-
throw
|
|
5271
|
+
throw internalError5({ message: "a locking read cannot read from a union page" });
|
|
4735
5272
|
const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
|
|
4736
5273
|
const names = outputNames(branches[0]);
|
|
4737
5274
|
return branches.map((branch, index) => {
|
|
4738
5275
|
if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
|
|
4739
|
-
throw
|
|
5276
|
+
throw internalError5({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
|
|
4740
5277
|
}
|
|
4741
5278
|
if (outputNames(branch) !== names)
|
|
4742
|
-
throw
|
|
5279
|
+
throw internalError5({ message: "fromUnion branches must project the same output names in the same order" });
|
|
4743
5280
|
const inner = renderSelect({ dialect, statement: branch, collector });
|
|
4744
5281
|
return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
|
|
4745
5282
|
}).join(" UNION ALL ");
|
|
@@ -4747,7 +5284,7 @@ function renderUnionPage(params) {
|
|
|
4747
5284
|
function jsonStringPattern(params) {
|
|
4748
5285
|
const { expr } = params;
|
|
4749
5286
|
if (typeof expr.value !== "string") {
|
|
4750
|
-
throw
|
|
5287
|
+
throw internalError5({ message: `json ${expr.op} expects a string operand` });
|
|
4751
5288
|
}
|
|
4752
5289
|
if (expr.op === "string_contains")
|
|
4753
5290
|
return containsPattern({ raw: expr.value });
|
|
@@ -4768,10 +5305,14 @@ function renderJsonLike(params) {
|
|
|
4768
5305
|
base = `${base}${dialect.likeEscapeClause}`;
|
|
4769
5306
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
4770
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
|
+
}
|
|
4771
5312
|
function renderJsonFilter(params) {
|
|
4772
5313
|
const { dialect, expr, collector } = params;
|
|
4773
5314
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
4774
|
-
throw
|
|
5315
|
+
throw unsupportedCapability29({
|
|
4775
5316
|
dialect: dialect.name,
|
|
4776
5317
|
feature: `the json filter "${expr.op}"`,
|
|
4777
5318
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -4783,6 +5324,16 @@ function renderJsonFilter(params) {
|
|
|
4783
5324
|
const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
|
|
4784
5325
|
const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
|
|
4785
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
|
+
}
|
|
4786
5337
|
case "equals":
|
|
4787
5338
|
return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
4788
5339
|
case "string_contains":
|
|
@@ -4820,6 +5371,8 @@ function renderJsonFilter(params) {
|
|
|
4820
5371
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
4821
5372
|
};
|
|
4822
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 });
|
|
4823
5376
|
case "equals":
|
|
4824
5377
|
return compareExtracted(jsonPathString(expr.path), expr.value);
|
|
4825
5378
|
case "string_contains":
|
|
@@ -4838,7 +5391,7 @@ function renderJsonFilter(params) {
|
|
|
4838
5391
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
4839
5392
|
const tests = elements.map((element) => {
|
|
4840
5393
|
if (element === null || typeof element === "object" && element !== null) {
|
|
4841
|
-
throw
|
|
5394
|
+
throw unsupportedCapability29({
|
|
4842
5395
|
dialect: dialect.name,
|
|
4843
5396
|
feature: "json array_contains with object/array/null elements",
|
|
4844
5397
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -4852,12 +5405,16 @@ function renderJsonFilter(params) {
|
|
|
4852
5405
|
return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
|
|
4853
5406
|
}
|
|
4854
5407
|
default:
|
|
4855
|
-
throw
|
|
5408
|
+
throw internalError5({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
|
|
4856
5409
|
}
|
|
4857
5410
|
}
|
|
4858
5411
|
const column = quoteCol(dialect, expr.column);
|
|
4859
5412
|
const pathString = jsonPathString(expr.path);
|
|
4860
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
|
+
}
|
|
4861
5418
|
case "equals":
|
|
4862
5419
|
return `JSON_EXTRACT(${column}, ${collector.add(pathString)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
|
|
4863
5420
|
case "string_contains":
|
|
@@ -4874,10 +5431,18 @@ function renderJsonFilter(params) {
|
|
|
4874
5431
|
return `JSON_EXTRACT(${column}, ${collector.add(`${pathString}[last]`)}) ${cmp} CAST(${collector.add(JSON.stringify(expr.value))} AS JSON)`;
|
|
4875
5432
|
}
|
|
4876
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
|
+
}
|
|
4877
5442
|
function renderOrderTerm(params) {
|
|
4878
|
-
const { dialect, term, collector } = params;
|
|
5443
|
+
const { dialect, term, collector, nullsOutputName } = params;
|
|
4879
5444
|
if ("agg" in term) {
|
|
4880
|
-
return `${renderAggRef(dialect, term.agg)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
|
|
5445
|
+
return `${renderAggRef(dialect, term.agg, collector)} ${term.direction === "asc" ? "ASC" : "DESC"}`;
|
|
4881
5446
|
}
|
|
4882
5447
|
const expression = "select" in term ? () => renderScalarSubquery({ dialect, subquery: term, collector }) : () => quoteCol(dialect, term.column);
|
|
4883
5448
|
const direction = term.direction === "asc" ? "ASC" : "DESC";
|
|
@@ -4890,7 +5455,7 @@ function renderOrderTerm(params) {
|
|
|
4890
5455
|
if (term.nulls === "first" === (term.direction === "asc")) {
|
|
4891
5456
|
return `${expression()} ${direction}`;
|
|
4892
5457
|
}
|
|
4893
|
-
const tested = expression();
|
|
5458
|
+
const tested = nullsOutputName === undefined ? expression() : dialect.quoteIdent({ name: nullsOutputName });
|
|
4894
5459
|
const nullsTerm = term.nulls === "first" ? `${tested} IS NULL DESC` : `${tested} IS NULL`;
|
|
4895
5460
|
return `${nullsTerm}, ${expression()} ${direction}`;
|
|
4896
5461
|
}
|
|
@@ -4907,11 +5472,11 @@ function renderPositionalRow(params) {
|
|
|
4907
5472
|
const position = join.select.rowNumber;
|
|
4908
5473
|
const positioned = position !== undefined && json.order === "positionSlot" && join.agg === "jsonArray" && position.partitionBy.length === 0 && position.orderBy.length > 0;
|
|
4909
5474
|
if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || position !== undefined && !positioned) {
|
|
4910
|
-
throw
|
|
5475
|
+
throw internalError5({ message: "positional lateral rows require an explicit column-only projection" });
|
|
4911
5476
|
}
|
|
4912
5477
|
const names = [...join.select.columns.map((column) => projectionOutputName({ column })), ...positioned ? [position.alias] : []];
|
|
4913
5478
|
if (new Set(names).size !== names.length) {
|
|
4914
|
-
throw
|
|
5479
|
+
throw internalError5({ message: "positional lateral projection has duplicate output names" });
|
|
4915
5480
|
}
|
|
4916
5481
|
const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
|
|
4917
5482
|
const chunks = [];
|
|
@@ -4919,14 +5484,14 @@ function renderPositionalRow(params) {
|
|
|
4919
5484
|
chunks.push(`${json.rowArray}(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
|
|
4920
5485
|
}
|
|
4921
5486
|
if (chunks.length > JSON_TRANSPORT.chunkSize) {
|
|
4922
|
-
throw
|
|
5487
|
+
throw internalError5({ message: "positional lateral projection exceeds the transport width limit" });
|
|
4923
5488
|
}
|
|
4924
5489
|
return chunks.length === 1 ? chunks[0] : `${json.rowArray}(${chunks.join(", ")})`;
|
|
4925
5490
|
}
|
|
4926
5491
|
function lateralJsonOf(params) {
|
|
4927
5492
|
const json = params.dialect.lateralJson;
|
|
4928
5493
|
if (json === null)
|
|
4929
|
-
throw
|
|
5494
|
+
throw internalError5({ message: "lateral JSON rendering on a dialect without LATERAL" });
|
|
4930
5495
|
return json;
|
|
4931
5496
|
}
|
|
4932
5497
|
function renderLateralJoin(params) {
|
|
@@ -4934,7 +5499,7 @@ function renderLateralJoin(params) {
|
|
|
4934
5499
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
4935
5500
|
if (join.agg === "count") {
|
|
4936
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) {
|
|
4937
|
-
throw
|
|
5502
|
+
throw internalError5({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
|
|
4938
5503
|
}
|
|
4939
5504
|
return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
|
|
4940
5505
|
}
|
|
@@ -4946,12 +5511,14 @@ function renderLateralJoin(params) {
|
|
|
4946
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)));
|
|
4947
5512
|
const terms = join.select.orderBy;
|
|
4948
5513
|
const columnTerms = terms.filter((term) => ("column" in term));
|
|
4949
|
-
|
|
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))) {
|
|
4950
5517
|
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
4951
5518
|
dialect,
|
|
4952
5519
|
term: {
|
|
4953
5520
|
...term,
|
|
4954
|
-
column:
|
|
5521
|
+
column: term.column.coalesce === undefined ? subRef(term.column) : { ...term.column, coalesce: term.column.coalesce.map(subRef) }
|
|
4955
5522
|
},
|
|
4956
5523
|
collector
|
|
4957
5524
|
}));
|
|
@@ -4959,7 +5526,7 @@ function renderLateralJoin(params) {
|
|
|
4959
5526
|
}
|
|
4960
5527
|
}
|
|
4961
5528
|
if (join.rowFormat !== "positional" && json.order !== "inAggregate") {
|
|
4962
|
-
throw
|
|
5529
|
+
throw unsupportedCapability29({ dialect: dialect.name, feature: "object-format lateral rows", hint: 'lateral relation rows render positional (rowFormat "positional") on this dialect' });
|
|
4963
5530
|
}
|
|
4964
5531
|
const rowJson = join.rowFormat === "positional" ? renderPositionalRow({ dialect, join, subAlias }) : `to_json(${subAlias}.*)`;
|
|
4965
5532
|
const projection = join.agg === "jsonArray" ? `COALESCE(${json.arrayAgg}(${rowJson}${aggOrder}), ${json.emptyArray}) AS ${columnAlias}` : `${rowJson} AS ${columnAlias}`;
|
|
@@ -4970,7 +5537,7 @@ function renderCountSubquery(params) {
|
|
|
4970
5537
|
const { dialect, subquery, collector } = params;
|
|
4971
5538
|
const select = subquery.select;
|
|
4972
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) {
|
|
4973
|
-
throw
|
|
5540
|
+
throw internalError5({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
4974
5541
|
}
|
|
4975
5542
|
const inner = renderSelect({ dialect, statement: select, collector });
|
|
4976
5543
|
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
@@ -4988,7 +5555,10 @@ function applyReturning(params) {
|
|
|
4988
5555
|
if (!dialect.capabilities.returning) {
|
|
4989
5556
|
const capture = requested.find(isCaptureReturningColumn);
|
|
4990
5557
|
if (capture !== undefined)
|
|
4991
|
-
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 });
|
|
4992
5562
|
return { suffix: "", status: "omitted" };
|
|
4993
5563
|
}
|
|
4994
5564
|
const read = (name) => params.qualify === undefined ? name : `${params.qualify}.${name}`;
|
|
@@ -4996,7 +5566,9 @@ function applyReturning(params) {
|
|
|
4996
5566
|
if (typeof column === "string")
|
|
4997
5567
|
return read(dialect.quoteIdent({ name: column }));
|
|
4998
5568
|
if ("capture" in column)
|
|
4999
|
-
return renderCaptureReturning({ dialect, column });
|
|
5569
|
+
return renderCaptureReturning({ dialect, column, collector: params.collector });
|
|
5570
|
+
if ("from" in column)
|
|
5571
|
+
return renderOtherRowReturning({ dialect, column });
|
|
5000
5572
|
if ("transform" in column) {
|
|
5001
5573
|
const name = dialect.quoteIdent({ name: column.name });
|
|
5002
5574
|
return `${dialect.jsonTextProjection({ expression: read(name) })} AS ${name}`;
|
|
@@ -5009,40 +5581,88 @@ function applyReturning(params) {
|
|
|
5009
5581
|
function renderCaptureReturning(params) {
|
|
5010
5582
|
const { dialect, column } = params;
|
|
5011
5583
|
if (!dialect.capabilities.rowCapture) {
|
|
5012
|
-
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" });
|
|
5013
5585
|
}
|
|
5014
|
-
const name = dialect.quoteIdent({ name: column.name });
|
|
5015
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 });
|
|
5016
5595
|
if (column.capture === "inserted")
|
|
5017
5596
|
return `(old.${name} IS NULL) AS ${alias}`;
|
|
5018
|
-
|
|
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 })}`;
|
|
5019
5639
|
}
|
|
5020
5640
|
function renderKeyTableJoin(params) {
|
|
5021
5641
|
const { dialect, join, collector } = params;
|
|
5022
5642
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
5023
5643
|
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
5024
5644
|
if (!admitted) {
|
|
5025
|
-
throw
|
|
5645
|
+
throw unsupportedCapability29({
|
|
5026
5646
|
dialect: dialect.name,
|
|
5027
5647
|
feature: "relation key table",
|
|
5028
5648
|
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
5029
5649
|
});
|
|
5030
5650
|
}
|
|
5031
5651
|
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
5032
|
-
throw
|
|
5652
|
+
throw internalError5({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
5033
5653
|
}
|
|
5034
5654
|
if (join.rows.length === 0)
|
|
5035
|
-
throw
|
|
5655
|
+
throw internalError5({ message: "key table needs at least one key row" });
|
|
5036
5656
|
for (const member of join.members) {
|
|
5037
5657
|
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
5038
|
-
throw
|
|
5658
|
+
throw internalError5({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
5039
5659
|
}
|
|
5040
5660
|
}
|
|
5041
5661
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
5042
5662
|
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
5043
5663
|
const rows = join.rows.map((row, ordinal) => {
|
|
5044
5664
|
if (row.length !== join.members.length) {
|
|
5045
|
-
throw
|
|
5665
|
+
throw internalError5({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
5046
5666
|
}
|
|
5047
5667
|
const cells = row.map((value, index) => {
|
|
5048
5668
|
const placeholder = collector.add(value);
|
|
@@ -5072,10 +5692,10 @@ function renderKeyTableJoin(params) {
|
|
|
5072
5692
|
function renderLeftJoin(params) {
|
|
5073
5693
|
const { dialect, join, collector } = params;
|
|
5074
5694
|
if (join.on.length === 0) {
|
|
5075
|
-
throw
|
|
5695
|
+
throw internalError5({ message: "left join needs at least one ON pair" });
|
|
5076
5696
|
}
|
|
5077
5697
|
if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
|
|
5078
|
-
throw
|
|
5698
|
+
throw internalError5({ message: "a left join's derived select must alias every projected column" });
|
|
5079
5699
|
}
|
|
5080
5700
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
5081
5701
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
@@ -5084,10 +5704,10 @@ function renderLeftJoin(params) {
|
|
|
5084
5704
|
function renderSelect(params) {
|
|
5085
5705
|
const { dialect, statement, collector } = params;
|
|
5086
5706
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
5087
|
-
throw
|
|
5707
|
+
throw internalError5({ message: "only countAll can combine with a column list" });
|
|
5088
5708
|
}
|
|
5089
5709
|
if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
|
|
5090
|
-
throw
|
|
5710
|
+
throw internalError5({ message: "selectExpr and aggregates cannot combine" });
|
|
5091
5711
|
}
|
|
5092
5712
|
const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
|
|
5093
5713
|
if (statement.selectExpr !== undefined) {
|
|
@@ -5095,11 +5715,15 @@ function renderSelect(params) {
|
|
|
5095
5715
|
}
|
|
5096
5716
|
if (statement.aggregates !== undefined) {
|
|
5097
5717
|
for (const selection of statement.aggregates) {
|
|
5098
|
-
const expression = renderAggRef(dialect, selection.agg);
|
|
5718
|
+
const expression = renderAggRef(dialect, selection.agg, collector);
|
|
5099
5719
|
const projected = selection.castText === true ? textProjection({ dialect, expression }) : expression;
|
|
5100
5720
|
columnList.push(`${projected} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
5101
5721
|
}
|
|
5102
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
|
+
}
|
|
5103
5727
|
if (statement.countSubqueries !== undefined) {
|
|
5104
5728
|
for (const subquery of statement.countSubqueries) {
|
|
5105
5729
|
columnList.push(renderCountSubquery({ dialect, subquery, collector }));
|
|
@@ -5113,7 +5737,7 @@ function renderSelect(params) {
|
|
|
5113
5737
|
if (statement.rowNumber !== undefined) {
|
|
5114
5738
|
const rowNumber = statement.rowNumber;
|
|
5115
5739
|
if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
|
|
5116
|
-
throw
|
|
5740
|
+
throw internalError5({ message: "rowNumber needs a partition column or an order" });
|
|
5117
5741
|
}
|
|
5118
5742
|
const partition = rowNumber.partitionBy.length === 0 ? "" : `PARTITION BY ${rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
|
|
5119
5743
|
const order = rowNumber.orderBy.length === 0 ? "" : `${partition === "" ? "" : " "}ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
@@ -5121,11 +5745,11 @@ function renderSelect(params) {
|
|
|
5121
5745
|
}
|
|
5122
5746
|
let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
|
|
5123
5747
|
if (statement.distinct === true && statement.distinctOn !== undefined) {
|
|
5124
|
-
throw
|
|
5748
|
+
throw internalError5({ message: "distinct and distinctOn cannot combine" });
|
|
5125
5749
|
}
|
|
5126
5750
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
5127
5751
|
if (!dialect.capabilities.distinctOn) {
|
|
5128
|
-
throw
|
|
5752
|
+
throw unsupportedCapability29({
|
|
5129
5753
|
dialect: dialect.name,
|
|
5130
5754
|
feature: "SELECT DISTINCT ON",
|
|
5131
5755
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -5141,12 +5765,12 @@ function renderSelect(params) {
|
|
|
5141
5765
|
from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5142
5766
|
} else if (statement.keyPage !== undefined) {
|
|
5143
5767
|
if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
|
|
5144
|
-
throw
|
|
5768
|
+
throw internalError5({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
|
|
5145
5769
|
}
|
|
5146
5770
|
from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
5147
5771
|
} else if (statement.fromSelect !== undefined) {
|
|
5148
5772
|
if (statement.table.alias !== undefined) {
|
|
5149
|
-
throw
|
|
5773
|
+
throw internalError5({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
|
|
5150
5774
|
}
|
|
5151
5775
|
const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
|
|
5152
5776
|
from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
@@ -5157,7 +5781,7 @@ function renderSelect(params) {
|
|
|
5157
5781
|
if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
|
|
5158
5782
|
for (const join of statement.innerJoins) {
|
|
5159
5783
|
if (join.on.length === 0) {
|
|
5160
|
-
throw
|
|
5784
|
+
throw internalError5({ message: "inner join needs at least one ON pair" });
|
|
5161
5785
|
}
|
|
5162
5786
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
5163
5787
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
@@ -5168,7 +5792,7 @@ function renderSelect(params) {
|
|
|
5168
5792
|
}
|
|
5169
5793
|
if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
|
|
5170
5794
|
if (statement.rowLock !== undefined) {
|
|
5171
|
-
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" });
|
|
5172
5796
|
}
|
|
5173
5797
|
for (const join of statement.leftJoins) {
|
|
5174
5798
|
text += ` ${renderLeftJoin({ dialect, join, collector })}`;
|
|
@@ -5176,7 +5800,7 @@ function renderSelect(params) {
|
|
|
5176
5800
|
}
|
|
5177
5801
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
5178
5802
|
if (!dialect.capabilities.lateralJoin || dialect.lateralJson === null) {
|
|
5179
|
-
throw
|
|
5803
|
+
throw unsupportedCapability29({
|
|
5180
5804
|
dialect: dialect.name,
|
|
5181
5805
|
feature: "LATERAL JOIN relation loading",
|
|
5182
5806
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -5190,13 +5814,25 @@ function renderSelect(params) {
|
|
|
5190
5814
|
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5191
5815
|
}
|
|
5192
5816
|
if (statement.groupBy !== undefined && statement.groupBy.length > 0) {
|
|
5193
|
-
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" });
|
|
5194
5820
|
}
|
|
5195
5821
|
if (statement.having !== undefined) {
|
|
5196
5822
|
text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
|
|
5197
5823
|
}
|
|
5198
5824
|
if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
|
|
5199
|
-
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
|
+
});
|
|
5200
5836
|
text += ` ORDER BY ${terms.join(", ")}`;
|
|
5201
5837
|
}
|
|
5202
5838
|
if (statement.limit !== undefined) {
|
|
@@ -5213,10 +5849,10 @@ function renderSelect(params) {
|
|
|
5213
5849
|
if (statement.rowLock !== undefined) {
|
|
5214
5850
|
const target = statement.table.alias ?? statement.table.name;
|
|
5215
5851
|
if (statement.rowLock.of !== undefined && statement.rowLock.of.some((name) => name !== target)) {
|
|
5216
|
-
throw
|
|
5852
|
+
throw internalError5({ message: `a row lock's \`of\` list may name only the statement's own table "${target}"` });
|
|
5217
5853
|
}
|
|
5218
5854
|
if (statement.rowLock.of !== undefined && (statement.fromSelect !== undefined || statement.keyPage !== undefined)) {
|
|
5219
|
-
throw
|
|
5855
|
+
throw internalError5({ message: "a row lock's `of` list cannot name a derived table" });
|
|
5220
5856
|
}
|
|
5221
5857
|
const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
|
|
5222
5858
|
if (clause !== null)
|
|
@@ -5232,7 +5868,7 @@ function unnestElement(params) {
|
|
|
5232
5868
|
return null;
|
|
5233
5869
|
if (isJsonParamCell(value)) {
|
|
5234
5870
|
if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
|
|
5235
|
-
throw
|
|
5871
|
+
throw internalError5({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
|
|
5236
5872
|
}
|
|
5237
5873
|
return value.text;
|
|
5238
5874
|
}
|
|
@@ -5248,24 +5884,24 @@ function unnestElement(params) {
|
|
|
5248
5884
|
return value;
|
|
5249
5885
|
}
|
|
5250
5886
|
}
|
|
5251
|
-
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` });
|
|
5252
5888
|
}
|
|
5253
5889
|
function renderUnnestRows(params) {
|
|
5254
5890
|
const { dialect, statement, members, collector } = params;
|
|
5255
5891
|
if (dialect.bulkInsertForm !== "unnest") {
|
|
5256
|
-
throw
|
|
5892
|
+
throw unsupportedCapability29({
|
|
5257
5893
|
dialect: dialect.name,
|
|
5258
5894
|
feature: "a bulk insert bound as typed arrays (INSERT \u2026 SELECT FROM unnest)",
|
|
5259
5895
|
hint: "this dialect binds createMany rows as VALUES"
|
|
5260
5896
|
});
|
|
5261
5897
|
}
|
|
5262
5898
|
if (statement.columns.length === 0)
|
|
5263
|
-
throw
|
|
5899
|
+
throw internalError5({ message: "an unnest insert needs at least one column" });
|
|
5264
5900
|
if (members.length !== statement.columns.length) {
|
|
5265
|
-
throw
|
|
5901
|
+
throw internalError5({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
|
|
5266
5902
|
}
|
|
5267
5903
|
if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
|
|
5268
|
-
throw
|
|
5904
|
+
throw internalError5({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
|
|
5269
5905
|
}
|
|
5270
5906
|
const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
|
|
5271
5907
|
const arrays = members.map((member, index) => {
|
|
@@ -5281,14 +5917,14 @@ function renderUnnestRows(params) {
|
|
|
5281
5917
|
function renderInsert(params) {
|
|
5282
5918
|
const { dialect, statement, collector } = params;
|
|
5283
5919
|
if (statement.rows.length === 0) {
|
|
5284
|
-
throw
|
|
5920
|
+
throw internalError5({ message: "insert requires at least one row" });
|
|
5285
5921
|
}
|
|
5286
5922
|
const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
|
|
5287
5923
|
if (defaultValues && statement.rows.length !== 1) {
|
|
5288
|
-
throw
|
|
5924
|
+
throw internalError5({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
|
|
5289
5925
|
}
|
|
5290
5926
|
if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
|
|
5291
|
-
throw
|
|
5927
|
+
throw unsupportedCapability29({
|
|
5292
5928
|
dialect: dialect.name,
|
|
5293
5929
|
feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
|
|
5294
5930
|
hint: "supply at least one column value, or drop the conflict clause"
|
|
@@ -5296,7 +5932,7 @@ function renderInsert(params) {
|
|
|
5296
5932
|
}
|
|
5297
5933
|
for (const row of statement.rows) {
|
|
5298
5934
|
if (row.length !== statement.columns.length) {
|
|
5299
|
-
throw
|
|
5935
|
+
throw internalError5({
|
|
5300
5936
|
message: `insert row has ${row.length} values but ${statement.columns.length} columns`
|
|
5301
5937
|
});
|
|
5302
5938
|
}
|
|
@@ -5314,7 +5950,7 @@ function renderInsert(params) {
|
|
|
5314
5950
|
if (!isSqlDefaultCell(value))
|
|
5315
5951
|
return collector.add(value);
|
|
5316
5952
|
if (!dialect.rowDefaultKeyword) {
|
|
5317
|
-
throw
|
|
5953
|
+
throw internalError5({
|
|
5318
5954
|
message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
|
|
5319
5955
|
});
|
|
5320
5956
|
}
|
|
@@ -5330,7 +5966,7 @@ function renderInsert(params) {
|
|
|
5330
5966
|
let target = "";
|
|
5331
5967
|
if (onConflict.constraint !== undefined) {
|
|
5332
5968
|
if (dialect.name !== "postgres") {
|
|
5333
|
-
throw
|
|
5969
|
+
throw unsupportedCapability29({
|
|
5334
5970
|
dialect: dialect.name,
|
|
5335
5971
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
5336
5972
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -5346,7 +5982,7 @@ function renderInsert(params) {
|
|
|
5346
5982
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
5347
5983
|
} else {
|
|
5348
5984
|
if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
5349
|
-
throw
|
|
5985
|
+
throw unsupportedCapability29({
|
|
5350
5986
|
dialect: dialect.name,
|
|
5351
5987
|
feature: "a refined conflict target (constraint / partial-index predicate) on ON DUPLICATE KEY UPDATE",
|
|
5352
5988
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -5354,36 +5990,36 @@ function renderInsert(params) {
|
|
|
5354
5990
|
}
|
|
5355
5991
|
const keepName = onConflict.keepColumn ?? statement.columns[0];
|
|
5356
5992
|
if (keepName === undefined) {
|
|
5357
|
-
throw
|
|
5993
|
+
throw internalError5({ message: "a skip-duplicates insert of an all-default row must name its keepColumn" });
|
|
5358
5994
|
}
|
|
5359
5995
|
const keep = dialect.quoteIdent({ name: keepName });
|
|
5360
5996
|
const current = `${dialect.quoteIdent({ name: statement.table.name })}.${keep}`;
|
|
5361
5997
|
const flag = onConflict.duplicateFlag;
|
|
5362
5998
|
if (flag !== undefined && !/^\d{1,20}$/.test(flag)) {
|
|
5363
|
-
throw
|
|
5999
|
+
throw internalError5({ message: `duplicateFlag must be a decimal literal, got ${JSON.stringify(flag)}` });
|
|
5364
6000
|
}
|
|
5365
6001
|
text += flag === undefined ? ` ON DUPLICATE KEY UPDATE ${keep} = ${current}` : ` ON DUPLICATE KEY UPDATE ${keep} = IF(LAST_INSERT_ID(${flag}), ${current}, ${current})`;
|
|
5366
6002
|
}
|
|
5367
6003
|
} else {
|
|
5368
6004
|
const updates = Object.entries(onConflict.update);
|
|
5369
6005
|
if (updates.length === 0) {
|
|
5370
|
-
throw
|
|
6006
|
+
throw internalError5({ message: "upsert update must set at least one column" });
|
|
5371
6007
|
}
|
|
5372
6008
|
const selfRef = dialect.quoteIdent({ name: statement.table.name });
|
|
5373
6009
|
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
|
|
5374
6010
|
if (dialect.upsertForm === "onConflict") {
|
|
5375
6011
|
if (onConflict.lastInsertIdColumn !== undefined) {
|
|
5376
|
-
throw
|
|
6012
|
+
throw internalError5({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
5377
6013
|
}
|
|
5378
6014
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
5379
|
-
throw
|
|
6015
|
+
throw unsupportedCapability29({
|
|
5380
6016
|
dialect: dialect.name,
|
|
5381
6017
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
5382
6018
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
5383
6019
|
});
|
|
5384
6020
|
}
|
|
5385
6021
|
if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
|
|
5386
|
-
throw
|
|
6022
|
+
throw internalError5({
|
|
5387
6023
|
message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
|
|
5388
6024
|
});
|
|
5389
6025
|
}
|
|
@@ -5392,7 +6028,7 @@ function renderInsert(params) {
|
|
|
5392
6028
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
5393
6029
|
if (onConflict.updateWhere !== undefined) {
|
|
5394
6030
|
if (!dialect.capabilities.conflictUpdateWhere) {
|
|
5395
|
-
throw
|
|
6031
|
+
throw unsupportedCapability29({
|
|
5396
6032
|
dialect: dialect.name,
|
|
5397
6033
|
feature: "a guarded conflict update (ON CONFLICT \u2026 DO UPDATE \u2026 WHERE)",
|
|
5398
6034
|
hint: "run the guarded upsert as a find-then-write in a transaction instead"
|
|
@@ -5402,21 +6038,21 @@ function renderInsert(params) {
|
|
|
5402
6038
|
}
|
|
5403
6039
|
} else {
|
|
5404
6040
|
if (onConflict.updateWhere !== undefined) {
|
|
5405
|
-
throw
|
|
6041
|
+
throw unsupportedCapability29({
|
|
5406
6042
|
dialect: dialect.name,
|
|
5407
6043
|
feature: "a guarded conflict update (ON DUPLICATE KEY UPDATE has no WHERE)",
|
|
5408
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"
|
|
5409
6045
|
});
|
|
5410
6046
|
}
|
|
5411
6047
|
if (onConflict.constraint !== undefined) {
|
|
5412
|
-
throw
|
|
6048
|
+
throw unsupportedCapability29({
|
|
5413
6049
|
dialect: dialect.name,
|
|
5414
6050
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
5415
6051
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
5416
6052
|
});
|
|
5417
6053
|
}
|
|
5418
6054
|
if (onConflict.conflictWhere !== undefined) {
|
|
5419
|
-
throw
|
|
6055
|
+
throw unsupportedCapability29({
|
|
5420
6056
|
dialect: dialect.name,
|
|
5421
6057
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
5422
6058
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -5427,25 +6063,35 @@ function renderInsert(params) {
|
|
|
5427
6063
|
}
|
|
5428
6064
|
}
|
|
5429
6065
|
}
|
|
5430
|
-
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
6066
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector });
|
|
5431
6067
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5432
6068
|
}
|
|
5433
6069
|
function renderUpdate(params) {
|
|
5434
6070
|
const { dialect, statement, collector } = params;
|
|
5435
6071
|
const assignments = Object.entries(statement.set);
|
|
5436
6072
|
if (assignments.length === 0) {
|
|
5437
|
-
throw
|
|
6073
|
+
throw internalError5({ message: "update must set at least one column" });
|
|
5438
6074
|
}
|
|
5439
6075
|
const source = statement.from;
|
|
5440
6076
|
if (source !== undefined) {
|
|
5441
6077
|
if (statement.alias === undefined || statement.alias === source.alias)
|
|
5442
|
-
throw
|
|
6078
|
+
throw internalError5({ message: "UPDATE \u2026 FROM needs distinct target and source aliases" });
|
|
5443
6079
|
const target = dialect.quoteIdent({ name: statement.alias });
|
|
5444
6080
|
const joined = dialect.quoteIdent({ name: source.alias });
|
|
5445
6081
|
const assign = () => renderAssignments({ dialect, entries: assignments, collector, selfRef: target, source: joined, ...dialect.name === "mysql" ? { qualifyWrite: target } : {}, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
5446
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
|
+
}
|
|
5447
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()}`;
|
|
5448
|
-
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 } : {} });
|
|
5449
6095
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5450
6096
|
}
|
|
5451
6097
|
const setSql = renderAssignments({ dialect, entries: assignments, collector, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
@@ -5453,7 +6099,7 @@ function renderUpdate(params) {
|
|
|
5453
6099
|
if (statement.where !== undefined) {
|
|
5454
6100
|
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5455
6101
|
}
|
|
5456
|
-
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
6102
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector });
|
|
5457
6103
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5458
6104
|
}
|
|
5459
6105
|
function renderIncomingRowColumn(params) {
|
|
@@ -5469,23 +6115,23 @@ function renderAssignments(params) {
|
|
|
5469
6115
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
5470
6116
|
if (isSourceColumnRef(value)) {
|
|
5471
6117
|
if (params.source === undefined)
|
|
5472
|
-
throw
|
|
6118
|
+
throw internalError5({ message: `an UPDATE set value reads the joined source's column ${value.column}, but the statement joins no source` });
|
|
5473
6119
|
return `${column} = ${params.source}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5474
6120
|
}
|
|
5475
6121
|
if (isStepColumnRef(value)) {
|
|
5476
6122
|
if (params.steps === undefined || !params.steps.has(value.step)) {
|
|
5477
|
-
throw
|
|
6123
|
+
throw internalError5({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
|
|
5478
6124
|
}
|
|
5479
6125
|
return `${column} = (SELECT ${dialect.quoteIdent({ name: value.column })} FROM ${dialect.quoteIdent({ name: value.step })})`;
|
|
5480
6126
|
}
|
|
5481
6127
|
if (isSqlFragment(value)) {
|
|
5482
6128
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5483
|
-
throw
|
|
6129
|
+
throw internalError5({ message: "SQL assignment fragments require a conflict update" });
|
|
5484
6130
|
return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
|
|
5485
6131
|
}
|
|
5486
6132
|
if (isExcludedRef(value)) {
|
|
5487
6133
|
if (params.allowExcluded !== true) {
|
|
5488
|
-
throw
|
|
6134
|
+
throw internalError5({
|
|
5489
6135
|
message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
|
|
5490
6136
|
});
|
|
5491
6137
|
}
|
|
@@ -5493,9 +6139,12 @@ function renderAssignments(params) {
|
|
|
5493
6139
|
}
|
|
5494
6140
|
if (isSqlKeepCell(value)) {
|
|
5495
6141
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5496
|
-
throw
|
|
6142
|
+
throw internalError5({ message: "a keep-column cell is only meaningful inside an upsert's update arm" });
|
|
5497
6143
|
return `${column} = ${read}`;
|
|
5498
6144
|
}
|
|
6145
|
+
if (isJsonPatchOperation(value)) {
|
|
6146
|
+
return `${column} = ${renderJsonPatch({ dialect, patch: value, read, collector })}`;
|
|
6147
|
+
}
|
|
5499
6148
|
if (isJsonParamCell(value)) {
|
|
5500
6149
|
return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
5501
6150
|
}
|
|
@@ -5523,7 +6172,7 @@ function renderDelete(params) {
|
|
|
5523
6172
|
if (statement.where !== undefined) {
|
|
5524
6173
|
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
5525
6174
|
}
|
|
5526
|
-
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
6175
|
+
const returning = applyReturning({ dialect, requested: statement.returning, collector });
|
|
5527
6176
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
5528
6177
|
}
|
|
5529
6178
|
var CHAIN_ROWS_ALIAS = "__vibe_rows";
|
|
@@ -5531,19 +6180,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
|
5531
6180
|
function renderChain(params) {
|
|
5532
6181
|
const { dialect, statement, collector } = params;
|
|
5533
6182
|
if (dialect.statementChain === null) {
|
|
5534
|
-
throw
|
|
6183
|
+
throw unsupportedCapability29({
|
|
5535
6184
|
dialect: dialect.name,
|
|
5536
6185
|
feature: "a statement chain (data-modifying WITH steps)",
|
|
5537
6186
|
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
5538
6187
|
});
|
|
5539
6188
|
}
|
|
5540
6189
|
if (statement.steps.length === 0)
|
|
5541
|
-
throw
|
|
6190
|
+
throw internalError5({ message: "a statement chain needs at least one step" });
|
|
5542
6191
|
const defined = new Set;
|
|
5543
6192
|
const steps = [];
|
|
5544
6193
|
for (const step of statement.steps) {
|
|
5545
6194
|
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
5546
|
-
throw
|
|
6195
|
+
throw internalError5({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
5547
6196
|
}
|
|
5548
6197
|
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
5549
6198
|
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
@@ -5552,11 +6201,11 @@ function renderChain(params) {
|
|
|
5552
6201
|
const result = statement.result;
|
|
5553
6202
|
const captures = result.capture ?? [];
|
|
5554
6203
|
if (captures.length > 0 && !dialect.capabilities.rowCapture) {
|
|
5555
|
-
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" });
|
|
5556
6205
|
}
|
|
5557
6206
|
for (const alias of captureAliases({ captures })) {
|
|
5558
6207
|
if (result.columns.includes(alias) || alias === result.verdict?.alias || alias === result.tally?.alias) {
|
|
5559
|
-
throw
|
|
6208
|
+
throw internalError5({ message: `chain capture alias "${alias}" collides with a result column` });
|
|
5560
6209
|
}
|
|
5561
6210
|
}
|
|
5562
6211
|
if (result.rowsFrom.length === 0 && result.columns.length === 0 && result.tally !== undefined && result.verdict === undefined) {
|
|
@@ -5564,11 +6213,11 @@ function renderChain(params) {
|
|
|
5564
6213
|
return { text: `WITH ${steps.join(", ")} SELECT ${[tally, ...renderChainCaptures({ dialect, captures, defined, collector })].join(", ")}`, values: collector.values, returning: "none" };
|
|
5565
6214
|
}
|
|
5566
6215
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
5567
|
-
throw
|
|
6216
|
+
throw internalError5({ message: "a chain result needs at least one source step and one column" });
|
|
5568
6217
|
}
|
|
5569
6218
|
for (const name of result.rowsFrom) {
|
|
5570
6219
|
if (!defined.has(name))
|
|
5571
|
-
throw
|
|
6220
|
+
throw internalError5({ message: `chain result reads undefined step "${name}"` });
|
|
5572
6221
|
}
|
|
5573
6222
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
5574
6223
|
const guarded = result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0;
|
|
@@ -5579,7 +6228,7 @@ function renderChain(params) {
|
|
|
5579
6228
|
const extra = [];
|
|
5580
6229
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
5581
6230
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
5582
|
-
throw
|
|
6231
|
+
throw internalError5({ message: `chain result alias "${alias}" collides with a result column` });
|
|
5583
6232
|
}
|
|
5584
6233
|
}
|
|
5585
6234
|
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
@@ -5624,6 +6273,8 @@ function captureAliases(params) {
|
|
|
5624
6273
|
if (entry.inserted !== undefined)
|
|
5625
6274
|
out.push(`${entry.alias}_ia`);
|
|
5626
6275
|
}
|
|
6276
|
+
for (const index of (entry.cascades ?? []).keys())
|
|
6277
|
+
out.push(`${entry.alias}_c${index}`);
|
|
5627
6278
|
}
|
|
5628
6279
|
return out;
|
|
5629
6280
|
}
|
|
@@ -5633,7 +6284,7 @@ function renderChainCaptures(params) {
|
|
|
5633
6284
|
const out = [];
|
|
5634
6285
|
for (const entry of captures) {
|
|
5635
6286
|
if (!defined.has(entry.step))
|
|
5636
|
-
throw
|
|
6287
|
+
throw internalError5({ message: `chain capture reads undefined step "${entry.step}"` });
|
|
5637
6288
|
const step = quote(entry.step);
|
|
5638
6289
|
const marker = entry.inserted === undefined ? undefined : quote(entry.inserted);
|
|
5639
6290
|
out.push(`(SELECT count(*) FROM ${step}) AS ${quote(`${entry.alias}_n`)}`);
|
|
@@ -5641,23 +6292,36 @@ function renderChainCaptures(params) {
|
|
|
5641
6292
|
out.push(`(SELECT count(*) FROM ${step} WHERE ${marker}) AS ${quote(`${entry.alias}_i`)}`);
|
|
5642
6293
|
const before = entry.before ?? [];
|
|
5643
6294
|
const after = entry.after ?? [];
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
const updatedRows = marker === undefined ? "" : ` WHERE NOT ${marker}`;
|
|
5652
|
-
if (before.length > 0)
|
|
5653
|
-
out.push(`${image(before, updatedRows)} AS ${quote(`${entry.alias}_b`)}`);
|
|
5654
|
-
if (after.length > 0 && (marker === undefined || entry.insertedOnly !== true))
|
|
5655
|
-
out.push(`${image(after, updatedRows)} AS ${quote(`${entry.alias}_a`)}`);
|
|
5656
|
-
if (after.length > 0 && marker !== undefined)
|
|
5657
|
-
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
|
+
}
|
|
5658
6302
|
}
|
|
5659
6303
|
return out;
|
|
5660
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
|
+
}
|
|
5661
6325
|
function renderSelectCell(params) {
|
|
5662
6326
|
const { dialect, value, collector } = params;
|
|
5663
6327
|
if (isJsonParamCell(value))
|
|
@@ -5665,21 +6329,21 @@ function renderSelectCell(params) {
|
|
|
5665
6329
|
if (isDbNowMarker(value))
|
|
5666
6330
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
5667
6331
|
if (isSqlDefaultCell(value)) {
|
|
5668
|
-
throw
|
|
6332
|
+
throw internalError5({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
5669
6333
|
}
|
|
5670
6334
|
return collector.add(value);
|
|
5671
6335
|
}
|
|
5672
6336
|
function renderRowSource(params) {
|
|
5673
6337
|
const { dialect, source, collector } = params;
|
|
5674
6338
|
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
5675
|
-
throw
|
|
6339
|
+
throw internalError5({ message: "a row source needs at least one column and one row" });
|
|
5676
6340
|
}
|
|
5677
6341
|
const typedBy = renderTable(dialect, source.typedBy);
|
|
5678
6342
|
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
5679
6343
|
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
5680
6344
|
for (const row of source.rows) {
|
|
5681
6345
|
if (row.length !== source.columns.length) {
|
|
5682
|
-
throw
|
|
6346
|
+
throw internalError5({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
5683
6347
|
}
|
|
5684
6348
|
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
5685
6349
|
}
|
|
@@ -5691,12 +6355,12 @@ function renderTallyColumn(params) {
|
|
|
5691
6355
|
const { dialect, result, defined, collector } = params;
|
|
5692
6356
|
const tally = result.tally;
|
|
5693
6357
|
if (tally === undefined)
|
|
5694
|
-
throw
|
|
6358
|
+
throw internalError5({ message: "a chain veto needs a tally column to guard" });
|
|
5695
6359
|
if (tally.steps.length === 0)
|
|
5696
|
-
throw
|
|
6360
|
+
throw internalError5({ message: "a chain tally needs at least one step" });
|
|
5697
6361
|
const step = (name) => {
|
|
5698
6362
|
if (!defined.has(name))
|
|
5699
|
-
throw
|
|
6363
|
+
throw internalError5({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
5700
6364
|
return dialect.quoteIdent({ name });
|
|
5701
6365
|
};
|
|
5702
6366
|
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
@@ -5706,14 +6370,14 @@ function renderTallyColumn(params) {
|
|
|
5706
6370
|
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
5707
6371
|
const arms = vetoes.map((veto) => {
|
|
5708
6372
|
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
5709
|
-
throw
|
|
6373
|
+
throw internalError5({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
5710
6374
|
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
5711
|
-
throw
|
|
6375
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
5712
6376
|
const minRows = veto.minRows ?? 1;
|
|
5713
6377
|
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
5714
|
-
throw
|
|
6378
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
5715
6379
|
if (veto.exactRows !== undefined && (!Number.isSafeInteger(veto.exactRows) || veto.exactRows < 0 || veto.minRows !== undefined)) {
|
|
5716
|
-
throw
|
|
6380
|
+
throw internalError5({ message: `chain veto "${veto.token}" needs a nonnegative safe integer exactRows without minRows` });
|
|
5717
6381
|
}
|
|
5718
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}`;
|
|
5719
6383
|
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
@@ -5723,25 +6387,25 @@ function renderTallyColumn(params) {
|
|
|
5723
6387
|
function renderInsertSelect(params) {
|
|
5724
6388
|
const { dialect, statement, collector } = params;
|
|
5725
6389
|
if (statement.columns.length === 0)
|
|
5726
|
-
throw
|
|
6390
|
+
throw internalError5({ message: "insert requires at least one column" });
|
|
5727
6391
|
if (statement.cells.length !== statement.columns.length) {
|
|
5728
|
-
throw
|
|
6392
|
+
throw internalError5({
|
|
5729
6393
|
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
5730
6394
|
});
|
|
5731
6395
|
}
|
|
5732
6396
|
const from = statement.from ?? [];
|
|
5733
6397
|
for (const name of from) {
|
|
5734
6398
|
if (!params.earlier.has(name))
|
|
5735
|
-
throw
|
|
6399
|
+
throw internalError5({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
5736
6400
|
}
|
|
5737
6401
|
const source = statement.source;
|
|
5738
6402
|
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
5739
|
-
throw
|
|
6403
|
+
throw internalError5({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
5740
6404
|
}
|
|
5741
6405
|
const cells = statement.cells.map((value) => {
|
|
5742
6406
|
if (isStepColumnRef(value)) {
|
|
5743
6407
|
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
5744
|
-
throw
|
|
6408
|
+
throw internalError5({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
5745
6409
|
}
|
|
5746
6410
|
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5747
6411
|
}
|
|
@@ -5760,9 +6424,9 @@ function renderInsertSelect(params) {
|
|
|
5760
6424
|
const conflict = statement.conflictDoNothing;
|
|
5761
6425
|
if (conflict !== undefined) {
|
|
5762
6426
|
if (conflict.columns.length === 0)
|
|
5763
|
-
throw
|
|
6427
|
+
throw internalError5({ message: "an INSERT \u2026 SELECT conflict clause needs at least one column" });
|
|
5764
6428
|
if (dialect.doNothingForm !== "onConflictDoNothing") {
|
|
5765
|
-
throw
|
|
6429
|
+
throw unsupportedCapability29({
|
|
5766
6430
|
dialect: dialect.name,
|
|
5767
6431
|
feature: "ON CONFLICT DO NOTHING on an INSERT \u2026 SELECT chain step",
|
|
5768
6432
|
hint: "gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
@@ -5770,10 +6434,10 @@ function renderInsertSelect(params) {
|
|
|
5770
6434
|
}
|
|
5771
6435
|
text += ` ON CONFLICT (${conflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")}) DO NOTHING`;
|
|
5772
6436
|
}
|
|
5773
|
-
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
6437
|
+
return text + applyReturning({ dialect, requested: statement.returning, collector }).suffix;
|
|
5774
6438
|
}
|
|
5775
6439
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
5776
|
-
import { unsupportedCapability as
|
|
6440
|
+
import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
|
|
5777
6441
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
5778
6442
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
5779
6443
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -5800,13 +6464,13 @@ function parsePostgresTypeName(params) {
|
|
|
5800
6464
|
}
|
|
5801
6465
|
function renderPostgresTypeName(params) {
|
|
5802
6466
|
if (params.dialect.name !== "postgres")
|
|
5803
|
-
throw
|
|
6467
|
+
throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
5804
6468
|
const { dialect, type } = params;
|
|
5805
6469
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
5806
6470
|
}
|
|
5807
6471
|
function renderPostgresTypeIdentity(params) {
|
|
5808
6472
|
if (params.dialect.name !== "postgres")
|
|
5809
|
-
throw
|
|
6473
|
+
throw unsupportedCapability30({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
5810
6474
|
const collector = new ParamCollector(params.dialect);
|
|
5811
6475
|
const { source } = params;
|
|
5812
6476
|
let text;
|
|
@@ -5819,11 +6483,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
5819
6483
|
return { text, values: collector.values, returning: "none" };
|
|
5820
6484
|
}
|
|
5821
6485
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
5822
|
-
import { unsupportedCapability as
|
|
6486
|
+
import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
|
|
5823
6487
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
5824
6488
|
const { dialect, table, column } = params;
|
|
5825
6489
|
if (dialect.name !== "postgres")
|
|
5826
|
-
throw
|
|
6490
|
+
throw unsupportedCapability31({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
5827
6491
|
const collector = new ParamCollector(dialect);
|
|
5828
6492
|
const namespace = collector.add(table.namespace);
|
|
5829
6493
|
const name = collector.add(table.name);
|
|
@@ -5847,28 +6511,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
5847
6511
|
};
|
|
5848
6512
|
}
|
|
5849
6513
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
5850
|
-
import { unsupportedCapability as
|
|
6514
|
+
import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
|
|
5851
6515
|
function renderConstraintOwnedIndexDrop(params) {
|
|
5852
6516
|
if (params.dialect.name !== "postgres")
|
|
5853
|
-
throw
|
|
6517
|
+
throw unsupportedCapability32({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
5854
6518
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
5855
6519
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
5856
6520
|
}
|
|
5857
6521
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
5858
|
-
import { unsupportedCapability as
|
|
6522
|
+
import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
|
|
5859
6523
|
function renderMysqlCheckProbe(params) {
|
|
5860
6524
|
if (params.dialect.name !== "mysql")
|
|
5861
|
-
throw
|
|
6525
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
5862
6526
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
5863
6527
|
const probe = q(params.probe);
|
|
5864
6528
|
switch (params.operation) {
|
|
5865
6529
|
case "clone":
|
|
5866
6530
|
if (params.table === undefined)
|
|
5867
|
-
throw
|
|
6531
|
+
throw unsupportedCapability33({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
5868
6532
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
5869
6533
|
case "add": {
|
|
5870
6534
|
if (params.expression === undefined || params.definition === undefined)
|
|
5871
|
-
throw
|
|
6535
|
+
throw unsupportedCapability33({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
5872
6536
|
const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: true });
|
|
5873
6537
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
5874
6538
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -5914,33 +6578,33 @@ function sqliteTableConstraints(params) {
|
|
|
5914
6578
|
return result;
|
|
5915
6579
|
}
|
|
5916
6580
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
5917
|
-
import { unsupportedCapability as
|
|
6581
|
+
import { unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
|
|
5918
6582
|
|
|
5919
6583
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
5920
|
-
import { unsupportedCapability as
|
|
6584
|
+
import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
|
|
5921
6585
|
var MYSQL_NO_ROW_LIMIT = " LIMIT 18446744073709551615";
|
|
5922
6586
|
function renderSqliteColumnInventory(params) {
|
|
5923
6587
|
if (params.dialect.name !== "sqlite")
|
|
5924
|
-
throw
|
|
6588
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
5925
6589
|
const collector = new ParamCollector(params.dialect);
|
|
5926
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" };
|
|
5927
6591
|
}
|
|
5928
6592
|
function renderSqliteForeignKeyInventory(params) {
|
|
5929
6593
|
if (params.dialect.name !== "sqlite")
|
|
5930
|
-
throw
|
|
6594
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
5931
6595
|
const collector = new ParamCollector(params.dialect);
|
|
5932
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" };
|
|
5933
6597
|
}
|
|
5934
6598
|
function renderSqliteColumnInventoryBatch(params) {
|
|
5935
6599
|
if (params.dialect.name !== "sqlite")
|
|
5936
|
-
throw
|
|
6600
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
5937
6601
|
const collector = new ParamCollector(params.dialect);
|
|
5938
6602
|
const request = params.dialect.quoteIdent({ name: "vibe_request" });
|
|
5939
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" };
|
|
5940
6604
|
}
|
|
5941
6605
|
function renderSqliteForeignKeyInventoryBatch(params) {
|
|
5942
6606
|
if (params.dialect.name !== "sqlite")
|
|
5943
|
-
throw
|
|
6607
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
5944
6608
|
const collector = new ParamCollector(params.dialect);
|
|
5945
6609
|
const request = params.dialect.quoteIdent({ name: "vibe_request" });
|
|
5946
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" };
|
|
@@ -5953,15 +6617,15 @@ var SQLITE_INTROSPECTION_PRAGMA_ORDER = Object.freeze({
|
|
|
5953
6617
|
});
|
|
5954
6618
|
function renderSqliteIntrospectionPragmaBatch(params) {
|
|
5955
6619
|
if (params.dialect.name !== "sqlite")
|
|
5956
|
-
throw
|
|
6620
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
|
|
5957
6621
|
const order = SQLITE_INTROSPECTION_PRAGMA_ORDER[params.pragma];
|
|
5958
6622
|
if (order === undefined)
|
|
5959
|
-
throw
|
|
6623
|
+
throw unsupportedCapability34({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
|
|
5960
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" };
|
|
5961
6625
|
}
|
|
5962
6626
|
function renderSqliteRebuildObjects(params) {
|
|
5963
6627
|
if (params.dialect.name !== "sqlite")
|
|
5964
|
-
throw
|
|
6628
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
5965
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" };
|
|
5966
6630
|
}
|
|
5967
6631
|
function renderMigrationInventory(params) {
|
|
@@ -6003,13 +6667,13 @@ ORDER BY kind, parent, name${MYSQL_NO_ROW_LIMIT}`, values: collector.values, ret
|
|
|
6003
6667
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
6004
6668
|
function renderMigrationVisibility(params) {
|
|
6005
6669
|
if (params.dialect.name !== "mysql")
|
|
6006
|
-
throw
|
|
6670
|
+
throw unsupportedCapability35({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
6007
6671
|
const collector = new ParamCollector(params.dialect);
|
|
6008
6672
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
6009
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" };
|
|
6010
6674
|
}
|
|
6011
6675
|
// packages/sql/src/dialects/migration-objects.ts
|
|
6012
|
-
import { internalError as
|
|
6676
|
+
import { internalError as internalError6, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
|
|
6013
6677
|
function renderMigrationObjectQuery(params) {
|
|
6014
6678
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
6015
6679
|
}
|
|
@@ -6017,7 +6681,7 @@ function objectQuery(params) {
|
|
|
6017
6681
|
const { dialect, identity: object, collector } = params;
|
|
6018
6682
|
if (object.kind === "namespace") {
|
|
6019
6683
|
if (dialect.name !== "postgres")
|
|
6020
|
-
throw
|
|
6684
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
6021
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" };
|
|
6022
6686
|
}
|
|
6023
6687
|
const physical = "table" in object ? object.table : object.name;
|
|
@@ -6065,18 +6729,18 @@ function objectQuery(params) {
|
|
|
6065
6729
|
}
|
|
6066
6730
|
} else if (dialect.name === "mysql") {
|
|
6067
6731
|
if (object.kind === "constraint" && object.constraintType === "exclusion")
|
|
6068
|
-
throw
|
|
6732
|
+
throw unsupportedCapability36({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
6069
6733
|
const mysql = mysqlFactsText({ dialect, identity: object, namespace, name, collector });
|
|
6070
6734
|
if (mysql === undefined)
|
|
6071
|
-
throw
|
|
6735
|
+
throw unsupportedCapability36({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
6072
6736
|
text = mysql;
|
|
6073
6737
|
} else {
|
|
6074
6738
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
6075
|
-
throw
|
|
6739
|
+
throw unsupportedCapability36({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
6076
6740
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
6077
6741
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
6078
6742
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
6079
|
-
throw
|
|
6743
|
+
throw unsupportedCapability36({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
6080
6744
|
const sqliteParams = new ParamCollector(dialect);
|
|
6081
6745
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
6082
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)}`;
|
|
@@ -6151,7 +6815,7 @@ function mysqlBatches(params) {
|
|
|
6151
6815
|
function renderMysqlObjectBatch(params) {
|
|
6152
6816
|
const { dialect } = params;
|
|
6153
6817
|
if (dialect.name !== "mysql")
|
|
6154
|
-
throw
|
|
6818
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
6155
6819
|
const position = dialect.quoteIdent({ name: "vibe_position" });
|
|
6156
6820
|
const groups = new Map;
|
|
6157
6821
|
for (const [index, identity] of params.identities.entries()) {
|
|
@@ -6171,7 +6835,7 @@ function renderMysqlObjectBatch(params) {
|
|
|
6171
6835
|
function renderMysqlObjectMembersBatch(params) {
|
|
6172
6836
|
const { dialect } = params;
|
|
6173
6837
|
if (dialect.name !== "mysql")
|
|
6174
|
-
throw
|
|
6838
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
6175
6839
|
const position = dialect.quoteIdent({ name: "vibe_position" });
|
|
6176
6840
|
const ordinal = dialect.quoteIdent({ name: "vibe_ordinal" });
|
|
6177
6841
|
const groups = new Map;
|
|
@@ -6191,7 +6855,7 @@ function renderMigrationDependencies(params) {
|
|
|
6191
6855
|
}
|
|
6192
6856
|
function dependencyQuery(params) {
|
|
6193
6857
|
if (params.dialect.name !== "postgres")
|
|
6194
|
-
throw
|
|
6858
|
+
throw unsupportedCapability36({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
6195
6859
|
const object = params.identity;
|
|
6196
6860
|
const collector = params.collector;
|
|
6197
6861
|
if (object.kind === "namespace") {
|
|
@@ -6248,7 +6912,7 @@ function inspectionReferences(params) {
|
|
|
6248
6912
|
function renderMigrationObjectBatch(params) {
|
|
6249
6913
|
const { dialect } = params;
|
|
6250
6914
|
if (dialect.name !== "postgres")
|
|
6251
|
-
throw
|
|
6915
|
+
throw unsupportedCapability36({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
6252
6916
|
const groups = new Map;
|
|
6253
6917
|
for (const [position, identity] of params.identities.entries()) {
|
|
6254
6918
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -6261,7 +6925,7 @@ function renderMigrationObjectBatch(params) {
|
|
|
6261
6925
|
const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
6262
6926
|
const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
6263
6927
|
if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
|
|
6264
|
-
throw
|
|
6928
|
+
throw internalError6({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
|
|
6265
6929
|
const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
|
|
6266
6930
|
const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
|
|
6267
6931
|
const collector = new ParamCollector(dialect);
|
|
@@ -6281,16 +6945,16 @@ function renderMigrationNamespace(params) {
|
|
|
6281
6945
|
}
|
|
6282
6946
|
function renderCheckProbe(params) {
|
|
6283
6947
|
if (params.dialect.name !== "postgres")
|
|
6284
|
-
throw
|
|
6948
|
+
throw unsupportedCapability36({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
6285
6949
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
6286
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" };
|
|
6287
6951
|
}
|
|
6288
6952
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
6289
|
-
import { VibeError as
|
|
6953
|
+
import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability37 } from "@vibeorm/schema";
|
|
6290
6954
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
6291
6955
|
function requirePostgres2(params) {
|
|
6292
6956
|
if (params.dialect.name !== "postgres") {
|
|
6293
|
-
throw
|
|
6957
|
+
throw unsupportedCapability37({ dialect: params.dialect.name, feature: params.feature });
|
|
6294
6958
|
}
|
|
6295
6959
|
}
|
|
6296
6960
|
function renderMigrationSessionRead(params) {
|
|
@@ -6305,7 +6969,7 @@ function renderMigrationSessionSet(params) {
|
|
|
6305
6969
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
6306
6970
|
const { milliseconds, setting } = params;
|
|
6307
6971
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
6308
|
-
throw new
|
|
6972
|
+
throw new VibeError25({
|
|
6309
6973
|
code: "VIBE_MIGRATION",
|
|
6310
6974
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
6311
6975
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -6400,34 +7064,45 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
6400
7064
|
export {
|
|
6401
7065
|
ADVISORY_KEY_BOUNDS,
|
|
6402
7066
|
ADVISORY_LOCK_RESULT_COLUMN,
|
|
7067
|
+
AUTOMATIC_SHADOW_NAME_PATTERN,
|
|
6403
7068
|
CHAIN_VETO_PREFIX,
|
|
6404
7069
|
CHAIN_VETO_TOKEN_PATTERN,
|
|
7070
|
+
COALESCE_REF_NAME,
|
|
6405
7071
|
DIALECT_CAPABILITIES,
|
|
6406
7072
|
DIALECT_CODECS,
|
|
6407
7073
|
JSON_TRANSPORT,
|
|
6408
7074
|
MODULE_JOURNAL_COLUMN,
|
|
6409
7075
|
MYSQL_CAPABILITIES,
|
|
6410
7076
|
MYSQL_CODECS,
|
|
7077
|
+
MYSQL_SCALAR_COLUMN_TYPES,
|
|
6411
7078
|
NATIVE_RLS,
|
|
6412
7079
|
NATIVE_RLS_PROBE_POLICY,
|
|
6413
7080
|
POSTGRES_CAPABILITIES,
|
|
6414
7081
|
POSTGRES_CODECS,
|
|
6415
7082
|
POSTGRES_DIAGNOSTIC_SQL,
|
|
7083
|
+
POSTGRES_EXACT_EQUIVALENTS,
|
|
7084
|
+
POSTGRES_NATIVE_ALIASES,
|
|
7085
|
+
POSTGRES_SCALAR_COLUMN_TYPES,
|
|
6416
7086
|
POSTGRES_WORKLOAD_LIMITS,
|
|
6417
7087
|
ParamCollector,
|
|
6418
7088
|
RLS_READINESS_SQL,
|
|
6419
7089
|
SHADOW_CAPABILITIES,
|
|
6420
7090
|
SHADOW_DATABASE,
|
|
7091
|
+
SHADOW_MARKER_CAPABILITIES,
|
|
6421
7092
|
SQLITE_CAPABILITIES,
|
|
6422
7093
|
SQLITE_CODECS,
|
|
7094
|
+
SQLITE_SCALAR_STORAGE_CLASSES,
|
|
6423
7095
|
SQLITE_SESSION_SQL,
|
|
6424
7096
|
SQL_DEFAULT_CELL,
|
|
6425
7097
|
SQL_DIALECTS,
|
|
6426
7098
|
SQL_KEEP_CELL,
|
|
6427
7099
|
assertIndexExpressionVersion,
|
|
6428
7100
|
assertSqlParamBuilding,
|
|
7101
|
+
automaticShadowMarkerText,
|
|
7102
|
+
canonicalColumnType,
|
|
6429
7103
|
chainVetoToken,
|
|
6430
7104
|
cloneTimestampDate,
|
|
7105
|
+
coalesceRef,
|
|
6431
7106
|
compareTimestampDates,
|
|
6432
7107
|
compileExclusion,
|
|
6433
7108
|
compileReusableSqlCommand,
|
|
@@ -6464,10 +7139,13 @@ export {
|
|
|
6464
7139
|
isFixedDiagnostic,
|
|
6465
7140
|
isJsonListParameter,
|
|
6466
7141
|
isJsonParamCell,
|
|
7142
|
+
isJsonPatchOperation,
|
|
6467
7143
|
isListSetOperation,
|
|
6468
7144
|
isNativeEnumArrayParameter,
|
|
7145
|
+
isOtherRowReturningColumn,
|
|
6469
7146
|
isReusableSqlCommand,
|
|
6470
7147
|
isReusableSqlQuery,
|
|
7148
|
+
isRollupChain,
|
|
6471
7149
|
isSetOperation,
|
|
6472
7150
|
isSourceColumnRef,
|
|
6473
7151
|
isSqlBindingCollector,
|
|
@@ -6481,17 +7159,22 @@ export {
|
|
|
6481
7159
|
isTimeZoneName,
|
|
6482
7160
|
jsonListParameter,
|
|
6483
7161
|
jsonParamCell,
|
|
7162
|
+
jsonPatchVetoOf,
|
|
6484
7163
|
keyListBindsUntypedRows,
|
|
6485
7164
|
localDateCodec,
|
|
6486
7165
|
mapConflictColumns,
|
|
6487
7166
|
mysqlDialect,
|
|
6488
7167
|
nativeEnumArrayParameter,
|
|
7168
|
+
normalizeMysqlNativeType,
|
|
7169
|
+
normalizePostgresNativeType,
|
|
7170
|
+
parseAutomaticShadowMarker,
|
|
6489
7171
|
parseDateTimeText,
|
|
6490
7172
|
parseExtendedDateTimeText,
|
|
6491
7173
|
parseJsonText,
|
|
6492
7174
|
parsePgArrayLiteral,
|
|
6493
7175
|
parsePgArrayLiteralNested,
|
|
6494
7176
|
parsePostgresTypeName,
|
|
7177
|
+
planJsonPatch,
|
|
6495
7178
|
postgresArrayElementFromText,
|
|
6496
7179
|
postgresBulkInsertArrayCastOf,
|
|
6497
7180
|
postgresDialect,
|
|
@@ -6502,6 +7185,7 @@ export {
|
|
|
6502
7185
|
registerSqlHandleCheck,
|
|
6503
7186
|
renderAdvisoryLock,
|
|
6504
7187
|
renderAdvisoryLocks,
|
|
7188
|
+
renderAutomaticShadowOperation,
|
|
6505
7189
|
renderCaptureReadiness,
|
|
6506
7190
|
renderCheckProbe,
|
|
6507
7191
|
renderColumnStateQuery,
|
|
@@ -6609,4 +7293,4 @@ export {
|
|
|
6609
7293
|
toPgArrayLiteral
|
|
6610
7294
|
};
|
|
6611
7295
|
|
|
6612
|
-
//# debugId=
|
|
7296
|
+
//# debugId=B21A2ED0EE62B5B964756E2164756E21
|