@vibeorm/sql 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.d.ts +54 -7
- package/dist/ast.d.ts.map +1 -1
- package/dist/codecs.d.ts +2 -3
- package/dist/codecs.d.ts.map +1 -1
- package/dist/composable.d.ts +70 -0
- package/dist/composable.d.ts.map +1 -0
- package/dist/dialect.d.ts +82 -6
- package/dist/dialect.d.ts.map +1 -1
- package/dist/dialects/constraint-owned-index.d.ts +9 -0
- package/dist/dialects/constraint-owned-index.d.ts.map +1 -0
- package/dist/dialects/migration-history.d.ts +8 -0
- package/dist/dialects/migration-history.d.ts.map +1 -0
- package/dist/dialects/migration-inventory.d.ts +24 -0
- package/dist/dialects/migration-inventory.d.ts.map +1 -0
- package/dist/dialects/migration-objects.d.ts +37 -0
- package/dist/dialects/migration-objects.d.ts.map +1 -0
- package/dist/dialects/migration-visibility.d.ts +8 -0
- package/dist/dialects/migration-visibility.d.ts.map +1 -0
- package/dist/dialects/mysql-check-probe.d.ts +16 -0
- package/dist/dialects/mysql-check-probe.d.ts.map +1 -0
- package/dist/dialects/mysql.d.ts.map +1 -1
- package/dist/dialects/postgres-rls-readiness.d.ts +3 -0
- package/dist/dialects/postgres-rls-readiness.d.ts.map +1 -1
- package/dist/dialects/postgres.d.ts.map +1 -1
- package/dist/dialects/sqlite-constraints.d.ts +16 -0
- package/dist/dialects/sqlite-constraints.d.ts.map +1 -0
- package/dist/dialects/sqlite-table-definition.d.ts +19 -0
- package/dist/dialects/sqlite-table-definition.d.ts.map +1 -0
- package/dist/dialects/sqlite.d.ts.map +1 -1
- package/dist/exclusion.d.ts +32 -0
- package/dist/exclusion.d.ts.map +1 -0
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1590 -408
- package/dist/index.js.map +26 -13
- package/dist/locks.d.ts +57 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/predicate.d.ts +53 -0
- package/dist/predicate.d.ts.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/result-decoder.d.ts +49 -0
- package/dist/result-decoder.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,878 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/sql/src/composable.ts
|
|
3
|
+
import { VibeError } from "@vibeorm/schema";
|
|
4
|
+
|
|
5
|
+
// packages/sql/src/params.ts
|
|
6
|
+
class ParamCollector {
|
|
7
|
+
dialect;
|
|
8
|
+
values;
|
|
9
|
+
constructor(dialect) {
|
|
10
|
+
this.dialect = dialect;
|
|
11
|
+
this.values = [];
|
|
12
|
+
}
|
|
13
|
+
add(value) {
|
|
14
|
+
this.values.push(value);
|
|
15
|
+
return this.dialect.placeholder({ index: this.values.length });
|
|
16
|
+
}
|
|
17
|
+
addMany(values) {
|
|
18
|
+
return values.map((value) => this.add(value));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// packages/sql/src/composable.ts
|
|
23
|
+
var fragments = new WeakMap;
|
|
24
|
+
function invalid(message) {
|
|
25
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message });
|
|
26
|
+
}
|
|
27
|
+
function construct(parts) {
|
|
28
|
+
const snapshot = Object.freeze([...parts]);
|
|
29
|
+
const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
|
|
30
|
+
value: () => ({ parts: snapshot })
|
|
31
|
+
}));
|
|
32
|
+
fragments.set(fragment, snapshot);
|
|
33
|
+
return fragment;
|
|
34
|
+
}
|
|
35
|
+
function isSqlFragment(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && fragments.has(value);
|
|
37
|
+
}
|
|
38
|
+
function partsOf(value) {
|
|
39
|
+
return fragments.get(value) ?? invalid("Expected a constructed SQL fragment");
|
|
40
|
+
}
|
|
41
|
+
function identifier(parts) {
|
|
42
|
+
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
43
|
+
invalid("SQL identifiers require non-empty, NUL-free name components");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function template(strings, ...values) {
|
|
47
|
+
if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
|
|
48
|
+
invalid("Use sql as a template tag");
|
|
49
|
+
}
|
|
50
|
+
const parts = [];
|
|
51
|
+
for (let index = 0;index < strings.length; index++) {
|
|
52
|
+
parts.push(strings[index]);
|
|
53
|
+
if (index < values.length) {
|
|
54
|
+
const value = values[index];
|
|
55
|
+
if (isSqlFragment(value))
|
|
56
|
+
parts.push(...partsOf(value));
|
|
57
|
+
else
|
|
58
|
+
parts.push(Object.freeze({ value }));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return construct(parts);
|
|
62
|
+
}
|
|
63
|
+
var sql = Object.freeze(Object.assign(template, {
|
|
64
|
+
text: ({ text }) => {
|
|
65
|
+
if (typeof text !== "string" || text.includes("\x00"))
|
|
66
|
+
invalid("SQL text must be a NUL-free string");
|
|
67
|
+
return construct([text]);
|
|
68
|
+
},
|
|
69
|
+
ident: ({ parts }) => {
|
|
70
|
+
identifier(parts);
|
|
71
|
+
return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
|
|
72
|
+
},
|
|
73
|
+
value: ({ value, decoder }) => construct([Object.freeze({ value, ...decoder === undefined ? {} : { decoder } })]),
|
|
74
|
+
join: ({ items, separator }) => {
|
|
75
|
+
if (!Array.isArray(items))
|
|
76
|
+
invalid("sql.join requires an array");
|
|
77
|
+
const between = separator === undefined ? [", "] : partsOf(separator);
|
|
78
|
+
const parts = [];
|
|
79
|
+
items.forEach((value, index) => {
|
|
80
|
+
if (index > 0)
|
|
81
|
+
parts.push(...between);
|
|
82
|
+
parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value })]);
|
|
83
|
+
});
|
|
84
|
+
return construct(parts);
|
|
85
|
+
},
|
|
86
|
+
existing: ({ column }) => {
|
|
87
|
+
identifier([column]);
|
|
88
|
+
return construct([Object.freeze({ row: "existing", column })]);
|
|
89
|
+
},
|
|
90
|
+
incoming: ({ column }) => {
|
|
91
|
+
identifier([column]);
|
|
92
|
+
return construct([Object.freeze({ row: "incoming", column })]);
|
|
93
|
+
},
|
|
94
|
+
greatest: ({ values }) => {
|
|
95
|
+
if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
|
|
96
|
+
invalid("sql.greatest requires two SQL fragments");
|
|
97
|
+
return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
function renderSqlFragment(params) {
|
|
101
|
+
const { dialect, collector, conflict } = params;
|
|
102
|
+
const render = (fragment) => renderSqlFragment({ ...params, fragment });
|
|
103
|
+
return partsOf(params.fragment).map((part) => {
|
|
104
|
+
if (typeof part === "string")
|
|
105
|
+
return part;
|
|
106
|
+
if ("value" in part) {
|
|
107
|
+
if (part.value === undefined)
|
|
108
|
+
invalid("SQL parameters cannot be undefined; use null explicitly");
|
|
109
|
+
return collector.add(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
|
|
110
|
+
}
|
|
111
|
+
if ("ident" in part)
|
|
112
|
+
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
113
|
+
if ("row" in part) {
|
|
114
|
+
if (conflict === undefined)
|
|
115
|
+
invalid("Existing/incoming row references require a conflict update");
|
|
116
|
+
const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
|
|
117
|
+
if (name === undefined)
|
|
118
|
+
invalid(`Unknown conflict-expression field "${part.column}"`);
|
|
119
|
+
const ref = dialect.excludedRowRef;
|
|
120
|
+
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
121
|
+
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
122
|
+
}
|
|
123
|
+
return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
|
|
124
|
+
}).join("");
|
|
125
|
+
}
|
|
126
|
+
function mapConflictColumns(params) {
|
|
127
|
+
return construct(partsOf(params.fragment).map((part) => {
|
|
128
|
+
if (typeof part === "string")
|
|
129
|
+
return part;
|
|
130
|
+
if ("row" in part) {
|
|
131
|
+
const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
|
|
132
|
+
if (column === undefined)
|
|
133
|
+
invalid(`Unknown conflict-expression field "${part.column}"`);
|
|
134
|
+
return Object.freeze({ ...part, column });
|
|
135
|
+
}
|
|
136
|
+
if ("greatest" in part)
|
|
137
|
+
return Object.freeze({ greatest: Object.freeze(part.greatest.map((fragment) => mapConflictColumns({ ...params, fragment }))) });
|
|
138
|
+
return part;
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
function compileSql(params) {
|
|
142
|
+
const collector = new ParamCollector(params.dialect);
|
|
143
|
+
const text = renderSqlFragment({ dialect: params.dialect, fragment: params.query, collector });
|
|
144
|
+
return { text, values: collector.values };
|
|
145
|
+
}
|
|
146
|
+
function sqlUsesIncoming(params) {
|
|
147
|
+
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
148
|
+
}
|
|
149
|
+
// packages/sql/src/result-decoder.ts
|
|
150
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
151
|
+
|
|
152
|
+
// packages/sql/src/pg-array.ts
|
|
153
|
+
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
154
|
+
function toPgArrayLiteral(params) {
|
|
155
|
+
const { values } = params;
|
|
156
|
+
const parts = values.map((value) => {
|
|
157
|
+
if (value === null || value === undefined)
|
|
158
|
+
return "NULL";
|
|
159
|
+
const text = String(value);
|
|
160
|
+
return PG_ARRAY_QUOTE.test(text) ? `"${text.replace(/["\\]/g, (char) => `\\${char}`)}"` : text;
|
|
161
|
+
});
|
|
162
|
+
return `{${parts.join(",")}}`;
|
|
163
|
+
}
|
|
164
|
+
function parsePgArrayLiteral(params) {
|
|
165
|
+
const { literal } = params;
|
|
166
|
+
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
167
|
+
return null;
|
|
168
|
+
const inner = literal.slice(1, -1);
|
|
169
|
+
if (inner.length === 0)
|
|
170
|
+
return [];
|
|
171
|
+
const out = [];
|
|
172
|
+
let index = 0;
|
|
173
|
+
while (index < inner.length) {
|
|
174
|
+
if (inner[index] === '"') {
|
|
175
|
+
let text = "";
|
|
176
|
+
index++;
|
|
177
|
+
while (index < inner.length) {
|
|
178
|
+
const char = inner[index];
|
|
179
|
+
if (char === "\\") {
|
|
180
|
+
text += inner[index + 1] ?? "";
|
|
181
|
+
index += 2;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
index++;
|
|
185
|
+
if (char === '"')
|
|
186
|
+
break;
|
|
187
|
+
text += char;
|
|
188
|
+
}
|
|
189
|
+
out.push(text);
|
|
190
|
+
} else {
|
|
191
|
+
let text = "";
|
|
192
|
+
while (index < inner.length && inner[index] !== ",") {
|
|
193
|
+
text += inner[index];
|
|
194
|
+
index++;
|
|
195
|
+
}
|
|
196
|
+
out.push(text === "NULL" ? null : text);
|
|
197
|
+
}
|
|
198
|
+
if (inner[index] === ",")
|
|
199
|
+
index++;
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// packages/sql/src/codecs.ts
|
|
205
|
+
import { VibeError as VibeError2, invalidStoredData } from "@vibeorm/schema";
|
|
206
|
+
function enumMembershipCodec(params) {
|
|
207
|
+
const { model, field, enum: enumName } = params;
|
|
208
|
+
const members = new Set(params.members);
|
|
209
|
+
return {
|
|
210
|
+
encode: (value) => {
|
|
211
|
+
if (typeof value === "string" && members.has(value))
|
|
212
|
+
return value;
|
|
213
|
+
throw new VibeError2({
|
|
214
|
+
code: "VIBE_VALIDATION",
|
|
215
|
+
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
216
|
+
meta: { model, field, enum: enumName }
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
decode: (value) => {
|
|
220
|
+
if (typeof value === "string" && members.has(value))
|
|
221
|
+
return value;
|
|
222
|
+
throw invalidStoredData({ model, field, enum: enumName, value });
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function decodeIsNoop(params) {
|
|
227
|
+
const { scalarType, wire } = params;
|
|
228
|
+
if (wire === undefined)
|
|
229
|
+
return false;
|
|
230
|
+
if (scalarType === "DateTime")
|
|
231
|
+
return false;
|
|
232
|
+
if (scalarType === "Json")
|
|
233
|
+
return wire.json === "parsed";
|
|
234
|
+
if (scalarType === "Int" || scalarType === "Float")
|
|
235
|
+
return wire.numbers === "native";
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
var identity = (value) => value;
|
|
239
|
+
var passthroughCodec = { encode: identity, decode: identity };
|
|
240
|
+
var numberCodec = {
|
|
241
|
+
encode: identity,
|
|
242
|
+
decode: (value) => typeof value === "string" ? Number(value) : value
|
|
243
|
+
};
|
|
244
|
+
var bigIntCodec = {
|
|
245
|
+
encode: (value) => typeof value === "bigint" ? value.toString() : value,
|
|
246
|
+
decode: (value) => {
|
|
247
|
+
if (typeof value === "bigint")
|
|
248
|
+
return value;
|
|
249
|
+
if (typeof value === "string" || typeof value === "number")
|
|
250
|
+
return BigInt(value);
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var decimalCodec = {
|
|
255
|
+
encode: (value) => typeof value === "number" || typeof value === "bigint" ? String(value) : value,
|
|
256
|
+
decode: identity
|
|
257
|
+
};
|
|
258
|
+
var mysqlDecimalCodec = {
|
|
259
|
+
encode: decimalCodec.encode,
|
|
260
|
+
decode: (value) => {
|
|
261
|
+
if (typeof value !== "string" || !value.includes("."))
|
|
262
|
+
return value;
|
|
263
|
+
const trimmed = value.replace(/0+$/, "").replace(/\.$/, "");
|
|
264
|
+
return trimmed === "" || trimmed === "-" ? "0" : trimmed;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
268
|
+
function parseDateTimeText(params) {
|
|
269
|
+
const trimmed = params.text.trim();
|
|
270
|
+
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
271
|
+
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
272
|
+
if (!Number.isNaN(asUtc.getTime()))
|
|
273
|
+
return asUtc;
|
|
274
|
+
}
|
|
275
|
+
const parsed = new Date(trimmed);
|
|
276
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
277
|
+
}
|
|
278
|
+
var dateNativeCodec = {
|
|
279
|
+
encode: (value) => {
|
|
280
|
+
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
281
|
+
throw new VibeError2({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
282
|
+
}
|
|
283
|
+
return value;
|
|
284
|
+
},
|
|
285
|
+
decode: (value) => {
|
|
286
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
287
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
288
|
+
return date;
|
|
289
|
+
throw new VibeError2({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var dateTextCodec = {
|
|
293
|
+
encode: (value) => {
|
|
294
|
+
dateNativeCodec.encode(value);
|
|
295
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
296
|
+
},
|
|
297
|
+
decode: dateNativeCodec.decode
|
|
298
|
+
};
|
|
299
|
+
var mysqlDateCodec = {
|
|
300
|
+
encode: (value) => {
|
|
301
|
+
dateNativeCodec.encode(value);
|
|
302
|
+
if (value instanceof Date)
|
|
303
|
+
return value;
|
|
304
|
+
if (typeof value === "number") {
|
|
305
|
+
const parsed = new Date(value);
|
|
306
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
307
|
+
return parsed;
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
if (typeof value === "string")
|
|
311
|
+
return parseDateTimeText({ text: value }) ?? value;
|
|
312
|
+
return value;
|
|
313
|
+
},
|
|
314
|
+
decode: dateNativeCodec.decode
|
|
315
|
+
};
|
|
316
|
+
var booleanIntCodec = {
|
|
317
|
+
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
318
|
+
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
319
|
+
};
|
|
320
|
+
var booleanNativeInCodec = {
|
|
321
|
+
encode: identity,
|
|
322
|
+
decode: booleanIntCodec.decode
|
|
323
|
+
};
|
|
324
|
+
var jsonCodec = {
|
|
325
|
+
encode: (value) => JSON.stringify(value),
|
|
326
|
+
decode: (value) => {
|
|
327
|
+
if (typeof value !== "string")
|
|
328
|
+
return value;
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(value);
|
|
331
|
+
} catch (cause) {
|
|
332
|
+
throw new VibeError2({
|
|
333
|
+
code: "VIBE_INTERNAL",
|
|
334
|
+
message: "a Json column returned text that is not valid JSON",
|
|
335
|
+
meta: { value },
|
|
336
|
+
cause
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var POSTGRES_CODECS = {
|
|
342
|
+
String: passthroughCodec,
|
|
343
|
+
Boolean: passthroughCodec,
|
|
344
|
+
Int: numberCodec,
|
|
345
|
+
BigInt: bigIntCodec,
|
|
346
|
+
Float: numberCodec,
|
|
347
|
+
Decimal: decimalCodec,
|
|
348
|
+
DateTime: dateNativeCodec,
|
|
349
|
+
Json: jsonCodec,
|
|
350
|
+
Bytes: passthroughCodec
|
|
351
|
+
};
|
|
352
|
+
var SQLITE_CODECS = {
|
|
353
|
+
String: passthroughCodec,
|
|
354
|
+
Boolean: booleanIntCodec,
|
|
355
|
+
Int: numberCodec,
|
|
356
|
+
BigInt: bigIntCodec,
|
|
357
|
+
Float: numberCodec,
|
|
358
|
+
Decimal: decimalCodec,
|
|
359
|
+
DateTime: dateTextCodec,
|
|
360
|
+
Json: jsonCodec,
|
|
361
|
+
Bytes: passthroughCodec
|
|
362
|
+
};
|
|
363
|
+
var MYSQL_CODECS = {
|
|
364
|
+
String: passthroughCodec,
|
|
365
|
+
Boolean: booleanNativeInCodec,
|
|
366
|
+
Int: numberCodec,
|
|
367
|
+
BigInt: bigIntCodec,
|
|
368
|
+
Float: numberCodec,
|
|
369
|
+
Decimal: mysqlDecimalCodec,
|
|
370
|
+
DateTime: mysqlDateCodec,
|
|
371
|
+
Json: jsonCodec,
|
|
372
|
+
Bytes: passthroughCodec
|
|
373
|
+
};
|
|
374
|
+
var DIALECT_CODECS = {
|
|
375
|
+
postgres: POSTGRES_CODECS,
|
|
376
|
+
sqlite: SQLITE_CODECS,
|
|
377
|
+
mysql: MYSQL_CODECS
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// packages/sql/src/result-decoder.ts
|
|
381
|
+
function refuse(encoding, message) {
|
|
382
|
+
throw new VibeError3({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
383
|
+
}
|
|
384
|
+
function scalar(type, valid) {
|
|
385
|
+
const convert = (params, encoding) => {
|
|
386
|
+
const { value, dialect } = params;
|
|
387
|
+
if (value === null || value === undefined)
|
|
388
|
+
refuse(encoding, `A non-null ${type} projection received null or a missing column`);
|
|
389
|
+
if (type === "BigInt" && !(typeof value === "bigint" || typeof value === "string" && /^[+-]?\d+$/.test(value) || typeof value === "number" && Number.isSafeInteger(value))) {
|
|
390
|
+
refuse(encoding, "BigInt requires exact integer text, bigint, or a safe integer");
|
|
391
|
+
}
|
|
392
|
+
if (type === "Boolean" && value !== true && value !== false && value !== 0 && value !== 1)
|
|
393
|
+
refuse(encoding, "Boolean requires true, false, 0, or 1");
|
|
394
|
+
if (type === "Decimal" && typeof value === "number")
|
|
395
|
+
refuse(encoding, "Decimal requires exact decimal text, not a floating-point number");
|
|
396
|
+
if ((type === "Int" || type === "Float") && typeof value === "string" && !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) {
|
|
397
|
+
refuse(encoding, `Invalid ${type} text`);
|
|
398
|
+
}
|
|
399
|
+
const codec = DIALECT_CODECS[dialect][type];
|
|
400
|
+
const result = type === "Json" && params.wire?.json === "parsed" ? value : codec.decode(value);
|
|
401
|
+
if (!valid(result))
|
|
402
|
+
refuse(encoding, `Invalid ${type} projection value`);
|
|
403
|
+
return result;
|
|
404
|
+
};
|
|
405
|
+
return Object.freeze({
|
|
406
|
+
decode: (params) => convert(params, false),
|
|
407
|
+
encode: (params) => {
|
|
408
|
+
const value = type === "Json" ? params.value : convert(params, true);
|
|
409
|
+
if (value === undefined || value === null)
|
|
410
|
+
refuse(true, `A non-null ${type} parameter cannot be null or undefined`);
|
|
411
|
+
return DIALECT_CODECS[params.dialect][type].encode(value);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
416
|
+
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
417
|
+
var extendedDecoder = Object.freeze({
|
|
418
|
+
decode: (params) => {
|
|
419
|
+
if (params.value === "infinity" || params.value === Infinity)
|
|
420
|
+
return "infinity";
|
|
421
|
+
if (params.value === "-infinity" || params.value === -Infinity)
|
|
422
|
+
return "-infinity";
|
|
423
|
+
return dateDecoder.decode(params);
|
|
424
|
+
},
|
|
425
|
+
encode: (params) => {
|
|
426
|
+
if (params.value === "infinity" || params.value === "-infinity") {
|
|
427
|
+
if (params.dialect !== "postgres")
|
|
428
|
+
throw new VibeError3({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
429
|
+
return params.value;
|
|
430
|
+
}
|
|
431
|
+
return dateDecoder.encode(params);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
var decode = Object.freeze({
|
|
435
|
+
string: stringDecoder,
|
|
436
|
+
boolean: scalar("Boolean", (value) => typeof value === "boolean"),
|
|
437
|
+
int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
|
|
438
|
+
float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
|
|
439
|
+
bigInt: scalar("BigInt", (value) => typeof value === "bigint"),
|
|
440
|
+
decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
|
|
441
|
+
dateTime: dateDecoder,
|
|
442
|
+
extendedDateTime: extendedDecoder,
|
|
443
|
+
json: scalar("Json", (value) => value !== undefined),
|
|
444
|
+
bytes: scalar("Bytes", (value) => value instanceof Uint8Array),
|
|
445
|
+
nullable: ({ decoder }) => Object.freeze({
|
|
446
|
+
decode: (params) => params.value === null ? null : decoder.decode(params),
|
|
447
|
+
encode: (params) => params.value === null ? null : decoder.encode(params)
|
|
448
|
+
}),
|
|
449
|
+
array: ({ decoder }) => Object.freeze({
|
|
450
|
+
decode: (params) => {
|
|
451
|
+
const raw = params.value;
|
|
452
|
+
const value = params.dialect === "postgres" && typeof raw === "string" ? parsePgArrayLiteral({ literal: raw }) : ArrayBuffer.isView(raw) && ("length" in raw) ? Array.from(raw) : raw;
|
|
453
|
+
if (!Array.isArray(value))
|
|
454
|
+
refuse(false, "Expected an array projection");
|
|
455
|
+
return value.map((element) => decoder.decode({ ...params, value: element }));
|
|
456
|
+
},
|
|
457
|
+
encode: (params) => {
|
|
458
|
+
if (!Array.isArray(params.value))
|
|
459
|
+
refuse(true, "Expected an array parameter");
|
|
460
|
+
return params.value.map((value) => decoder.encode({ ...params, value }));
|
|
461
|
+
}
|
|
462
|
+
}),
|
|
463
|
+
enum: ({ values }) => {
|
|
464
|
+
const members = new Set(values);
|
|
465
|
+
return Object.freeze({
|
|
466
|
+
decode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(false, "Unknown enum projection member"),
|
|
467
|
+
encode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(true, "Unknown enum parameter member")
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
object: ({ fields }) => {
|
|
471
|
+
const entries = Object.entries(fields);
|
|
472
|
+
const convert = (params, encoding) => {
|
|
473
|
+
if (typeof params.value !== "object" || params.value === null || Array.isArray(params.value))
|
|
474
|
+
refuse(encoding, "Expected a result row object");
|
|
475
|
+
const row = params.value;
|
|
476
|
+
return Object.fromEntries(entries.map(([name, decoder]) => {
|
|
477
|
+
if (!Object.hasOwn(row, name))
|
|
478
|
+
refuse(encoding, `Missing projection column "${name}"`);
|
|
479
|
+
return [name, encoding ? decoder.encode({ ...params, value: row[name] }) : decoder.decode({ ...params, value: row[name] })];
|
|
480
|
+
}));
|
|
481
|
+
};
|
|
482
|
+
return Object.freeze({ decode: (params) => convert(params, false), encode: (params) => convert(params, true) });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
// packages/sql/src/exclusion.ts
|
|
486
|
+
import { VibeError as VibeError5, fieldTypeIdentity as fieldTypeIdentity2, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
487
|
+
|
|
488
|
+
// packages/sql/src/predicate.ts
|
|
489
|
+
import { VibeError as VibeError4, fieldTypeIdentity, isEnumType, unsupportedCapability } from "@vibeorm/schema";
|
|
490
|
+
function renderPredicate(params) {
|
|
491
|
+
if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
|
|
492
|
+
throw unsupportedCapability({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
493
|
+
if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
|
|
494
|
+
throw unsupportedCapability({ dialect: params.dialect.name, feature: "partialIndexes" });
|
|
495
|
+
}
|
|
496
|
+
const context = {
|
|
497
|
+
model: params.model,
|
|
498
|
+
dialect: params.dialect,
|
|
499
|
+
enums: params.enums ?? [],
|
|
500
|
+
usage: params.usage,
|
|
501
|
+
site: params.site
|
|
502
|
+
};
|
|
503
|
+
return renderNode({ node: params.predicate, context });
|
|
504
|
+
}
|
|
505
|
+
var PRECEDENCE = {
|
|
506
|
+
or: 1,
|
|
507
|
+
and: 2,
|
|
508
|
+
not: 3,
|
|
509
|
+
compare: 4,
|
|
510
|
+
inList: 4,
|
|
511
|
+
isNull: 4
|
|
512
|
+
};
|
|
513
|
+
var COMPARISON_SQL = {
|
|
514
|
+
eq: "=",
|
|
515
|
+
ne: "<>",
|
|
516
|
+
gt: ">",
|
|
517
|
+
gte: ">=",
|
|
518
|
+
lt: "<",
|
|
519
|
+
lte: "<="
|
|
520
|
+
};
|
|
521
|
+
var EXACT_INTEGER = /^-?(0|[1-9]\d*)$/;
|
|
522
|
+
var EXACT_DECIMAL = /^-?(0|[1-9]\d*)(\.\d+)?$/;
|
|
523
|
+
var INT32_MIN = -2147483648n;
|
|
524
|
+
var INT32_MAX = 2147483647n;
|
|
525
|
+
var INT64_MIN = -(2n ** 63n);
|
|
526
|
+
var INT64_MAX = 2n ** 63n - 1n;
|
|
527
|
+
function refuse2(params) {
|
|
528
|
+
const { context, rule, message } = params;
|
|
529
|
+
throw new VibeError4({
|
|
530
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
531
|
+
message: `${context.site} on "${context.model.name}": ${message}`,
|
|
532
|
+
meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function renderChild(params) {
|
|
536
|
+
const { node, parent, context } = params;
|
|
537
|
+
if ((node.kind === "and" || node.kind === "or") && node.operands.length === 1) {
|
|
538
|
+
return renderChild({ node: node.operands[0], parent, context });
|
|
539
|
+
}
|
|
540
|
+
const text = renderNode({ node, context });
|
|
541
|
+
return PRECEDENCE[node.kind] < PRECEDENCE[parent] ? `(${text})` : text;
|
|
542
|
+
}
|
|
543
|
+
function renderNode(params) {
|
|
544
|
+
const { node, context } = params;
|
|
545
|
+
switch (node.kind) {
|
|
546
|
+
case "and":
|
|
547
|
+
case "or": {
|
|
548
|
+
if (node.operands.length === 0) {
|
|
549
|
+
refuse2({
|
|
550
|
+
context,
|
|
551
|
+
rule: "predicate-empty",
|
|
552
|
+
message: `${node.kind === "and" ? "and()" : "or()"} has no operands \u2014 an empty boolean group has no honest SQL spelling`
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (node.operands.length === 1)
|
|
556
|
+
return renderNode({ node: node.operands[0], context });
|
|
557
|
+
const separator = node.kind === "and" ? " AND " : " OR ";
|
|
558
|
+
return node.operands.map((operand) => renderChild({ node: operand, parent: node.kind, context })).join(separator);
|
|
559
|
+
}
|
|
560
|
+
case "not":
|
|
561
|
+
return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
|
|
562
|
+
case "isNull": {
|
|
563
|
+
const field = resolveField({ name: node.field.name, context });
|
|
564
|
+
return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
|
|
565
|
+
}
|
|
566
|
+
case "compare": {
|
|
567
|
+
const left = resolveField({ name: node.left.name, context });
|
|
568
|
+
const right = renderOperand({ operand: node.right, field: left, context });
|
|
569
|
+
return `${compareColumn({ field: left, context })} ${COMPARISON_SQL[node.op]} ${right}`;
|
|
570
|
+
}
|
|
571
|
+
case "inList": {
|
|
572
|
+
if (node.values.length === 0) {
|
|
573
|
+
refuse2({
|
|
574
|
+
context,
|
|
575
|
+
rule: "predicate-empty",
|
|
576
|
+
message: `inList("${node.field.name}") has no values \u2014 an empty IN list is UNKNOWN on every row and never what an author means`,
|
|
577
|
+
meta: { field: node.field.name }
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
const field = resolveField({ name: node.field.name, context });
|
|
581
|
+
const values = node.values.map((value) => renderLiteral({ value, field, context })).join(", ");
|
|
582
|
+
return `${compareColumn({ field, context })} ${node.negate === true ? "NOT IN" : "IN"} (${values})`;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function resolveField(params) {
|
|
587
|
+
const { name, context } = params;
|
|
588
|
+
const field = context.model.fields.find((candidate) => candidate.name === name);
|
|
589
|
+
if (field === undefined) {
|
|
590
|
+
refuse2({
|
|
591
|
+
context,
|
|
592
|
+
rule: "predicate-unknown-field",
|
|
593
|
+
message: `references field "${name}", which the model does not declare`,
|
|
594
|
+
meta: { field: name }
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (field.isList) {
|
|
598
|
+
refuse2({
|
|
599
|
+
context,
|
|
600
|
+
rule: "predicate-list-field",
|
|
601
|
+
message: `compares scalar list "${name}" \u2014 array operators differ per dialect; use a raw expression for them`,
|
|
602
|
+
meta: { field: name }
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (field.isUnsupported === true) {
|
|
606
|
+
refuse2({
|
|
607
|
+
context,
|
|
608
|
+
rule: "predicate-unsupported-type",
|
|
609
|
+
message: `references "${name}", an Unsupported("${field.nativeType ?? "?"}") column whose operators VibeORM does not model \u2014 use a raw expression`,
|
|
610
|
+
meta: { field: name }
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
if (field.type === "Json" || field.type === "Bytes") {
|
|
614
|
+
refuse2({
|
|
615
|
+
context,
|
|
616
|
+
rule: "predicate-unsupported-type",
|
|
617
|
+
message: `references ${field.type} column "${name}" \u2014 ${field.type} comparison operators differ per dialect; use a raw expression`,
|
|
618
|
+
meta: { field: name, type: field.type }
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
|
|
622
|
+
throw unsupportedCapability({
|
|
623
|
+
dialect: "mysql",
|
|
624
|
+
feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
|
|
625
|
+
hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
return field;
|
|
629
|
+
}
|
|
630
|
+
function quoteColumn(params) {
|
|
631
|
+
const { field, context } = params;
|
|
632
|
+
return context.dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
633
|
+
}
|
|
634
|
+
function compareColumn(params) {
|
|
635
|
+
const { field, context } = params;
|
|
636
|
+
const quoted = quoteColumn({ field, context });
|
|
637
|
+
return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
|
|
638
|
+
}
|
|
639
|
+
function renderOperand(params) {
|
|
640
|
+
const { operand, field, context } = params;
|
|
641
|
+
if (operand.kind === "value")
|
|
642
|
+
return renderLiteral({ value: operand, field, context });
|
|
643
|
+
const other = resolveField({ name: operand.name, context });
|
|
644
|
+
const leftType = fieldTypeIdentity({ type: field.type });
|
|
645
|
+
const rightType = fieldTypeIdentity({ type: other.type });
|
|
646
|
+
if (leftType !== rightType) {
|
|
647
|
+
refuse2({
|
|
648
|
+
context,
|
|
649
|
+
rule: "predicate-type-mismatch",
|
|
650
|
+
message: `compares "${field.name}" (${leftType}) with "${other.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
|
|
651
|
+
meta: { field: field.name, other: other.name, fieldType: leftType, otherType: rightType }
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return compareColumn({ field: other, context });
|
|
655
|
+
}
|
|
656
|
+
function renderLiteral(params) {
|
|
657
|
+
const { field, context } = params;
|
|
658
|
+
const { value } = params.value;
|
|
659
|
+
const type = field.type;
|
|
660
|
+
const wrongCarrier = (expected) => refuse2({
|
|
661
|
+
context,
|
|
662
|
+
rule: "predicate-type-mismatch",
|
|
663
|
+
message: `compares ${fieldTypeIdentity({ type })} field "${field.name}" with ${describe({ value })} \u2014 ${expected}`,
|
|
664
|
+
meta: { field: field.name, fieldType: fieldTypeIdentity({ type }), value }
|
|
665
|
+
});
|
|
666
|
+
if (isEnumType(type)) {
|
|
667
|
+
if (typeof value !== "string")
|
|
668
|
+
return wrongCarrier(`enum ${type.enum} takes one of its member names as a string`);
|
|
669
|
+
const declared = context.enums.find((entry) => entry.name === type.enum);
|
|
670
|
+
if (declared !== undefined && !declared.values.includes(value)) {
|
|
671
|
+
refuse2({
|
|
672
|
+
context,
|
|
673
|
+
rule: "predicate-enum-member",
|
|
674
|
+
message: `compares "${field.name}" with "${value}", which is not a member of enum ${type.enum} (${declared.values.join(", ")})`,
|
|
675
|
+
meta: { field: field.name, enum: type.enum, value, members: [...declared.values] }
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return stringLiteral({ value, context, field });
|
|
679
|
+
}
|
|
680
|
+
switch (type) {
|
|
681
|
+
case "String":
|
|
682
|
+
if (typeof value !== "string")
|
|
683
|
+
return wrongCarrier("a String column takes a string literal");
|
|
684
|
+
return stringLiteral({ value, context, field });
|
|
685
|
+
case "Boolean":
|
|
686
|
+
if (typeof value !== "boolean")
|
|
687
|
+
return wrongCarrier("a Boolean column takes `true` or `false`");
|
|
688
|
+
return value ? context.dialect.booleanLiteral.true : context.dialect.booleanLiteral.false;
|
|
689
|
+
case "Int":
|
|
690
|
+
return integerLiteral({ value, field, context, min: INT32_MIN, max: INT32_MAX, label: "Int" });
|
|
691
|
+
case "BigInt":
|
|
692
|
+
return integerLiteral({ value, field, context, min: INT64_MIN, max: INT64_MAX, label: "BigInt" });
|
|
693
|
+
case "Float": {
|
|
694
|
+
if (typeof value !== "number")
|
|
695
|
+
return wrongCarrier("a Float column takes a number");
|
|
696
|
+
if (!Number.isFinite(value)) {
|
|
697
|
+
refuse2({
|
|
698
|
+
context,
|
|
699
|
+
rule: "predicate-literal-invalid",
|
|
700
|
+
message: `compares "${field.name}" with ${String(value)} \u2014 a Float literal must be finite`,
|
|
701
|
+
meta: { field: field.name, value: String(value) }
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return String(value);
|
|
705
|
+
}
|
|
706
|
+
case "Decimal": {
|
|
707
|
+
if (typeof value !== "string") {
|
|
708
|
+
return wrongCarrier('a Decimal column takes exact decimal TEXT ("0", "19.99") \u2014 a JS number cannot carry an exact decimal');
|
|
709
|
+
}
|
|
710
|
+
if (!EXACT_DECIMAL.test(value)) {
|
|
711
|
+
refuse2({
|
|
712
|
+
context,
|
|
713
|
+
rule: "predicate-literal-invalid",
|
|
714
|
+
message: `compares "${field.name}" with "${value}", which is not an exact decimal literal`,
|
|
715
|
+
meta: { field: field.name, value }
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return value;
|
|
719
|
+
}
|
|
720
|
+
case "DateTime":
|
|
721
|
+
refuse2({
|
|
722
|
+
context,
|
|
723
|
+
rule: "predicate-unsupported-literal",
|
|
724
|
+
message: `compares DateTime field "${field.name}" with a literal \u2014 DateTime literals are spelled differently on postgres, mysql and sqlite, so VibeORM does not guess one. Compare two DateTime columns, or write a raw expression for a fixed instant`,
|
|
725
|
+
meta: { field: field.name }
|
|
726
|
+
});
|
|
727
|
+
case "Json":
|
|
728
|
+
case "Bytes":
|
|
729
|
+
return wrongCarrier(`${type} columns take no predicate literal`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function integerLiteral(params) {
|
|
733
|
+
const { value, field, context, min, max, label } = params;
|
|
734
|
+
let text;
|
|
735
|
+
if (typeof value === "number") {
|
|
736
|
+
if (!Number.isSafeInteger(value)) {
|
|
737
|
+
refuse2({
|
|
738
|
+
context,
|
|
739
|
+
rule: "predicate-literal-invalid",
|
|
740
|
+
message: `compares "${field.name}" with ${String(value)} \u2014 an ${label} literal must be an exact integer JavaScript can represent; pass a bigint or a decimal string for larger values`,
|
|
741
|
+
meta: { field: field.name, value: String(value) }
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
text = String(value);
|
|
745
|
+
} else if (typeof value === "string") {
|
|
746
|
+
if (!EXACT_INTEGER.test(value)) {
|
|
747
|
+
refuse2({
|
|
748
|
+
context,
|
|
749
|
+
rule: "predicate-literal-invalid",
|
|
750
|
+
message: `compares "${field.name}" with "${value}", which is not an exact integer literal`,
|
|
751
|
+
meta: { field: field.name, value }
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
text = value;
|
|
755
|
+
} else {
|
|
756
|
+
refuse2({
|
|
757
|
+
context,
|
|
758
|
+
rule: "predicate-type-mismatch",
|
|
759
|
+
message: `compares ${label} field "${field.name}" with ${describe({ value })} \u2014 an ${label} column takes an integer`,
|
|
760
|
+
meta: { field: field.name, value }
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
const parsed = BigInt(text);
|
|
764
|
+
if (parsed < min || parsed > max) {
|
|
765
|
+
refuse2({
|
|
766
|
+
context,
|
|
767
|
+
rule: "predicate-literal-invalid",
|
|
768
|
+
message: `compares "${field.name}" with ${text}, which is outside the range an ${label} column can hold (${min}\u2026${max})`,
|
|
769
|
+
meta: { field: field.name, value: text }
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
return text;
|
|
773
|
+
}
|
|
774
|
+
function stringLiteral(params) {
|
|
775
|
+
const { value, context, field } = params;
|
|
776
|
+
if (value.includes("\x00")) {
|
|
777
|
+
refuse2({
|
|
778
|
+
context,
|
|
779
|
+
rule: "predicate-literal-invalid",
|
|
780
|
+
message: `compares "${field.name}" with a string containing a NUL byte, which no dialect here can store`,
|
|
781
|
+
meta: { field: field.name }
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return context.dialect.quoteStringLiteral({ value });
|
|
785
|
+
}
|
|
786
|
+
function describe(params) {
|
|
787
|
+
const { value } = params;
|
|
788
|
+
return typeof value === "string" ? `the string ${JSON.stringify(value)}` : `the ${typeof value} ${String(value)}`;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// packages/sql/src/exclusion.ts
|
|
792
|
+
function invalid2(rule) {
|
|
793
|
+
throw new VibeError5({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
794
|
+
}
|
|
795
|
+
function compileExclusion(params) {
|
|
796
|
+
const { exclusion: spec, model, dialect } = params;
|
|
797
|
+
if (!dialect.capabilities.exclusionConstraints)
|
|
798
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
799
|
+
if (!spec.name || spec.name.includes("\x00"))
|
|
800
|
+
invalid2("name");
|
|
801
|
+
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
802
|
+
invalid2("range-type");
|
|
803
|
+
if (!["[)", "[]", "()", "(]"].includes(spec.range.bounds))
|
|
804
|
+
invalid2("bounds");
|
|
805
|
+
const field = (name) => {
|
|
806
|
+
const value = model.fields.find((entry) => entry.name === name);
|
|
807
|
+
if (value === undefined || value.isList || value.isUnsupported)
|
|
808
|
+
invalid2("field");
|
|
809
|
+
return value;
|
|
810
|
+
};
|
|
811
|
+
const endpoint = (value) => {
|
|
812
|
+
if ("unbounded" in value)
|
|
813
|
+
return "NULL";
|
|
814
|
+
const infinity = "infinity" in value ? value.infinity : value.nullAs;
|
|
815
|
+
if (infinity !== undefined && (spec.range.type === "int8range" || !["infinity", "-infinity"].includes(infinity)))
|
|
816
|
+
invalid2("infinity");
|
|
817
|
+
const literal = infinity === undefined ? undefined : `${dialect.quoteStringLiteral({ value: infinity })}::${spec.range.type === "tsrange" ? "timestamp" : "timestamptz"}`;
|
|
818
|
+
if ("infinity" in value)
|
|
819
|
+
return literal;
|
|
820
|
+
const column = field(value.field);
|
|
821
|
+
if (fieldTypeIdentity2({ type: column.type }) !== (spec.range.type === "int8range" ? "BigInt" : "DateTime"))
|
|
822
|
+
invalid2("range-field-type");
|
|
823
|
+
if (column.type === "DateTime") {
|
|
824
|
+
const timezone = /timestamptz|timestamp.*with time zone/i.test(column.nativeType ?? "timestamptz");
|
|
825
|
+
if (timezone !== (spec.range.type === "tstzrange"))
|
|
826
|
+
invalid2("range-timezone");
|
|
827
|
+
}
|
|
828
|
+
const sql2 = dialect.quoteIdent({ name: column.dbName ?? column.name });
|
|
829
|
+
return literal === undefined ? sql2 : `COALESCE(${sql2}, ${literal})`;
|
|
830
|
+
};
|
|
831
|
+
const keys = spec.keys.map((key) => {
|
|
832
|
+
if ("expression" in key) {
|
|
833
|
+
if (!key.expression.trim() || key.expression.includes("\x00") || key.fields.length === 0)
|
|
834
|
+
invalid2("expression");
|
|
835
|
+
key.fields.forEach(field);
|
|
836
|
+
return `(${key.expression}) WITH =`;
|
|
837
|
+
}
|
|
838
|
+
const column = field(key.field);
|
|
839
|
+
if (column.isOptional)
|
|
840
|
+
invalid2("nullable-key-requires-expression");
|
|
841
|
+
if (!["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
|
|
842
|
+
invalid2("key-type");
|
|
843
|
+
return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
|
|
844
|
+
});
|
|
845
|
+
keys.push(`${spec.range.type}(${endpoint(spec.range.lower)}, ${endpoint(spec.range.upper)}, ${dialect.quoteStringLiteral({ value: spec.range.bounds })}) WITH &&`);
|
|
846
|
+
const where = spec.where === undefined ? "" : ` WHERE (${renderPredicate({ predicate: spec.where, model, dialect, usage: "exclusionPredicate", site: spec.name })})`;
|
|
847
|
+
return { name: spec.name, definition: `EXCLUDE USING gist (${keys.join(", ")})${where}`, source: spec, requiredExtensions: spec.keys.length === 0 ? [] : dialect.capabilities.exclusionExtensions };
|
|
848
|
+
}
|
|
849
|
+
function renderExclusionSetup(params) {
|
|
850
|
+
if (!params.dialect.capabilities.exclusionConstraints)
|
|
851
|
+
throw unsupportedCapability2({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
852
|
+
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
853
|
+
}
|
|
854
|
+
function renderExclusionDdl(params) {
|
|
855
|
+
const { dialect, table, exclusion, operation } = params;
|
|
856
|
+
if (!dialect.capabilities.exclusionConstraints)
|
|
857
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
858
|
+
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
859
|
+
if (operation === "drop")
|
|
860
|
+
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
861
|
+
return [...renderExclusionSetup({ dialect, exclusion }), `${target} ADD CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })} ${exclusion.definition};`];
|
|
862
|
+
}
|
|
863
|
+
function renderExclusionCatalog(params) {
|
|
864
|
+
const { dialect, probe } = params;
|
|
865
|
+
if (!dialect.capabilities.exclusionConstraints)
|
|
866
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
867
|
+
const scope = probe === undefined ? "n.nspname = 'public'" : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
868
|
+
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`;
|
|
869
|
+
}
|
|
870
|
+
function renderExclusionProbe(params) {
|
|
871
|
+
const { dialect, table, probe, definition } = params;
|
|
872
|
+
if (!dialect.capabilities.exclusionConstraints)
|
|
873
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
874
|
+
return [`CREATE TEMP TABLE ${dialect.quoteIdent({ name: probe })} (LIKE ${dialect.quoteIdent({ name: "public" })}.${dialect.quoteIdent({ name: table })})`, `ALTER TABLE ${dialect.quoteIdent({ name: probe })} ADD CONSTRAINT ${dialect.quoteIdent({ name: `${probe}_x` })} ${definition}`];
|
|
875
|
+
}
|
|
2
876
|
// packages/sql/src/ast.ts
|
|
3
877
|
var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
|
|
4
878
|
function isSetOperation(value) {
|
|
@@ -48,6 +922,90 @@ function excludedRef(params) {
|
|
|
48
922
|
function isExcludedRef(value) {
|
|
49
923
|
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
50
924
|
}
|
|
925
|
+
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
926
|
+
// packages/sql/src/locks.ts
|
|
927
|
+
import { internalError, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
928
|
+
var ROW_LOCK_PROSE = Object.freeze({
|
|
929
|
+
strengths: Object.freeze({
|
|
930
|
+
update: "FOR UPDATE",
|
|
931
|
+
noKeyUpdate: "FOR NO KEY UPDATE",
|
|
932
|
+
share: "FOR SHARE",
|
|
933
|
+
keyShare: "FOR KEY SHARE"
|
|
934
|
+
}),
|
|
935
|
+
wait: Object.freeze({ wait: "", nowait: " NOWAIT", skipLocked: " SKIP LOCKED" })
|
|
936
|
+
});
|
|
937
|
+
function describeRowLock(params) {
|
|
938
|
+
const { strength, wait } = params.lock;
|
|
939
|
+
return `${ROW_LOCK_PROSE.strengths[strength]}${ROW_LOCK_PROSE.wait[wait]}`;
|
|
940
|
+
}
|
|
941
|
+
function renderRowLockClause(params) {
|
|
942
|
+
const { dialect, lock } = params;
|
|
943
|
+
const syntax = dialect.rowLockSyntax;
|
|
944
|
+
if (syntax === null) {
|
|
945
|
+
if (lock.strength === "update" && lock.wait === "wait")
|
|
946
|
+
return null;
|
|
947
|
+
throw unsupportedCapability3({
|
|
948
|
+
dialect: dialect.name,
|
|
949
|
+
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
950
|
+
hint: `${dialect.name} has no locking-read clause at all`
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
const strengthText = syntax.strengths[lock.strength];
|
|
954
|
+
if (strengthText === undefined) {
|
|
955
|
+
throw unsupportedCapability3({
|
|
956
|
+
dialect: dialect.name,
|
|
957
|
+
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
958
|
+
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
const waitText = syntax.wait[lock.wait];
|
|
962
|
+
if (waitText === undefined) {
|
|
963
|
+
throw unsupportedCapability3({
|
|
964
|
+
dialect: dialect.name,
|
|
965
|
+
feature: `the "${lock.wait}" lock wait policy`,
|
|
966
|
+
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
return `${strengthText}${waitText}`;
|
|
970
|
+
}
|
|
971
|
+
var ADVISORY_KEY_BOUNDS = Object.freeze({
|
|
972
|
+
single: Object.freeze({ min: -(2n ** 63n), max: 2n ** 63n - 1n }),
|
|
973
|
+
pair: Object.freeze({ min: -(2 ** 31), max: 2 ** 31 - 1 })
|
|
974
|
+
});
|
|
975
|
+
function isSingleKey(key) {
|
|
976
|
+
return "key" in key;
|
|
977
|
+
}
|
|
978
|
+
function renderAdvisoryLock(params) {
|
|
979
|
+
const { dialect, statement, collector } = params;
|
|
980
|
+
const functions = dialect.advisoryLockFunctions;
|
|
981
|
+
if (functions === null) {
|
|
982
|
+
throw unsupportedCapability3({
|
|
983
|
+
dialect: dialect.name,
|
|
984
|
+
feature: "advisory locks",
|
|
985
|
+
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`
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
const mode = statement.mode;
|
|
989
|
+
const fn = (mode === "exclusive" ? functions.exclusive : functions.shared)[statement.wait];
|
|
990
|
+
const key = statement.key;
|
|
991
|
+
const args = isSingleKey(key) ? `${collector.add(key.key.toString())}${functions.keyCast.single}` : `${collector.add(String(assertInt32({ value: key.classId, part: "classId" })))}${functions.keyCast.pair}, ` + `${collector.add(String(assertInt32({ value: key.objectId, part: "objectId" })))}${functions.keyCast.pair}`;
|
|
992
|
+
const projection = statement.wait === "nowait" ? ` AS ${dialect.quoteIdent({ name: ADVISORY_LOCK_RESULT_COLUMN })}` : "";
|
|
993
|
+
return {
|
|
994
|
+
text: `SELECT ${fn}(${args})${projection}`,
|
|
995
|
+
values: collector.values,
|
|
996
|
+
returning: "none"
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function assertInt32(params) {
|
|
1000
|
+
const { value, part } = params;
|
|
1001
|
+
if (!Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
|
|
1002
|
+
throw internalError({
|
|
1003
|
+
message: `advisory lock ${part} must be a 32-bit integer, got ${String(value)}`,
|
|
1004
|
+
meta: { part, value }
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
return value;
|
|
1008
|
+
}
|
|
51
1009
|
// packages/sql/src/json-transport.ts
|
|
52
1010
|
var JSON_TRANSPORT = Object.freeze({ chunkSize: 100 });
|
|
53
1011
|
function projectionOutputName(params) {
|
|
@@ -93,12 +1051,35 @@ var postgresDialect = {
|
|
|
93
1051
|
],
|
|
94
1052
|
jsonParamCast: "::text::jsonb",
|
|
95
1053
|
lastInsertIdSql: null,
|
|
96
|
-
|
|
1054
|
+
rowLockSyntax: {
|
|
1055
|
+
strengths: Object.freeze({
|
|
1056
|
+
update: "FOR UPDATE",
|
|
1057
|
+
noKeyUpdate: "FOR NO KEY UPDATE",
|
|
1058
|
+
share: "FOR SHARE",
|
|
1059
|
+
keyShare: "FOR KEY SHARE"
|
|
1060
|
+
}),
|
|
1061
|
+
wait: Object.freeze({ wait: "", nowait: " NOWAIT", skipLocked: " SKIP LOCKED" })
|
|
1062
|
+
},
|
|
1063
|
+
advisoryLockFunctions: {
|
|
1064
|
+
exclusive: Object.freeze({
|
|
1065
|
+
wait: "pg_catalog.pg_advisory_xact_lock",
|
|
1066
|
+
nowait: "pg_catalog.pg_try_advisory_xact_lock"
|
|
1067
|
+
}),
|
|
1068
|
+
shared: Object.freeze({
|
|
1069
|
+
wait: "pg_catalog.pg_advisory_xact_lock_shared",
|
|
1070
|
+
nowait: "pg_catalog.pg_try_advisory_xact_lock_shared"
|
|
1071
|
+
}),
|
|
1072
|
+
keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
|
|
1073
|
+
},
|
|
97
1074
|
rowDefaultKeyword: true,
|
|
98
1075
|
paramBudget: 30000,
|
|
1076
|
+
booleanLiteral: { true: "true", false: "false" },
|
|
99
1077
|
quoteIdent({ name }) {
|
|
100
1078
|
return `"${name.replaceAll('"', '""')}"`;
|
|
101
1079
|
},
|
|
1080
|
+
quoteStringLiteral({ value }) {
|
|
1081
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
1082
|
+
},
|
|
102
1083
|
placeholder({ index }) {
|
|
103
1084
|
return `$${index}`;
|
|
104
1085
|
},
|
|
@@ -127,260 +1108,104 @@ var sqliteDialect = {
|
|
|
127
1108
|
orderByNullsMode: "native",
|
|
128
1109
|
offsetRequiresLimit: { limitText: "-1" },
|
|
129
1110
|
likeEscapeClause: " ESCAPE '\\'",
|
|
130
|
-
jsonPathMode: "sqliteExtract",
|
|
131
|
-
supportedJsonOps: [
|
|
132
|
-
"equals",
|
|
133
|
-
"string_contains",
|
|
134
|
-
"string_starts_with",
|
|
135
|
-
"string_ends_with",
|
|
136
|
-
"array_contains",
|
|
137
|
-
"array_starts_with",
|
|
138
|
-
"array_ends_with"
|
|
139
|
-
],
|
|
140
|
-
jsonParamCast: "",
|
|
141
|
-
lastInsertIdSql: null,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
capabilities: MYSQL_CAPABILITIES,
|
|
161
|
-
inArrayStrategy: "expand",
|
|
162
|
-
tupleInStrategy: "rowValue",
|
|
163
|
-
upsertForm: "onDuplicateKey",
|
|
164
|
-
conflictKeyEquality: {
|
|
165
|
-
foldingScalars: ["Decimal"],
|
|
166
|
-
defaultStringMaxLength: 191,
|
|
167
|
-
textIsBinaryByDefault: false,
|
|
168
|
-
duplicatesInOneStatement: "lastWriteWins"
|
|
169
|
-
},
|
|
170
|
-
excludedRowRef: { form: "rowAlias", alias: "new" },
|
|
171
|
-
doNothingForm: "insertIgnore",
|
|
172
|
-
insensitiveLike: "lower",
|
|
173
|
-
strictMatchStrategy: "binary",
|
|
174
|
-
orderByNullsMode: "emulate",
|
|
175
|
-
offsetRequiresLimit: { limitText: "18446744073709551615" },
|
|
176
|
-
likeEscapeClause: null,
|
|
177
|
-
jsonPathMode: "mysqlExtract",
|
|
178
|
-
supportedJsonOps: [
|
|
179
|
-
"equals",
|
|
180
|
-
"string_contains",
|
|
181
|
-
"string_starts_with",
|
|
182
|
-
"string_ends_with",
|
|
183
|
-
"array_contains",
|
|
184
|
-
"array_starts_with",
|
|
185
|
-
"array_ends_with"
|
|
186
|
-
],
|
|
187
|
-
jsonParamCast: "",
|
|
188
|
-
lastInsertIdSql: "SELECT LAST_INSERT_ID() AS `id`",
|
|
189
|
-
selectForUpdate: " FOR UPDATE",
|
|
190
|
-
rowDefaultKeyword: true,
|
|
191
|
-
paramBudget: 1e4,
|
|
192
|
-
quoteIdent({ name }) {
|
|
193
|
-
return `\`${name.replaceAll("`", "``")}\``;
|
|
194
|
-
},
|
|
195
|
-
placeholder() {
|
|
196
|
-
return "?";
|
|
197
|
-
},
|
|
198
|
-
renderDbNow({ offsetSeconds, collector }) {
|
|
199
|
-
if (offsetSeconds === 0)
|
|
200
|
-
return "NOW(6)";
|
|
201
|
-
return `DATE_ADD(NOW(6), INTERVAL ${collector.add(offsetSeconds)} SECOND)`;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
// packages/sql/src/dialects/registry.ts
|
|
205
|
-
var SQL_DIALECTS = {
|
|
206
|
-
postgres: postgresDialect,
|
|
207
|
-
sqlite: sqliteDialect,
|
|
208
|
-
mysql: mysqlDialect
|
|
209
|
-
};
|
|
210
|
-
function getSqlDialect(params) {
|
|
211
|
-
return SQL_DIALECTS[params.dialect];
|
|
212
|
-
}
|
|
213
|
-
// packages/sql/src/codecs.ts
|
|
214
|
-
import { VibeError, invalidStoredData } from "@vibeorm/schema";
|
|
215
|
-
function enumMembershipCodec(params) {
|
|
216
|
-
const { model, field, enum: enumName } = params;
|
|
217
|
-
const members = new Set(params.members);
|
|
218
|
-
return {
|
|
219
|
-
encode: (value) => {
|
|
220
|
-
if (typeof value === "string" && members.has(value))
|
|
221
|
-
return value;
|
|
222
|
-
throw new VibeError({
|
|
223
|
-
code: "VIBE_VALIDATION",
|
|
224
|
-
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
225
|
-
meta: { model, field, enum: enumName }
|
|
226
|
-
});
|
|
227
|
-
},
|
|
228
|
-
decode: (value) => {
|
|
229
|
-
if (typeof value === "string" && members.has(value))
|
|
230
|
-
return value;
|
|
231
|
-
throw invalidStoredData({ model, field, enum: enumName, value });
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function decodeIsNoop(params) {
|
|
236
|
-
const { scalarType, wire } = params;
|
|
237
|
-
if (wire === undefined)
|
|
238
|
-
return false;
|
|
239
|
-
if (scalarType === "DateTime")
|
|
240
|
-
return wire.dateTime === "native";
|
|
241
|
-
if (scalarType === "Json")
|
|
242
|
-
return wire.json === "parsed";
|
|
243
|
-
if (scalarType === "Int" || scalarType === "Float")
|
|
244
|
-
return wire.numbers === "native";
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
var identity = (value) => value;
|
|
248
|
-
var passthroughCodec = { encode: identity, decode: identity };
|
|
249
|
-
var numberCodec = {
|
|
250
|
-
encode: identity,
|
|
251
|
-
decode: (value) => typeof value === "string" ? Number(value) : value
|
|
252
|
-
};
|
|
253
|
-
var bigIntCodec = {
|
|
254
|
-
encode: (value) => typeof value === "bigint" ? value.toString() : value,
|
|
255
|
-
decode: (value) => {
|
|
256
|
-
if (typeof value === "bigint")
|
|
257
|
-
return value;
|
|
258
|
-
if (typeof value === "string" || typeof value === "number")
|
|
259
|
-
return BigInt(value);
|
|
260
|
-
return value;
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
var decimalCodec = {
|
|
264
|
-
encode: (value) => typeof value === "number" || typeof value === "bigint" ? String(value) : value,
|
|
265
|
-
decode: identity
|
|
266
|
-
};
|
|
267
|
-
var mysqlDecimalCodec = {
|
|
268
|
-
encode: decimalCodec.encode,
|
|
269
|
-
decode: (value) => {
|
|
270
|
-
if (typeof value !== "string" || !value.includes("."))
|
|
271
|
-
return value;
|
|
272
|
-
const trimmed = value.replace(/0+$/, "").replace(/\.$/, "");
|
|
273
|
-
return trimmed === "" || trimmed === "-" ? "0" : trimmed;
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
277
|
-
function parseDateTimeText(params) {
|
|
278
|
-
const trimmed = params.text.trim();
|
|
279
|
-
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
280
|
-
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
281
|
-
if (!Number.isNaN(asUtc.getTime()))
|
|
282
|
-
return asUtc;
|
|
283
|
-
}
|
|
284
|
-
const parsed = new Date(trimmed);
|
|
285
|
-
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
286
|
-
}
|
|
287
|
-
var dateNativeCodec = {
|
|
288
|
-
encode: identity,
|
|
289
|
-
decode: (value) => {
|
|
290
|
-
if (value instanceof Date)
|
|
291
|
-
return value;
|
|
292
|
-
if (typeof value === "string")
|
|
293
|
-
return parseDateTimeText({ text: value }) ?? new Date(value);
|
|
294
|
-
if (typeof value === "number")
|
|
295
|
-
return new Date(value);
|
|
296
|
-
return value;
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
var dateTextCodec = {
|
|
300
|
-
encode: (value) => value instanceof Date ? value.toISOString() : value,
|
|
301
|
-
decode: dateNativeCodec.decode
|
|
302
|
-
};
|
|
303
|
-
var mysqlDateCodec = {
|
|
304
|
-
encode: (value) => {
|
|
305
|
-
if (value instanceof Date)
|
|
306
|
-
return value;
|
|
307
|
-
if (typeof value === "number") {
|
|
308
|
-
const parsed = new Date(value);
|
|
309
|
-
if (!Number.isNaN(parsed.getTime()))
|
|
310
|
-
return parsed;
|
|
311
|
-
return value;
|
|
312
|
-
}
|
|
313
|
-
if (typeof value === "string")
|
|
314
|
-
return parseDateTimeText({ text: value }) ?? value;
|
|
315
|
-
return value;
|
|
316
|
-
},
|
|
317
|
-
decode: dateNativeCodec.decode
|
|
318
|
-
};
|
|
319
|
-
var booleanIntCodec = {
|
|
320
|
-
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
321
|
-
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
322
|
-
};
|
|
323
|
-
var booleanNativeInCodec = {
|
|
324
|
-
encode: identity,
|
|
325
|
-
decode: booleanIntCodec.decode
|
|
326
|
-
};
|
|
327
|
-
var jsonCodec = {
|
|
328
|
-
encode: (value) => JSON.stringify(value),
|
|
329
|
-
decode: (value) => {
|
|
330
|
-
if (typeof value !== "string")
|
|
331
|
-
return value;
|
|
332
|
-
try {
|
|
333
|
-
return JSON.parse(value);
|
|
334
|
-
} catch (cause) {
|
|
335
|
-
throw new VibeError({
|
|
336
|
-
code: "VIBE_INTERNAL",
|
|
337
|
-
message: "a Json column returned text that is not valid JSON",
|
|
338
|
-
meta: { value },
|
|
339
|
-
cause
|
|
340
|
-
});
|
|
341
|
-
}
|
|
1111
|
+
jsonPathMode: "sqliteExtract",
|
|
1112
|
+
supportedJsonOps: [
|
|
1113
|
+
"equals",
|
|
1114
|
+
"string_contains",
|
|
1115
|
+
"string_starts_with",
|
|
1116
|
+
"string_ends_with",
|
|
1117
|
+
"array_contains",
|
|
1118
|
+
"array_starts_with",
|
|
1119
|
+
"array_ends_with"
|
|
1120
|
+
],
|
|
1121
|
+
jsonParamCast: "",
|
|
1122
|
+
lastInsertIdSql: null,
|
|
1123
|
+
rowLockSyntax: null,
|
|
1124
|
+
advisoryLockFunctions: null,
|
|
1125
|
+
rowDefaultKeyword: false,
|
|
1126
|
+
paramBudget: 900,
|
|
1127
|
+
booleanLiteral: { true: "1", false: "0" },
|
|
1128
|
+
quoteIdent({ name }) {
|
|
1129
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
1130
|
+
},
|
|
1131
|
+
quoteStringLiteral({ value }) {
|
|
1132
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
1133
|
+
},
|
|
1134
|
+
placeholder() {
|
|
1135
|
+
return "?";
|
|
1136
|
+
},
|
|
1137
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
1138
|
+
if (offsetSeconds === 0)
|
|
1139
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1140
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
342
1141
|
}
|
|
343
1142
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
1143
|
+
// packages/sql/src/dialects/mysql.ts
|
|
1144
|
+
var mysqlDialect = {
|
|
1145
|
+
name: "mysql",
|
|
1146
|
+
capabilities: MYSQL_CAPABILITIES,
|
|
1147
|
+
inArrayStrategy: "expand",
|
|
1148
|
+
tupleInStrategy: "rowValue",
|
|
1149
|
+
upsertForm: "onDuplicateKey",
|
|
1150
|
+
conflictKeyEquality: {
|
|
1151
|
+
foldingScalars: ["Decimal"],
|
|
1152
|
+
defaultStringMaxLength: 191,
|
|
1153
|
+
textIsBinaryByDefault: false,
|
|
1154
|
+
duplicatesInOneStatement: "lastWriteWins"
|
|
1155
|
+
},
|
|
1156
|
+
excludedRowRef: { form: "rowAlias", alias: "new" },
|
|
1157
|
+
doNothingForm: "insertIgnore",
|
|
1158
|
+
insensitiveLike: "lower",
|
|
1159
|
+
strictMatchStrategy: "binary",
|
|
1160
|
+
orderByNullsMode: "emulate",
|
|
1161
|
+
offsetRequiresLimit: { limitText: "18446744073709551615" },
|
|
1162
|
+
likeEscapeClause: null,
|
|
1163
|
+
jsonPathMode: "mysqlExtract",
|
|
1164
|
+
supportedJsonOps: [
|
|
1165
|
+
"equals",
|
|
1166
|
+
"string_contains",
|
|
1167
|
+
"string_starts_with",
|
|
1168
|
+
"string_ends_with",
|
|
1169
|
+
"array_contains",
|
|
1170
|
+
"array_starts_with",
|
|
1171
|
+
"array_ends_with"
|
|
1172
|
+
],
|
|
1173
|
+
jsonParamCast: "",
|
|
1174
|
+
lastInsertIdSql: "SELECT LAST_INSERT_ID() AS `id`",
|
|
1175
|
+
rowLockSyntax: {
|
|
1176
|
+
strengths: Object.freeze({ update: "FOR UPDATE", share: "FOR SHARE" }),
|
|
1177
|
+
wait: Object.freeze({ wait: "", nowait: " NOWAIT", skipLocked: " SKIP LOCKED" })
|
|
1178
|
+
},
|
|
1179
|
+
advisoryLockFunctions: null,
|
|
1180
|
+
rowDefaultKeyword: true,
|
|
1181
|
+
paramBudget: 1e4,
|
|
1182
|
+
booleanLiteral: { true: "1", false: "0" },
|
|
1183
|
+
quoteIdent({ name }) {
|
|
1184
|
+
return `\`${name.replaceAll("`", "``")}\``;
|
|
1185
|
+
},
|
|
1186
|
+
quoteStringLiteral({ value }) {
|
|
1187
|
+
return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "''")}'`;
|
|
1188
|
+
},
|
|
1189
|
+
placeholder() {
|
|
1190
|
+
return "?";
|
|
1191
|
+
},
|
|
1192
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
1193
|
+
if (offsetSeconds === 0)
|
|
1194
|
+
return "NOW(6)";
|
|
1195
|
+
return `DATE_ADD(NOW(6), INTERVAL ${collector.add(offsetSeconds)} SECOND)`;
|
|
1196
|
+
}
|
|
376
1197
|
};
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
1198
|
+
// packages/sql/src/dialects/registry.ts
|
|
1199
|
+
var SQL_DIALECTS = {
|
|
1200
|
+
postgres: postgresDialect,
|
|
1201
|
+
sqlite: sqliteDialect,
|
|
1202
|
+
mysql: mysqlDialect
|
|
381
1203
|
};
|
|
1204
|
+
function getSqlDialect(params) {
|
|
1205
|
+
return SQL_DIALECTS[params.dialect];
|
|
1206
|
+
}
|
|
382
1207
|
// packages/sql/src/dialects/diagnostics.ts
|
|
383
|
-
import { unsupportedCapability } from "@vibeorm/schema";
|
|
1208
|
+
import { unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
384
1209
|
var QUERIES = Object.freeze({
|
|
385
1210
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
386
1211
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -388,7 +1213,7 @@ var QUERIES = Object.freeze({
|
|
|
388
1213
|
function renderDiagnostic(params) {
|
|
389
1214
|
const query = QUERIES[params.query];
|
|
390
1215
|
if (params.dialect.name !== query.dialect) {
|
|
391
|
-
throw
|
|
1216
|
+
throw unsupportedCapability4({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
392
1217
|
}
|
|
393
1218
|
return { text: query.text, values: [], returning: "none" };
|
|
394
1219
|
}
|
|
@@ -397,26 +1222,7 @@ function isFixedDiagnostic(params) {
|
|
|
397
1222
|
}
|
|
398
1223
|
|
|
399
1224
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
400
|
-
import { VibeError as
|
|
401
|
-
|
|
402
|
-
// packages/sql/src/params.ts
|
|
403
|
-
class ParamCollector {
|
|
404
|
-
dialect;
|
|
405
|
-
values;
|
|
406
|
-
constructor(dialect) {
|
|
407
|
-
this.dialect = dialect;
|
|
408
|
-
this.values = [];
|
|
409
|
-
}
|
|
410
|
-
add(value) {
|
|
411
|
-
this.values.push(value);
|
|
412
|
-
return this.dialect.placeholder({ index: this.values.length });
|
|
413
|
-
}
|
|
414
|
-
addMany(values) {
|
|
415
|
-
return values.map((value) => this.add(value));
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
1225
|
+
import { VibeError as VibeError6, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
420
1226
|
function renderPostgresExplain(params) {
|
|
421
1227
|
const { dialect, query, options } = params;
|
|
422
1228
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -451,7 +1257,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
451
1257
|
});
|
|
452
1258
|
function requirePostgres(params) {
|
|
453
1259
|
if (params.dialect.name !== "postgres") {
|
|
454
|
-
throw
|
|
1260
|
+
throw unsupportedCapability5({
|
|
455
1261
|
dialect: params.dialect.name,
|
|
456
1262
|
feature: params.feature,
|
|
457
1263
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -469,7 +1275,7 @@ function renderPostgresWorkload(params) {
|
|
|
469
1275
|
if (query === "statementSnapshot") {
|
|
470
1276
|
const bounded = limit ?? 20;
|
|
471
1277
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
472
|
-
throw new
|
|
1278
|
+
throw new VibeError6({
|
|
473
1279
|
code: "VIBE_CONFIG",
|
|
474
1280
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
475
1281
|
meta: { limit }
|
|
@@ -488,7 +1294,7 @@ function renderPostgresWorkload(params) {
|
|
|
488
1294
|
function requireArg(params) {
|
|
489
1295
|
const { value, name, query } = params;
|
|
490
1296
|
if (value === undefined || value.length === 0) {
|
|
491
|
-
throw new
|
|
1297
|
+
throw new VibeError6({
|
|
492
1298
|
code: "VIBE_CONFIG",
|
|
493
1299
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
494
1300
|
meta: { query, argument: name }
|
|
@@ -498,10 +1304,10 @@ function requireArg(params) {
|
|
|
498
1304
|
}
|
|
499
1305
|
|
|
500
1306
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
501
|
-
import { unsupportedCapability as
|
|
1307
|
+
import { unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
502
1308
|
|
|
503
1309
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
504
|
-
import { RlsContextError, VibeError as
|
|
1310
|
+
import { RlsContextError, VibeError as VibeError7, assertValidSchemaIR, unsupportedCapability as unsupportedCapability6, validateRlsContextIR } from "@vibeorm/schema";
|
|
505
1311
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
506
1312
|
var COLUMN_TYPES = {
|
|
507
1313
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -514,20 +1320,20 @@ var COLUMN_TYPES = {
|
|
|
514
1320
|
};
|
|
515
1321
|
function requireRls(params) {
|
|
516
1322
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
517
|
-
throw
|
|
1323
|
+
throw unsupportedCapability6({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
518
1324
|
}
|
|
519
1325
|
}
|
|
520
|
-
function
|
|
1326
|
+
function identifier2(params) {
|
|
521
1327
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
522
|
-
throw new
|
|
1328
|
+
throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
523
1329
|
}
|
|
524
1330
|
return params.dialect.quoteIdent({ name: params.name });
|
|
525
1331
|
}
|
|
526
1332
|
function tableRef(params) {
|
|
527
1333
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
528
|
-
throw
|
|
1334
|
+
throw unsupportedCapability6({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
529
1335
|
}
|
|
530
|
-
return `${
|
|
1336
|
+
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
531
1337
|
}
|
|
532
1338
|
function query(params) {
|
|
533
1339
|
return { text: params.text, values: params.values ?? [], returning: "none" };
|
|
@@ -550,7 +1356,7 @@ function renderRlsNoForce({ dialect, table }) {
|
|
|
550
1356
|
}
|
|
551
1357
|
function renderRlsDropPolicy({ dialect, table, policyName }) {
|
|
552
1358
|
requireRls({ dialect });
|
|
553
|
-
const name =
|
|
1359
|
+
const name = identifier2({ dialect, name: policyName ?? NATIVE_RLS.policyName });
|
|
554
1360
|
return query({ text: `DROP POLICY ${name} ON ${tableRef({ dialect, table })}` });
|
|
555
1361
|
}
|
|
556
1362
|
function renderRlsColumnType(params) {
|
|
@@ -566,14 +1372,14 @@ function renderRlsColumnType(params) {
|
|
|
566
1372
|
if (native !== undefined)
|
|
567
1373
|
return native;
|
|
568
1374
|
}
|
|
569
|
-
throw
|
|
1375
|
+
throw unsupportedCapability6({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
570
1376
|
}
|
|
571
1377
|
function protectedModel(params) {
|
|
572
1378
|
requireRls(params);
|
|
573
1379
|
assertValidSchemaIR({ schema: params.schema });
|
|
574
1380
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
575
1381
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
576
|
-
throw new
|
|
1382
|
+
throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
577
1383
|
}
|
|
578
1384
|
return model;
|
|
579
1385
|
}
|
|
@@ -589,7 +1395,7 @@ function predicate(params) {
|
|
|
589
1395
|
if (typeof rule === "object" && "where" in rule) {
|
|
590
1396
|
return Object.entries(rule.where).map(([name, ref]) => {
|
|
591
1397
|
const field = model.fields.find((entry) => entry.name === name);
|
|
592
|
-
const column =
|
|
1398
|
+
const column = identifier2({ dialect, name: field.dbName ?? field.name });
|
|
593
1399
|
const physicalType = renderRlsColumnType({ dialect, field });
|
|
594
1400
|
return `(${column} = NULLIF(current_setting('${NATIVE_RLS.settingPrefix}${ref.context}', true), '')::${physicalType})`;
|
|
595
1401
|
}).join(" AND ");
|
|
@@ -597,21 +1403,21 @@ function predicate(params) {
|
|
|
597
1403
|
if (typeof rule === "object" && "through" in rule) {
|
|
598
1404
|
const relation = model.relations.find((entry) => entry.name === rule.through);
|
|
599
1405
|
const parent = schema.models.find((entry) => entry.name === relation.targetModel);
|
|
600
|
-
const alias =
|
|
601
|
-
const child =
|
|
1406
|
+
const alias = identifier2({ dialect, name: NATIVE_RLS.parentAlias });
|
|
1407
|
+
const child = identifier2({ dialect, name: model.dbName ?? model.name });
|
|
602
1408
|
const join = relation.fields.map((name, index) => {
|
|
603
1409
|
const childField = model.fields.find((field) => field.name === name);
|
|
604
1410
|
const parentField = parent.fields.find((field) => field.name === relation.references[index]);
|
|
605
|
-
return `(${alias}.${
|
|
1411
|
+
return `(${alias}.${identifier2({ dialect, name: parentField.dbName ?? parentField.name })} = ${child}.${identifier2({ dialect, name: childField.dbName ?? childField.name })})`;
|
|
606
1412
|
}).join(" AND ");
|
|
607
1413
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
608
1414
|
}
|
|
609
|
-
throw new
|
|
1415
|
+
throw new VibeError7({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
610
1416
|
}
|
|
611
1417
|
function renderRlsPolicy(params) {
|
|
612
1418
|
const model = protectedModel(params);
|
|
613
1419
|
const expression = predicate({ dialect: params.dialect, schema: params.schema, model });
|
|
614
|
-
const name =
|
|
1420
|
+
const name = identifier2({ dialect: params.dialect, name: params.policyName ?? NATIVE_RLS.policyName });
|
|
615
1421
|
const table = tableRef({ dialect: params.dialect, table: model.dbName ?? model.name, schema: params.targetSchema });
|
|
616
1422
|
return query({ text: `CREATE POLICY ${name} ON ${table} AS PERMISSIVE FOR ALL TO PUBLIC USING (${expression}) WITH CHECK (${expression})` });
|
|
617
1423
|
}
|
|
@@ -640,7 +1446,7 @@ function renderRlsContext(params) {
|
|
|
640
1446
|
function renderRlsRole(params) {
|
|
641
1447
|
requireRls(params);
|
|
642
1448
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
643
|
-
throw new
|
|
1449
|
+
throw new VibeError7({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
644
1450
|
}
|
|
645
1451
|
const collector = new ParamCollector(params.dialect);
|
|
646
1452
|
return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
|
|
@@ -648,7 +1454,7 @@ function renderRlsRole(params) {
|
|
|
648
1454
|
|
|
649
1455
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
650
1456
|
var NATIVE_RLS_PROBE_POLICY = "vibeorm_probe";
|
|
651
|
-
var
|
|
1457
|
+
var CATALOG_READS = Object.freeze({
|
|
652
1458
|
identity: `SELECT current_user::text AS "currentUser",
|
|
653
1459
|
session_user::text AS "sessionUser",
|
|
654
1460
|
r.rolsuper AS "super",
|
|
@@ -711,17 +1517,6 @@ FROM pg_catalog.pg_policy p
|
|
|
711
1517
|
JOIN pg_catalog.pg_class c ON c.oid = p.polrelid
|
|
712
1518
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
713
1519
|
WHERE n.nspname = $1
|
|
714
|
-
ORDER BY c.relname, p.polname`,
|
|
715
|
-
expressions: `SELECT c.relname::text AS "table",
|
|
716
|
-
p.polname::text AS "name",
|
|
717
|
-
(n.nspname <> $1) AS "probe",
|
|
718
|
-
pg_catalog.pg_get_expr(p.polqual, p.polrelid) AS "using",
|
|
719
|
-
pg_catalog.pg_get_expr(p.polwithcheck, p.polrelid) AS "withCheck"
|
|
720
|
-
FROM pg_catalog.pg_policy p
|
|
721
|
-
JOIN pg_catalog.pg_class c ON c.oid = p.polrelid
|
|
722
|
-
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
723
|
-
WHERE (n.nspname = $1 AND p.polname = $2)
|
|
724
|
-
OR (c.relnamespace = pg_catalog.pg_my_temp_schema() AND p.polname = $3)
|
|
725
1520
|
ORDER BY c.relname, p.polname`,
|
|
726
1521
|
views: `SELECT n.nspname::text AS "schema",
|
|
727
1522
|
c.relname::text AS "name",
|
|
@@ -769,13 +1564,41 @@ JOIN pg_catalog.pg_proc f ON f.oid = t.tgfoid
|
|
|
769
1564
|
WHERE n.nspname = $1
|
|
770
1565
|
AND NOT t.tgisinternal
|
|
771
1566
|
AND f.prosecdef
|
|
772
|
-
ORDER BY c.relname, t.tgname
|
|
1567
|
+
ORDER BY c.relname, t.tgname`
|
|
1568
|
+
});
|
|
1569
|
+
var RLS_READINESS_CATALOG_KEYS = Object.freeze(["identity", "elevatedRoles", "schemaControl", "tables", "policies", "views", "routines", "triggers"]);
|
|
1570
|
+
var COMPOSITE_CATALOG = `SELECT
|
|
1571
|
+
${RLS_READINESS_CATALOG_KEYS.map((key) => ` (SELECT json_agg(t)::text FROM (
|
|
1572
|
+
${CATALOG_READS[key]}
|
|
1573
|
+
) t) AS "${key}"`).join(`,
|
|
1574
|
+
`)}`;
|
|
1575
|
+
var RLS_READINESS_SQL = Object.freeze({
|
|
1576
|
+
identity: CATALOG_READS.identity,
|
|
1577
|
+
elevatedRoles: CATALOG_READS.elevatedRoles,
|
|
1578
|
+
schemaControl: CATALOG_READS.schemaControl,
|
|
1579
|
+
tables: CATALOG_READS.tables,
|
|
1580
|
+
policies: CATALOG_READS.policies,
|
|
1581
|
+
expressions: `SELECT c.relname::text AS "table",
|
|
1582
|
+
p.polname::text AS "name",
|
|
1583
|
+
(n.nspname <> $1) AS "probe",
|
|
1584
|
+
pg_catalog.pg_get_expr(p.polqual, p.polrelid) AS "using",
|
|
1585
|
+
pg_catalog.pg_get_expr(p.polwithcheck, p.polrelid) AS "withCheck"
|
|
1586
|
+
FROM pg_catalog.pg_policy p
|
|
1587
|
+
JOIN pg_catalog.pg_class c ON c.oid = p.polrelid
|
|
1588
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
1589
|
+
WHERE (n.nspname = $1 AND p.polname = $2)
|
|
1590
|
+
OR (c.relnamespace = pg_catalog.pg_my_temp_schema() AND p.polname = $3)
|
|
1591
|
+
ORDER BY c.relname, p.polname`,
|
|
1592
|
+
views: CATALOG_READS.views,
|
|
1593
|
+
routines: CATALOG_READS.routines,
|
|
1594
|
+
triggers: CATALOG_READS.triggers,
|
|
1595
|
+
catalog: COMPOSITE_CATALOG,
|
|
773
1596
|
readSearchPath: `SELECT pg_catalog.current_setting('search_path') AS "searchPath"`,
|
|
774
1597
|
setSearchPath: `SELECT pg_catalog.set_config('search_path', $1, true)`
|
|
775
1598
|
});
|
|
776
1599
|
function requireRls2(params) {
|
|
777
1600
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
778
|
-
throw
|
|
1601
|
+
throw unsupportedCapability7({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
779
1602
|
}
|
|
780
1603
|
}
|
|
781
1604
|
function renderRlsReadinessQuery(params) {
|
|
@@ -807,7 +1630,7 @@ function renderRlsPolicyPath(params) {
|
|
|
807
1630
|
}
|
|
808
1631
|
|
|
809
1632
|
// packages/sql/src/render.ts
|
|
810
|
-
import { internalError, unsupportedCapability as
|
|
1633
|
+
import { internalError as internalError2, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
811
1634
|
|
|
812
1635
|
// packages/sql/src/like.ts
|
|
813
1636
|
function escapeLikePattern(params) {
|
|
@@ -853,6 +1676,8 @@ function renderStatement(params) {
|
|
|
853
1676
|
}
|
|
854
1677
|
case "select":
|
|
855
1678
|
return renderSelect({ dialect, statement, collector });
|
|
1679
|
+
case "advisoryLock":
|
|
1680
|
+
return renderAdvisoryLock({ dialect, statement, collector });
|
|
856
1681
|
case "insert":
|
|
857
1682
|
return renderInsert({ dialect, statement, collector });
|
|
858
1683
|
case "update":
|
|
@@ -888,7 +1713,7 @@ function quoteCol(dialect, column) {
|
|
|
888
1713
|
const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
|
|
889
1714
|
if (column.castReal === true) {
|
|
890
1715
|
if (dialect.name !== "sqlite") {
|
|
891
|
-
throw
|
|
1716
|
+
throw internalError2({ message: "ColRef.castReal is a sqlite Decimal construct" });
|
|
892
1717
|
}
|
|
893
1718
|
return `CAST(${ref} AS REAL)`;
|
|
894
1719
|
}
|
|
@@ -902,7 +1727,7 @@ function quoteProjection(dialect, column) {
|
|
|
902
1727
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
903
1728
|
}
|
|
904
1729
|
if (dialect.name !== "postgres") {
|
|
905
|
-
throw
|
|
1730
|
+
throw internalError2({ message: "ColRef.transform is a postgres lateral-join construct" });
|
|
906
1731
|
}
|
|
907
1732
|
const ref = quoteCol(dialect, column);
|
|
908
1733
|
const alias = dialect.quoteIdent({ name: column.name });
|
|
@@ -918,10 +1743,10 @@ function renderTable(dialect, table) {
|
|
|
918
1743
|
}
|
|
919
1744
|
function assertCount(value, what) {
|
|
920
1745
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
921
|
-
throw
|
|
1746
|
+
throw internalError2({ message: `${what} must be a non-negative integer, got ${value}` });
|
|
922
1747
|
}
|
|
923
1748
|
}
|
|
924
|
-
var
|
|
1749
|
+
var COMPARISON_SQL2 = {
|
|
925
1750
|
eq: "=",
|
|
926
1751
|
ne: "<>",
|
|
927
1752
|
gt: ">",
|
|
@@ -950,7 +1775,7 @@ function renderAggRef(dialect, agg) {
|
|
|
950
1775
|
function assertScalarArrays(dialect, feature) {
|
|
951
1776
|
if (dialect.capabilities.scalarArrays)
|
|
952
1777
|
return;
|
|
953
|
-
throw
|
|
1778
|
+
throw unsupportedCapability8({
|
|
954
1779
|
dialect: dialect.name,
|
|
955
1780
|
feature,
|
|
956
1781
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -960,9 +1785,9 @@ function renderExpr(params) {
|
|
|
960
1785
|
const { dialect, expr, collector } = params;
|
|
961
1786
|
switch (expr.kind) {
|
|
962
1787
|
case "cmp": {
|
|
963
|
-
const op =
|
|
1788
|
+
const op = COMPARISON_SQL2[expr.op];
|
|
964
1789
|
if (op === undefined) {
|
|
965
|
-
throw
|
|
1790
|
+
throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
|
|
966
1791
|
}
|
|
967
1792
|
if (isDbNowMarker(expr.value)) {
|
|
968
1793
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
@@ -970,9 +1795,9 @@ function renderExpr(params) {
|
|
|
970
1795
|
return `${quoteCol(dialect, expr.column)} ${op} ${collector.add(expr.value)}`;
|
|
971
1796
|
}
|
|
972
1797
|
case "cmpCol": {
|
|
973
|
-
const op =
|
|
1798
|
+
const op = COMPARISON_SQL2[expr.op];
|
|
974
1799
|
if (op === undefined) {
|
|
975
|
-
throw
|
|
1800
|
+
throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
|
|
976
1801
|
}
|
|
977
1802
|
return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
|
|
978
1803
|
}
|
|
@@ -992,11 +1817,11 @@ function renderExpr(params) {
|
|
|
992
1817
|
}
|
|
993
1818
|
case "tupleIn": {
|
|
994
1819
|
if (expr.columns.length === 0) {
|
|
995
|
-
throw
|
|
1820
|
+
throw internalError2({ message: "tupleIn needs at least one column" });
|
|
996
1821
|
}
|
|
997
1822
|
for (const row of expr.rows) {
|
|
998
1823
|
if (row.length !== expr.columns.length) {
|
|
999
|
-
throw
|
|
1824
|
+
throw internalError2({
|
|
1000
1825
|
message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
|
|
1001
1826
|
});
|
|
1002
1827
|
}
|
|
@@ -1068,9 +1893,9 @@ function renderExpr(params) {
|
|
|
1068
1893
|
case "not":
|
|
1069
1894
|
return `NOT (${renderExpr({ dialect, expr: expr.operand, collector })})`;
|
|
1070
1895
|
case "aggCmp": {
|
|
1071
|
-
const op =
|
|
1896
|
+
const op = COMPARISON_SQL2[expr.op];
|
|
1072
1897
|
if (op === undefined) {
|
|
1073
|
-
throw
|
|
1898
|
+
throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
|
|
1074
1899
|
}
|
|
1075
1900
|
return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
|
|
1076
1901
|
}
|
|
@@ -1098,6 +1923,8 @@ function renderExpr(params) {
|
|
|
1098
1923
|
const sub = renderSelect({ dialect, statement: expr.select, collector });
|
|
1099
1924
|
return `${expr.negate === true ? "NOT " : ""}EXISTS (${sub.text})`;
|
|
1100
1925
|
}
|
|
1926
|
+
case "composed":
|
|
1927
|
+
return `(${renderSqlFragment({ dialect, fragment: expr.fragment, collector })})`;
|
|
1101
1928
|
case "fragment":
|
|
1102
1929
|
return renderFragment({ dialect, fragment: expr.fragment, collector });
|
|
1103
1930
|
case "raw":
|
|
@@ -1134,7 +1961,7 @@ function jsonPathString(path) {
|
|
|
1134
1961
|
function jsonStringPattern(params) {
|
|
1135
1962
|
const { expr } = params;
|
|
1136
1963
|
if (typeof expr.value !== "string") {
|
|
1137
|
-
throw
|
|
1964
|
+
throw internalError2({ message: `json ${expr.op} expects a string operand` });
|
|
1138
1965
|
}
|
|
1139
1966
|
if (expr.op === "string_contains")
|
|
1140
1967
|
return containsPattern({ raw: expr.value });
|
|
@@ -1158,7 +1985,7 @@ function renderJsonLike(params) {
|
|
|
1158
1985
|
function renderJsonFilter(params) {
|
|
1159
1986
|
const { dialect, expr, collector } = params;
|
|
1160
1987
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
1161
|
-
throw
|
|
1988
|
+
throw unsupportedCapability8({
|
|
1162
1989
|
dialect: dialect.name,
|
|
1163
1990
|
feature: `the json filter "${expr.op}"`,
|
|
1164
1991
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -1213,7 +2040,7 @@ function renderJsonFilter(params) {
|
|
|
1213
2040
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
1214
2041
|
const tests = elements.map((element) => {
|
|
1215
2042
|
if (element === null || typeof element === "object" && element !== null) {
|
|
1216
|
-
throw
|
|
2043
|
+
throw unsupportedCapability8({
|
|
1217
2044
|
dialect: dialect.name,
|
|
1218
2045
|
feature: "json array_contains with object/array/null elements",
|
|
1219
2046
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -1225,7 +2052,7 @@ function renderJsonFilter(params) {
|
|
|
1225
2052
|
return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
|
|
1226
2053
|
}
|
|
1227
2054
|
default:
|
|
1228
|
-
throw
|
|
2055
|
+
throw internalError2({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
|
|
1229
2056
|
}
|
|
1230
2057
|
}
|
|
1231
2058
|
const column = quoteCol(dialect, expr.column);
|
|
@@ -1280,11 +2107,11 @@ function renderSelectExpr(params) {
|
|
|
1280
2107
|
function renderPositionalRow(params) {
|
|
1281
2108
|
const { dialect, join, subAlias } = params;
|
|
1282
2109
|
if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || join.select.rowNumber !== undefined) {
|
|
1283
|
-
throw
|
|
2110
|
+
throw internalError2({ message: "positional lateral rows require an explicit column-only projection" });
|
|
1284
2111
|
}
|
|
1285
2112
|
const names = join.select.columns.map((column) => projectionOutputName({ column }));
|
|
1286
2113
|
if (new Set(names).size !== names.length) {
|
|
1287
|
-
throw
|
|
2114
|
+
throw internalError2({ message: "positional lateral projection has duplicate output names" });
|
|
1288
2115
|
}
|
|
1289
2116
|
const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
|
|
1290
2117
|
const chunks = [];
|
|
@@ -1292,7 +2119,7 @@ function renderPositionalRow(params) {
|
|
|
1292
2119
|
chunks.push(`json_build_array(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
|
|
1293
2120
|
}
|
|
1294
2121
|
if (chunks.length > JSON_TRANSPORT.chunkSize) {
|
|
1295
|
-
throw
|
|
2122
|
+
throw internalError2({ message: "positional lateral projection exceeds the transport width limit" });
|
|
1296
2123
|
}
|
|
1297
2124
|
return chunks.length === 1 ? chunks[0] : `json_build_array(${chunks.join(", ")})`;
|
|
1298
2125
|
}
|
|
@@ -1301,7 +2128,7 @@ function renderLateralJoin(params) {
|
|
|
1301
2128
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
1302
2129
|
if (join.agg === "count") {
|
|
1303
2130
|
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) {
|
|
1304
|
-
throw
|
|
2131
|
+
throw internalError2({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
|
|
1305
2132
|
}
|
|
1306
2133
|
return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
|
|
1307
2134
|
}
|
|
@@ -1336,10 +2163,10 @@ function applyReturning(params) {
|
|
|
1336
2163
|
function renderSelect(params) {
|
|
1337
2164
|
const { dialect, statement, collector } = params;
|
|
1338
2165
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
1339
|
-
throw
|
|
2166
|
+
throw internalError2({ message: "only countAll can combine with a column list" });
|
|
1340
2167
|
}
|
|
1341
2168
|
if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
|
|
1342
|
-
throw
|
|
2169
|
+
throw internalError2({ message: "selectExpr and aggregates cannot combine" });
|
|
1343
2170
|
}
|
|
1344
2171
|
const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
|
|
1345
2172
|
if (statement.selectExpr !== undefined) {
|
|
@@ -1353,7 +2180,7 @@ function renderSelect(params) {
|
|
|
1353
2180
|
if (statement.rowNumber !== undefined) {
|
|
1354
2181
|
const rowNumber = statement.rowNumber;
|
|
1355
2182
|
if (rowNumber.partitionBy.length === 0) {
|
|
1356
|
-
throw
|
|
2183
|
+
throw internalError2({ message: "rowNumber needs at least one partition column" });
|
|
1357
2184
|
}
|
|
1358
2185
|
const partition = rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ");
|
|
1359
2186
|
const order = rowNumber.orderBy.length === 0 ? "" : ` ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
@@ -1362,7 +2189,7 @@ function renderSelect(params) {
|
|
|
1362
2189
|
let distinctPrefix = "";
|
|
1363
2190
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
1364
2191
|
if (!dialect.capabilities.distinctOn) {
|
|
1365
|
-
throw
|
|
2192
|
+
throw unsupportedCapability8({
|
|
1366
2193
|
dialect: dialect.name,
|
|
1367
2194
|
feature: "SELECT DISTINCT ON",
|
|
1368
2195
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -1374,7 +2201,7 @@ function renderSelect(params) {
|
|
|
1374
2201
|
let from;
|
|
1375
2202
|
if (statement.fromSelect !== undefined) {
|
|
1376
2203
|
if (statement.table.alias !== undefined) {
|
|
1377
|
-
throw
|
|
2204
|
+
throw internalError2({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
|
|
1378
2205
|
}
|
|
1379
2206
|
const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
|
|
1380
2207
|
from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
@@ -1385,7 +2212,7 @@ function renderSelect(params) {
|
|
|
1385
2212
|
if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
|
|
1386
2213
|
for (const join of statement.innerJoins) {
|
|
1387
2214
|
if (join.on.length === 0) {
|
|
1388
|
-
throw
|
|
2215
|
+
throw internalError2({ message: "inner join needs at least one ON pair" });
|
|
1389
2216
|
}
|
|
1390
2217
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
1391
2218
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
@@ -1393,7 +2220,7 @@ function renderSelect(params) {
|
|
|
1393
2220
|
}
|
|
1394
2221
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
1395
2222
|
if (!dialect.capabilities.lateralJoin) {
|
|
1396
|
-
throw
|
|
2223
|
+
throw unsupportedCapability8({
|
|
1397
2224
|
dialect: dialect.name,
|
|
1398
2225
|
feature: "LATERAL JOIN relation loading",
|
|
1399
2226
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -1427,22 +2254,24 @@ function renderSelect(params) {
|
|
|
1427
2254
|
}
|
|
1428
2255
|
text += ` OFFSET ${statement.offset}`;
|
|
1429
2256
|
}
|
|
1430
|
-
if (statement.
|
|
1431
|
-
|
|
2257
|
+
if (statement.rowLock !== undefined) {
|
|
2258
|
+
const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
|
|
2259
|
+
if (clause !== null)
|
|
2260
|
+
text += ` ${clause}`;
|
|
1432
2261
|
}
|
|
1433
2262
|
return { text, values: collector.values, returning: "none" };
|
|
1434
2263
|
}
|
|
1435
2264
|
function renderInsert(params) {
|
|
1436
2265
|
const { dialect, statement, collector } = params;
|
|
1437
2266
|
if (statement.columns.length === 0) {
|
|
1438
|
-
throw
|
|
2267
|
+
throw internalError2({ message: "insert requires at least one column" });
|
|
1439
2268
|
}
|
|
1440
2269
|
if (statement.rows.length === 0) {
|
|
1441
|
-
throw
|
|
2270
|
+
throw internalError2({ message: "insert requires at least one row" });
|
|
1442
2271
|
}
|
|
1443
2272
|
for (const row of statement.rows) {
|
|
1444
2273
|
if (row.length !== statement.columns.length) {
|
|
1445
|
-
throw
|
|
2274
|
+
throw internalError2({
|
|
1446
2275
|
message: `insert row has ${row.length} values but ${statement.columns.length} columns`
|
|
1447
2276
|
});
|
|
1448
2277
|
}
|
|
@@ -1460,14 +2289,14 @@ function renderInsert(params) {
|
|
|
1460
2289
|
if (!isSqlDefaultCell(value))
|
|
1461
2290
|
return collector.add(value);
|
|
1462
2291
|
if (!dialect.rowDefaultKeyword) {
|
|
1463
|
-
throw
|
|
2292
|
+
throw internalError2({
|
|
1464
2293
|
message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
|
|
1465
2294
|
});
|
|
1466
2295
|
}
|
|
1467
2296
|
return "DEFAULT";
|
|
1468
2297
|
}).join(", ")})`).join(", ");
|
|
1469
2298
|
let text = `${insertKeyword} ${renderTable(dialect, statement.table)} (${columnList}) VALUES ${rowsSql}`;
|
|
1470
|
-
if (onConflict !== undefined && !("doNothing" in onConflict) && dialect.excludedRowRef.form === "rowAlias" && Object.values(onConflict.update).some((value) => isExcludedRef(value))) {
|
|
2299
|
+
if (onConflict !== undefined && !("doNothing" in onConflict) && dialect.excludedRowRef.form === "rowAlias" && Object.values(onConflict.update).some((value) => isExcludedRef(value) || isSqlFragment(value) && sqlUsesIncoming({ fragment: value }))) {
|
|
1471
2300
|
text += ` AS ${dialect.quoteIdent({ name: dialect.excludedRowRef.alias })}`;
|
|
1472
2301
|
}
|
|
1473
2302
|
if (onConflict !== undefined) {
|
|
@@ -1476,7 +2305,7 @@ function renderInsert(params) {
|
|
|
1476
2305
|
let target = "";
|
|
1477
2306
|
if (onConflict.constraint !== undefined) {
|
|
1478
2307
|
if (dialect.name !== "postgres") {
|
|
1479
|
-
throw
|
|
2308
|
+
throw unsupportedCapability8({
|
|
1480
2309
|
dialect: dialect.name,
|
|
1481
2310
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
1482
2311
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -1491,7 +2320,7 @@ function renderInsert(params) {
|
|
|
1491
2320
|
}
|
|
1492
2321
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
1493
2322
|
} else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
1494
|
-
throw
|
|
2323
|
+
throw unsupportedCapability8({
|
|
1495
2324
|
dialect: dialect.name,
|
|
1496
2325
|
feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
|
|
1497
2326
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -1500,23 +2329,23 @@ function renderInsert(params) {
|
|
|
1500
2329
|
} else {
|
|
1501
2330
|
const updates = Object.entries(onConflict.update);
|
|
1502
2331
|
if (updates.length === 0) {
|
|
1503
|
-
throw
|
|
2332
|
+
throw internalError2({ message: "upsert update must set at least one column" });
|
|
1504
2333
|
}
|
|
1505
2334
|
const selfRef = dialect.quoteIdent({ name: statement.table.name });
|
|
1506
|
-
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true });
|
|
2335
|
+
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
|
|
1507
2336
|
if (dialect.upsertForm === "onConflict") {
|
|
1508
2337
|
if (onConflict.lastInsertIdColumn !== undefined) {
|
|
1509
|
-
throw
|
|
2338
|
+
throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
1510
2339
|
}
|
|
1511
2340
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
1512
|
-
throw
|
|
2341
|
+
throw unsupportedCapability8({
|
|
1513
2342
|
dialect: dialect.name,
|
|
1514
2343
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
1515
2344
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
1516
2345
|
});
|
|
1517
2346
|
}
|
|
1518
2347
|
if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
|
|
1519
|
-
throw
|
|
2348
|
+
throw internalError2({
|
|
1520
2349
|
message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
|
|
1521
2350
|
});
|
|
1522
2351
|
}
|
|
@@ -1525,14 +2354,14 @@ function renderInsert(params) {
|
|
|
1525
2354
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
1526
2355
|
} else {
|
|
1527
2356
|
if (onConflict.constraint !== undefined) {
|
|
1528
|
-
throw
|
|
2357
|
+
throw unsupportedCapability8({
|
|
1529
2358
|
dialect: dialect.name,
|
|
1530
2359
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
1531
2360
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
1532
2361
|
});
|
|
1533
2362
|
}
|
|
1534
2363
|
if (onConflict.conflictWhere !== undefined) {
|
|
1535
|
-
throw
|
|
2364
|
+
throw unsupportedCapability8({
|
|
1536
2365
|
dialect: dialect.name,
|
|
1537
2366
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
1538
2367
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -1550,7 +2379,7 @@ function renderUpdate(params) {
|
|
|
1550
2379
|
const { dialect, statement, collector } = params;
|
|
1551
2380
|
const assignments = Object.entries(statement.set);
|
|
1552
2381
|
if (assignments.length === 0) {
|
|
1553
|
-
throw
|
|
2382
|
+
throw internalError2({ message: "update must set at least one column" });
|
|
1554
2383
|
}
|
|
1555
2384
|
const setSql = renderAssignments({ dialect, entries: assignments, collector });
|
|
1556
2385
|
let text = `UPDATE ${renderTable(dialect, statement.table)} SET ${setSql}`;
|
|
@@ -1571,9 +2400,14 @@ function renderAssignments(params) {
|
|
|
1571
2400
|
return entries.map(([name, value]) => {
|
|
1572
2401
|
const column = dialect.quoteIdent({ name });
|
|
1573
2402
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
2403
|
+
if (isSqlFragment(value)) {
|
|
2404
|
+
if (params.allowExcluded !== true || selfRef === undefined)
|
|
2405
|
+
throw internalError2({ message: "SQL assignment fragments require a conflict update" });
|
|
2406
|
+
return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
|
|
2407
|
+
}
|
|
1574
2408
|
if (isExcludedRef(value)) {
|
|
1575
2409
|
if (params.allowExcluded !== true) {
|
|
1576
|
-
throw
|
|
2410
|
+
throw internalError2({
|
|
1577
2411
|
message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
|
|
1578
2412
|
});
|
|
1579
2413
|
}
|
|
@@ -1607,12 +2441,377 @@ function renderDelete(params) {
|
|
|
1607
2441
|
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
1608
2442
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
1609
2443
|
}
|
|
2444
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
2445
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
2446
|
+
function tableDefinitionParts(params) {
|
|
2447
|
+
const sql2 = params.sql;
|
|
2448
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql2))
|
|
2449
|
+
throw unavailable();
|
|
2450
|
+
const parts = [];
|
|
2451
|
+
let start = -1;
|
|
2452
|
+
let depth = 0;
|
|
2453
|
+
let quote;
|
|
2454
|
+
for (let index = 0;index < sql2.length; index++) {
|
|
2455
|
+
const char = sql2[index];
|
|
2456
|
+
if (quote !== undefined) {
|
|
2457
|
+
if (char === quote) {
|
|
2458
|
+
if (quote !== "]" && sql2[index + 1] === quote)
|
|
2459
|
+
index++;
|
|
2460
|
+
else
|
|
2461
|
+
quote = undefined;
|
|
2462
|
+
}
|
|
2463
|
+
continue;
|
|
2464
|
+
}
|
|
2465
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
2466
|
+
quote = char;
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2469
|
+
if (char === "[") {
|
|
2470
|
+
quote = "]";
|
|
2471
|
+
continue;
|
|
2472
|
+
}
|
|
2473
|
+
if (char === "-" && sql2[index + 1] === "-") {
|
|
2474
|
+
const end = sql2.indexOf(`
|
|
2475
|
+
`, index + 2);
|
|
2476
|
+
if (end < 0)
|
|
2477
|
+
break;
|
|
2478
|
+
index = end;
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
if (char === "/" && sql2[index + 1] === "*") {
|
|
2482
|
+
const end = sql2.indexOf("*/", index + 2);
|
|
2483
|
+
if (end < 0)
|
|
2484
|
+
break;
|
|
2485
|
+
index = end + 1;
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (char === "(") {
|
|
2489
|
+
if (depth === 0)
|
|
2490
|
+
start = index + 1;
|
|
2491
|
+
depth++;
|
|
2492
|
+
} else if (char === ")") {
|
|
2493
|
+
depth--;
|
|
2494
|
+
if (depth === 0 && start >= 0) {
|
|
2495
|
+
parts.push(sql2.slice(start, index).trim());
|
|
2496
|
+
return { clauses: parts, suffix: sql2.slice(index + 1) };
|
|
2497
|
+
}
|
|
2498
|
+
} else if (char === "," && depth === 1) {
|
|
2499
|
+
parts.push(sql2.slice(start, index).trim());
|
|
2500
|
+
start = index + 1;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
throw unavailable();
|
|
2504
|
+
}
|
|
2505
|
+
function unavailable() {
|
|
2506
|
+
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
2507
|
+
}
|
|
2508
|
+
function sqliteColumnClauses(params) {
|
|
2509
|
+
const columns = [];
|
|
2510
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
2511
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
2512
|
+
if (token === null)
|
|
2513
|
+
throw unavailable();
|
|
2514
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
2515
|
+
continue;
|
|
2516
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
2517
|
+
columns.push({ name, sql: part });
|
|
2518
|
+
}
|
|
2519
|
+
return columns;
|
|
2520
|
+
}
|
|
2521
|
+
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
2522
|
+
import { unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
2523
|
+
function renderConstraintOwnedIndexDrop(params) {
|
|
2524
|
+
if (params.dialect.name !== "postgres")
|
|
2525
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
2526
|
+
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
2527
|
+
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
2528
|
+
}
|
|
2529
|
+
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
2530
|
+
import { unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
2531
|
+
function renderMysqlCheckProbe(params) {
|
|
2532
|
+
if (params.dialect.name !== "mysql")
|
|
2533
|
+
throw unsupportedCapability10({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
2534
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2535
|
+
const probe = q(params.probe);
|
|
2536
|
+
switch (params.operation) {
|
|
2537
|
+
case "clone":
|
|
2538
|
+
if (params.table === undefined)
|
|
2539
|
+
throw unsupportedCapability10({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
2540
|
+
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
2541
|
+
case "add": {
|
|
2542
|
+
if (params.expression === undefined || params.definition === undefined)
|
|
2543
|
+
throw unsupportedCapability10({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
2544
|
+
const parts = tableDefinitionParts({ sql: params.definition });
|
|
2545
|
+
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
2546
|
+
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
2547
|
+
`)}
|
|
2548
|
+
)${parts.suffix}`, values: [], returning: "none" };
|
|
2549
|
+
}
|
|
2550
|
+
case "read":
|
|
2551
|
+
return { text: `SHOW CREATE TABLE ${probe}`, values: [], returning: "none" };
|
|
2552
|
+
case "drop":
|
|
2553
|
+
return { text: `DROP TEMPORARY TABLE ${probe}`, values: [], returning: "none" };
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
// packages/sql/src/dialects/migration-history.ts
|
|
2557
|
+
function renderMigrationHistoryQuery(params) {
|
|
2558
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2559
|
+
return { text: `SELECT ${q("name")}, ${q("checksum")}, ${q("statement_index")}, ${q("error")} FROM ${q(params.namespace)}.${q("_vibe_migrations")} ORDER BY ${q("id")}`, values: [], returning: "none" };
|
|
2560
|
+
}
|
|
2561
|
+
// packages/sql/src/dialects/sqlite-constraints.ts
|
|
2562
|
+
var IDENTIFIER = '(?:"(?:[^" ]| |"")+"|`(?:[^`]|``)+`|\\[[^\\]]+\\]|[A-Za-z_][A-Za-z_0-9]*)';
|
|
2563
|
+
function identifier3(value) {
|
|
2564
|
+
const text = value.trim();
|
|
2565
|
+
if (text.startsWith('"'))
|
|
2566
|
+
return text.slice(1, -1).replaceAll('""', '"');
|
|
2567
|
+
if (text.startsWith("`"))
|
|
2568
|
+
return text.slice(1, -1).replaceAll("``", "`");
|
|
2569
|
+
if (text.startsWith("["))
|
|
2570
|
+
return text.slice(1, -1);
|
|
2571
|
+
return text;
|
|
2572
|
+
}
|
|
2573
|
+
function sqliteTableConstraints(params) {
|
|
2574
|
+
const result = [];
|
|
2575
|
+
for (const clause of tableDefinitionParts(params).clauses) {
|
|
2576
|
+
const named = new RegExp(`^CONSTRAINT\\s+(${IDENTIFIER})\\s+`, "i").exec(clause);
|
|
2577
|
+
const body = named === null ? clause : clause.slice(named[0].length);
|
|
2578
|
+
const kind = /^(CHECK|FOREIGN\s+KEY|UNIQUE|PRIMARY\s+KEY)\s*\(/i.exec(body);
|
|
2579
|
+
if (kind === null)
|
|
2580
|
+
continue;
|
|
2581
|
+
const type = /^CHECK$/i.test(kind[1]) ? "check" : /^FOREIGN/i.test(kind[1]) ? "foreignKey" : /^PRIMARY/i.test(kind[1]) ? "primaryKey" : "unique";
|
|
2582
|
+
const parts = tableDefinitionParts({ sql: `CREATE TABLE x ${body.slice(kind[0].indexOf("("))}` });
|
|
2583
|
+
const columns = type === "check" ? [] : parts.clauses.map(identifier3);
|
|
2584
|
+
result.push({ name: named === null ? `@sqlite:${type}:${result.length}` : identifier3(named[1]), named: named !== null, type, sql: body, columns });
|
|
2585
|
+
}
|
|
2586
|
+
return result;
|
|
2587
|
+
}
|
|
2588
|
+
// packages/sql/src/dialects/migration-visibility.ts
|
|
2589
|
+
import { unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
2590
|
+
function renderMigrationVisibility(params) {
|
|
2591
|
+
if (params.dialect.name !== "mysql")
|
|
2592
|
+
throw unsupportedCapability11({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
2593
|
+
const collector = new ParamCollector(params.dialect);
|
|
2594
|
+
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
2595
|
+
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`, values: collector.values, returning: "none" };
|
|
2596
|
+
}
|
|
2597
|
+
// packages/sql/src/dialects/migration-inventory.ts
|
|
2598
|
+
import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
2599
|
+
function renderSqliteColumnInventory(params) {
|
|
2600
|
+
if (params.dialect.name !== "sqlite")
|
|
2601
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
2602
|
+
const collector = new ParamCollector(params.dialect);
|
|
2603
|
+
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" };
|
|
2604
|
+
}
|
|
2605
|
+
function renderSqliteForeignKeyInventory(params) {
|
|
2606
|
+
if (params.dialect.name !== "sqlite")
|
|
2607
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
2608
|
+
const collector = new ParamCollector(params.dialect);
|
|
2609
|
+
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" };
|
|
2610
|
+
}
|
|
2611
|
+
function renderSqliteRebuildObjects(params) {
|
|
2612
|
+
if (params.dialect.name !== "sqlite")
|
|
2613
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
2614
|
+
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" };
|
|
2615
|
+
}
|
|
2616
|
+
function renderMigrationInventory(params) {
|
|
2617
|
+
const collector = new ParamCollector(params.dialect);
|
|
2618
|
+
const namespace = collector.add(params.namespace);
|
|
2619
|
+
if (params.dialect.name === "postgres") {
|
|
2620
|
+
return { text: `WITH relations AS (
|
|
2621
|
+
SELECT c.oid, c.relname, c.relkind FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
2622
|
+
WHERE n.nspname = ${namespace} AND c.relname <> '_vibe_migrations'
|
|
2623
|
+
AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend jd JOIN pg_catalog.pg_class jt ON jt.oid = jd.refobjid WHERE jd.classid = 'pg_class'::regclass AND jd.objid = c.oid AND jd.refclassid = 'pg_class'::regclass AND jt.relname = '_vibe_migrations' AND jt.relnamespace = c.relnamespace)
|
|
2624
|
+
AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = c.oid AND d.deptype = 'e')
|
|
2625
|
+
)
|
|
2626
|
+
SELECT CASE WHEN r.relkind IN ('v','m') THEN 'view' WHEN r.relkind = 'S' THEN 'sequence' ELSE 'table' END AS kind, r.relname::text AS name, NULL::text AS parent, NULL::text AS subtype, NULL::text AS signature, NULL::text AS backing FROM relations r WHERE r.relkind IN ('r','p','v','m','S')
|
|
2627
|
+
UNION ALL SELECT 'constraint', con.conname::text, r.relname::text, con.contype::text, NULL, NULL FROM relations r JOIN pg_catalog.pg_constraint con ON con.conrelid = r.oid WHERE con.contype IN ('c','f','x','u','p')
|
|
2628
|
+
UNION ALL SELECT 'index', ix.relname::text, r.relname::text, NULL, NULL, con.conname::text FROM relations r JOIN pg_catalog.pg_index i ON i.indrelid = r.oid JOIN pg_catalog.pg_class ix ON ix.oid = i.indexrelid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = i.indexrelid AND con.contype IN ('p','u','x')
|
|
2629
|
+
UNION ALL SELECT 'trigger', t.tgname::text, r.relname::text, NULL, NULL, NULL FROM relations r JOIN pg_catalog.pg_trigger t ON t.tgrelid = r.oid WHERE NOT t.tgisinternal
|
|
2630
|
+
UNION ALL SELECT 'column', a.attname::text, r.relname::text, NULL, NULL, NULL FROM relations r JOIN pg_catalog.pg_attribute a ON a.attrelid = r.oid WHERE a.attnum > 0 AND NOT a.attisdropped AND r.relkind IN ('r','p')
|
|
2631
|
+
UNION ALL SELECT CASE WHEN a.attgenerated <> '' THEN 'generatedColumn' ELSE 'columnDefault' END, a.attname::text, r.relname::text, NULL, NULL, NULL FROM relations r JOIN pg_catalog.pg_attribute a ON a.attrelid = r.oid JOIN pg_catalog.pg_attrdef d ON d.adrelid = r.oid AND d.adnum = a.attnum WHERE a.attnum > 0 AND NOT a.attisdropped
|
|
2632
|
+
UNION ALL SELECT 'function', p.proname::text, NULL, NULL, COALESCE((SELECT json_agg(json_build_object('namespace', tn.nspname, 'name', t.typname) ORDER BY a.ord)::text FROM unnest(p.proargtypes) WITH ORDINALITY a(oid, ord) JOIN pg_catalog.pg_type t ON t.oid = a.oid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace), '[]'), NULL FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.prokind = 'f' AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e')
|
|
2633
|
+
ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
2634
|
+
}
|
|
2635
|
+
if (params.dialect.name === "mysql") {
|
|
2636
|
+
const n = () => collector.add(params.namespace);
|
|
2637
|
+
return { text: `SELECT CASE WHEN TABLE_TYPE = 'VIEW' THEN 'view' ELSE 'table' END AS kind, TABLE_NAME AS name, NULL AS parent, NULL AS subtype, NULL AS signature, NULL AS backing FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME <> '_vibe_migrations'
|
|
2638
|
+
UNION ALL SELECT 'constraint', CONSTRAINT_NAME, TABLE_NAME, CONSTRAINT_TYPE, NULL, NULL FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = ${n()} AND TABLE_NAME <> '_vibe_migrations'
|
|
2639
|
+
UNION ALL SELECT 'index', s.INDEX_NAME, s.TABLE_NAME, NULL, NULL, tc.CONSTRAINT_NAME FROM information_schema.STATISTICS s LEFT JOIN information_schema.TABLE_CONSTRAINTS tc ON tc.TABLE_SCHEMA = s.TABLE_SCHEMA AND tc.TABLE_NAME = s.TABLE_NAME AND tc.CONSTRAINT_NAME = s.INDEX_NAME AND tc.CONSTRAINT_TYPE IN ('UNIQUE', 'PRIMARY KEY') WHERE s.TABLE_SCHEMA = ${n()} AND s.TABLE_NAME <> '_vibe_migrations' GROUP BY s.INDEX_NAME, s.TABLE_NAME, tc.CONSTRAINT_NAME
|
|
2640
|
+
UNION ALL SELECT 'trigger', TRIGGER_NAME, EVENT_OBJECT_TABLE, NULL, NULL, NULL FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = ${n()}
|
|
2641
|
+
UNION ALL SELECT 'column', COLUMN_NAME, TABLE_NAME, NULL, NULL, NULL FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ${n()} AND TABLE_NAME <> '_vibe_migrations'
|
|
2642
|
+
UNION ALL SELECT CASE WHEN GENERATION_EXPRESSION <> '' THEN 'generatedColumn' ELSE 'columnDefault' END, COLUMN_NAME, TABLE_NAME, NULL, NULL, NULL FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ${n()} AND TABLE_NAME <> '_vibe_migrations' AND (COLUMN_DEFAULT IS NOT NULL OR GENERATION_EXPRESSION <> '')
|
|
2643
|
+
UNION ALL SELECT 'function', ROUTINE_NAME, NULL, NULL, '[]', NULL FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${n()} AND ROUTINE_TYPE = 'FUNCTION'
|
|
2644
|
+
ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
2645
|
+
}
|
|
2646
|
+
const schema = params.dialect.quoteIdent({ name: params.namespace });
|
|
2647
|
+
return { text: `SELECT type AS kind, name, tbl_name AS parent, sql AS definition FROM ${schema}.sqlite_schema WHERE name NOT LIKE 'sqlite_%' AND name <> '_vibe_migrations' ORDER BY type, tbl_name, name`, values: [], returning: "none" };
|
|
2648
|
+
}
|
|
2649
|
+
// packages/sql/src/dialects/migration-objects.ts
|
|
2650
|
+
import { unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
|
|
2651
|
+
function renderMigrationObjectQuery(params) {
|
|
2652
|
+
const { dialect, identity: object } = params;
|
|
2653
|
+
const collector = new ParamCollector(dialect);
|
|
2654
|
+
const physical = "table" in object ? object.table : object.name;
|
|
2655
|
+
const namespace = collector.add(physical.namespace);
|
|
2656
|
+
const name = collector.add(physical.name);
|
|
2657
|
+
let text;
|
|
2658
|
+
if (dialect.name === "postgres") {
|
|
2659
|
+
const relation = `FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace`;
|
|
2660
|
+
const where = `n.nspname = ${namespace} AND c.relname = ${name}`;
|
|
2661
|
+
switch (object.kind) {
|
|
2662
|
+
case "constraint": {
|
|
2663
|
+
const enforcement = object.constraintType === "foreignKey" ? `, (SELECT bool_and(t.tgenabled IN ('O', 'A')) FROM pg_catalog.pg_trigger t WHERE t.tgconstraint = con.oid) AS "triggersEnabled"` : "";
|
|
2664
|
+
text = `SELECT con.contype::text AS "type", pg_catalog.pg_get_constraintdef(con.oid, false) AS "definition", con.convalidated AS "validated", con.condeferrable AS "deferrable", con.condeferred AS "deferred"${enforcement} ${relation} JOIN pg_catalog.pg_constraint con ON con.conrelid = c.oid WHERE ${where} AND con.conname = ${collector.add(object.name)}`;
|
|
2665
|
+
break;
|
|
2666
|
+
}
|
|
2667
|
+
case "trigger":
|
|
2668
|
+
text = `SELECT pg_catalog.pg_get_triggerdef(t.oid, false) AS "definition", t.tgenabled::text AS "enabled", pn.nspname::text AS "functionNamespace", p.proname::text AS "functionName", pg_catalog.pg_get_function_identity_arguments(p.oid) AS "functionArguments" ${relation} JOIN pg_catalog.pg_trigger t ON t.tgrelid = c.oid JOIN pg_catalog.pg_proc p ON p.oid = t.tgfoid JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace WHERE ${where} AND t.tgname = ${collector.add(object.name)} AND NOT t.tgisinternal`;
|
|
2669
|
+
break;
|
|
2670
|
+
case "function":
|
|
2671
|
+
text = `SELECT pg_catalog.pg_get_functiondef(p.oid) AS "definition", p.provolatile::text AS "volatility", p.prosecdef AS "securityDefiner", p.proisstrict AS "strict", p.proconfig::text AS "config", pg_catalog.pg_get_function_result(p.oid) AS "result" FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.proname = ${name} AND p.prokind = 'f' AND ARRAY(SELECT tn.nspname || '.' || t.typname FROM unnest(p.proargtypes) WITH ORDINALITY a(oid, ord) JOIN pg_catalog.pg_type t ON t.oid = a.oid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace ORDER BY a.ord) = ${collector.add(object.argumentTypes.map((type) => `${type.namespace}.${type.name}`))}::text[]`;
|
|
2672
|
+
break;
|
|
2673
|
+
case "view":
|
|
2674
|
+
text = `SELECT pg_catalog.pg_get_viewdef(c.oid, false) AS "definition", c.relkind::text AS "type", c.reloptions::text AS "options" ${relation} WHERE ${where} AND c.relkind IN ('v', 'm')`;
|
|
2675
|
+
break;
|
|
2676
|
+
case "sequence":
|
|
2677
|
+
text = `SELECT pg_catalog.format_type(s.seqtypid, NULL) AS "type", s.seqstart::text AS "start", s.seqincrement::text AS "increment", s.seqmax::text AS "max", s.seqmin::text AS "min", s.seqcache::text AS "cache", s.seqcycle AS "cycle" ${relation} JOIN pg_catalog.pg_sequence s ON s.seqrelid = c.oid WHERE ${where}`;
|
|
2678
|
+
break;
|
|
2679
|
+
case "column":
|
|
2680
|
+
case "columnDefault":
|
|
2681
|
+
case "generatedColumn":
|
|
2682
|
+
text = `SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) AS "type", a.attnotnull AS "notNull", a.attgenerated::text AS "generated", a.attidentity::text AS "identity", pg_catalog.pg_get_expr(d.adbin, d.adrelid, false) AS "definition" ${relation} JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum WHERE ${where} AND a.attname = ${collector.add(object.column)} AND a.attnum > 0 AND NOT a.attisdropped`;
|
|
2683
|
+
break;
|
|
2684
|
+
case "index":
|
|
2685
|
+
text = `SELECT pg_catalog.pg_get_indexdef(i.indexrelid, 0, false) AS "definition", i.indisunique AS "unique", i.indisvalid AS "valid", i.indisready AS "ready", con.conname::text AS "constraint", con.contype::text AS "constraintType" ${relation} JOIN pg_catalog.pg_index i ON i.indrelid = c.oid JOIN pg_catalog.pg_class ix ON ix.oid = i.indexrelid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = i.indexrelid AND con.contype IN ('p', 'u', 'x') WHERE ${where} AND ix.relname = ${collector.add(object.name)}`;
|
|
2686
|
+
break;
|
|
2687
|
+
case "table":
|
|
2688
|
+
text = `SELECT c.relkind::text AS "type", c.relrowsecurity AS "rls", c.relforcerowsecurity AS "forceRls" ${relation} WHERE ${where} AND c.relkind IN ('r', 'p')`;
|
|
2689
|
+
break;
|
|
2690
|
+
case "postgresExtension":
|
|
2691
|
+
text = `SELECT e.extversion::text AS "version", e.extrelocatable AS "relocatable" FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE n.nspname = ${namespace} AND e.extname = ${name}`;
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
} else if (dialect.name === "mysql") {
|
|
2695
|
+
switch (object.kind) {
|
|
2696
|
+
case "view":
|
|
2697
|
+
text = `SELECT VIEW_DEFINITION AS definition, CHECK_OPTION AS checkOption, SECURITY_TYPE AS securityType, DEFINER AS definer FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name}`;
|
|
2698
|
+
break;
|
|
2699
|
+
case "trigger":
|
|
2700
|
+
text = `SELECT ACTION_STATEMENT AS definition, ACTION_TIMING AS timing, EVENT_MANIPULATION AS event, ACTION_ORDER AS actionOrder, SQL_MODE AS sqlMode, DEFINER AS definer FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = ${namespace} AND EVENT_OBJECT_TABLE = ${name} AND TRIGGER_NAME = ${collector.add(object.name)}`;
|
|
2701
|
+
break;
|
|
2702
|
+
case "function":
|
|
2703
|
+
text = `SELECT ROUTINE_DEFINITION AS definition, DTD_IDENTIFIER AS result, IS_DETERMINISTIC AS deterministic, SQL_DATA_ACCESS AS dataAccess, SECURITY_TYPE AS securityType, SQL_MODE AS sqlMode, DEFINER AS definer FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${namespace} AND ROUTINE_NAME = ${name} AND ROUTINE_TYPE = 'FUNCTION'`;
|
|
2704
|
+
break;
|
|
2705
|
+
case "column":
|
|
2706
|
+
case "columnDefault":
|
|
2707
|
+
case "generatedColumn":
|
|
2708
|
+
text = `SELECT COLUMN_TYPE AS type, IS_NULLABLE AS nullable, COLUMN_DEFAULT AS definition, GENERATION_EXPRESSION AS generated, EXTRA AS extra, COLLATION_NAME AS collation FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name} AND COLUMN_NAME = ${collector.add(object.column)}`;
|
|
2709
|
+
break;
|
|
2710
|
+
case "constraint":
|
|
2711
|
+
if (object.constraintType === "exclusion")
|
|
2712
|
+
throw unsupportedCapability13({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
2713
|
+
text = `SELECT tc.CONSTRAINT_TYPE AS type, tc.ENFORCED AS enforced, cc.CHECK_CLAUSE AS definition, rc.UPDATE_RULE AS onUpdate, rc.DELETE_RULE AS onDelete, rc.MATCH_OPTION AS matchOption FROM information_schema.TABLE_CONSTRAINTS tc LEFT JOIN information_schema.CHECK_CONSTRAINTS cc ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS rc ON rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND rc.TABLE_NAME = tc.TABLE_NAME AND rc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE tc.TABLE_SCHEMA = ${namespace} AND tc.TABLE_NAME = ${name} AND tc.CONSTRAINT_NAME = ${collector.add(object.name)}`;
|
|
2714
|
+
break;
|
|
2715
|
+
case "index":
|
|
2716
|
+
text = `SELECT INDEX_NAME AS name, NON_UNIQUE AS nonUnique, INDEX_TYPE AS method, IS_VISIBLE AS visible FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name} AND INDEX_NAME = ${collector.add(object.name)} GROUP BY INDEX_NAME, NON_UNIQUE, INDEX_TYPE, IS_VISIBLE`;
|
|
2717
|
+
break;
|
|
2718
|
+
case "table":
|
|
2719
|
+
text = `SELECT TABLE_TYPE AS type, ENGINE AS engine, TABLE_COLLATION AS collation FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name}`;
|
|
2720
|
+
break;
|
|
2721
|
+
default:
|
|
2722
|
+
throw unsupportedCapability13({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
2723
|
+
}
|
|
2724
|
+
} else {
|
|
2725
|
+
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
2726
|
+
throw unsupportedCapability13({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
2727
|
+
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
2728
|
+
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
2729
|
+
if (["function", "sequence", "postgresExtension"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
2730
|
+
throw unsupportedCapability13({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
2731
|
+
const sqliteParams = new ParamCollector(dialect);
|
|
2732
|
+
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
2733
|
+
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)}`;
|
|
2734
|
+
return { text, values: sqliteParams.values, returning: "none" };
|
|
2735
|
+
}
|
|
2736
|
+
const objectName = object.kind === "trigger" || object.kind === "index" ? object.name : physical.name;
|
|
2737
|
+
text = `SELECT sql AS definition, type AS type FROM ${catalog} WHERE name = ${sqliteParams.add(objectName)} AND type = ${sqliteParams.add(kind)}`;
|
|
2738
|
+
return { text, values: sqliteParams.values, returning: "none" };
|
|
2739
|
+
}
|
|
2740
|
+
return { text, values: collector.values, returning: "none" };
|
|
2741
|
+
}
|
|
2742
|
+
function renderMysqlObjectMembers(params) {
|
|
2743
|
+
if (params.dialect.name !== "mysql")
|
|
2744
|
+
return;
|
|
2745
|
+
const object = params.identity;
|
|
2746
|
+
if (object.kind !== "function" && object.kind !== "index" && object.kind !== "constraint")
|
|
2747
|
+
return;
|
|
2748
|
+
const collector = new ParamCollector(params.dialect);
|
|
2749
|
+
const physical = "table" in object ? object.table : object.name;
|
|
2750
|
+
const namespace = collector.add(physical.namespace);
|
|
2751
|
+
const name = collector.add(physical.name);
|
|
2752
|
+
const text = object.kind === "function" ? `SELECT PARAMETER_MODE AS mode, PARAMETER_NAME AS name, DTD_IDENTIFIER AS type, CHARACTER_SET_NAME AS characterSet, COLLATION_NAME AS collation FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA = ${namespace} AND SPECIFIC_NAME = ${name} ORDER BY ORDINAL_POSITION` : object.kind === "index" ? `SELECT COLUMN_NAME AS name, EXPRESSION AS expression, SUB_PART AS prefixLength, COLLATION AS collation FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name} AND INDEX_NAME = ${collector.add(object.name)} ORDER BY SEQ_IN_INDEX` : `SELECT COLUMN_NAME AS name, REFERENCED_TABLE_SCHEMA AS referenceNamespace, REFERENCED_TABLE_NAME AS referenceTable, REFERENCED_COLUMN_NAME AS referenceColumn FROM information_schema.KEY_COLUMN_USAGE WHERE CONSTRAINT_SCHEMA = ${namespace} AND TABLE_NAME = ${name} AND CONSTRAINT_NAME = ${collector.add(object.name)} ORDER BY ORDINAL_POSITION`;
|
|
2753
|
+
return { text, values: collector.values, returning: "none" };
|
|
2754
|
+
}
|
|
2755
|
+
function renderMigrationDependencies(params) {
|
|
2756
|
+
if (params.dialect.name !== "postgres")
|
|
2757
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
2758
|
+
const object = params.identity;
|
|
2759
|
+
const physical = "table" in object ? object.table : object.name;
|
|
2760
|
+
const collector = new ParamCollector(params.dialect);
|
|
2761
|
+
const namespace = collector.add(physical.namespace);
|
|
2762
|
+
const name = collector.add(physical.name);
|
|
2763
|
+
const relation = `SELECT c.oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND c.relname = ${name}`;
|
|
2764
|
+
let source;
|
|
2765
|
+
switch (object.kind) {
|
|
2766
|
+
case "constraint":
|
|
2767
|
+
source = `SELECT 'pg_constraint'::regclass AS classid, con.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_constraint con WHERE con.conrelid = (${relation}) AND con.conname = ${collector.add(object.name)}`;
|
|
2768
|
+
break;
|
|
2769
|
+
case "trigger":
|
|
2770
|
+
source = `SELECT 'pg_trigger'::regclass AS classid, t.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_trigger t WHERE t.tgrelid = (${relation}) AND t.tgname = ${collector.add(object.name)}`;
|
|
2771
|
+
break;
|
|
2772
|
+
case "columnDefault":
|
|
2773
|
+
case "generatedColumn":
|
|
2774
|
+
source = `SELECT 'pg_attrdef'::regclass AS classid, d.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_attrdef d JOIN pg_catalog.pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attrelid = (${relation}) AND a.attname = ${collector.add(object.column)}`;
|
|
2775
|
+
break;
|
|
2776
|
+
case "column":
|
|
2777
|
+
source = `SELECT 'pg_class'::regclass AS classid, a.attrelid AS objid, a.attnum AS objsubid FROM pg_catalog.pg_attribute a WHERE a.attrelid = (${relation}) AND a.attname = ${collector.add(object.column)}`;
|
|
2778
|
+
break;
|
|
2779
|
+
case "view":
|
|
2780
|
+
source = `SELECT 'pg_rewrite'::regclass AS classid, r.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_rewrite r WHERE r.ev_class = (${relation})`;
|
|
2781
|
+
break;
|
|
2782
|
+
case "index":
|
|
2783
|
+
source = `SELECT 'pg_class'::regclass AS classid, i.indexrelid AS objid, 0 AS objsubid FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid WHERE i.indrelid = (${relation}) AND c.relname = ${collector.add(object.name)}`;
|
|
2784
|
+
break;
|
|
2785
|
+
case "function":
|
|
2786
|
+
source = `SELECT 'pg_proc'::regclass AS classid, p.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.proname = ${name} AND ARRAY(SELECT tn.nspname || '.' || t.typname FROM unnest(p.proargtypes) WITH ORDINALITY a(oid, ord) JOIN pg_catalog.pg_type t ON t.oid = a.oid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace ORDER BY a.ord) = ${collector.add(object.argumentTypes.map((type) => `${type.namespace}.${type.name}`))}::text[]`;
|
|
2787
|
+
break;
|
|
2788
|
+
case "postgresExtension":
|
|
2789
|
+
source = `SELECT 'pg_extension'::regclass AS classid, e.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE n.nspname = ${namespace} AND e.extname = ${name}`;
|
|
2790
|
+
break;
|
|
2791
|
+
default:
|
|
2792
|
+
source = `SELECT 'pg_class'::regclass AS classid, c.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_class c WHERE c.oid = (${relation})`;
|
|
2793
|
+
break;
|
|
2794
|
+
}
|
|
2795
|
+
return { text: `SELECT DISTINCT json_build_array(d.deptype, a.type, a.object_names, a.object_args)::text AS address FROM (${source}) s JOIN pg_catalog.pg_depend d ON d.classid = s.classid AND d.objid = s.objid AND d.objsubid = s.objsubid CROSS JOIN LATERAL pg_catalog.pg_identify_object_as_address(d.refclassid, d.refobjid, d.refobjsubid) a ORDER BY address`, values: collector.values, returning: "none" };
|
|
2796
|
+
}
|
|
2797
|
+
function renderMigrationServerVersion(params) {
|
|
2798
|
+
return { text: params.dialect.name === "sqlite" ? "SELECT sqlite_version() AS version" : "SELECT version() AS version", values: [], returning: "none" };
|
|
2799
|
+
}
|
|
2800
|
+
function renderMigrationNamespace(params) {
|
|
2801
|
+
return { text: params.dialect.name === "mysql" ? "SELECT DATABASE() AS namespace" : params.dialect.name === "sqlite" ? "SELECT 'main' AS namespace" : "SELECT 'public' AS namespace", values: [], returning: "none" };
|
|
2802
|
+
}
|
|
2803
|
+
function renderCheckProbe(params) {
|
|
2804
|
+
if (params.dialect.name !== "postgres")
|
|
2805
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
2806
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2807
|
+
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" };
|
|
2808
|
+
}
|
|
1610
2809
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
1611
|
-
import { VibeError as
|
|
2810
|
+
import { VibeError as VibeError9, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
|
|
1612
2811
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
1613
2812
|
function requirePostgres2(params) {
|
|
1614
2813
|
if (params.dialect.name !== "postgres") {
|
|
1615
|
-
throw
|
|
2814
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: params.feature });
|
|
1616
2815
|
}
|
|
1617
2816
|
}
|
|
1618
2817
|
function renderMigrationSessionRead(params) {
|
|
@@ -1627,7 +2826,7 @@ function renderMigrationSessionSet(params) {
|
|
|
1627
2826
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
1628
2827
|
const { milliseconds, setting } = params;
|
|
1629
2828
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
1630
|
-
throw new
|
|
2829
|
+
throw new VibeError9({
|
|
1631
2830
|
code: "VIBE_MIGRATION",
|
|
1632
2831
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
1633
2832
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -1714,63 +2913,21 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
1714
2913
|
returning: "none"
|
|
1715
2914
|
};
|
|
1716
2915
|
}
|
|
1717
|
-
// packages/sql/src/pg-array.ts
|
|
1718
|
-
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
1719
|
-
function toPgArrayLiteral(params) {
|
|
1720
|
-
const { values } = params;
|
|
1721
|
-
const parts = values.map((value) => {
|
|
1722
|
-
if (value === null || value === undefined)
|
|
1723
|
-
return "NULL";
|
|
1724
|
-
const text = String(value);
|
|
1725
|
-
return PG_ARRAY_QUOTE.test(text) ? `"${text.replace(/["\\]/g, (char) => `\\${char}`)}"` : text;
|
|
1726
|
-
});
|
|
1727
|
-
return `{${parts.join(",")}}`;
|
|
1728
|
-
}
|
|
1729
|
-
function parsePgArrayLiteral(params) {
|
|
1730
|
-
const { literal } = params;
|
|
1731
|
-
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
1732
|
-
return null;
|
|
1733
|
-
const inner = literal.slice(1, -1);
|
|
1734
|
-
if (inner.length === 0)
|
|
1735
|
-
return [];
|
|
1736
|
-
const out = [];
|
|
1737
|
-
let index = 0;
|
|
1738
|
-
while (index < inner.length) {
|
|
1739
|
-
if (inner[index] === '"') {
|
|
1740
|
-
let text = "";
|
|
1741
|
-
index++;
|
|
1742
|
-
while (index < inner.length) {
|
|
1743
|
-
const char = inner[index];
|
|
1744
|
-
if (char === "\\") {
|
|
1745
|
-
text += inner[index + 1] ?? "";
|
|
1746
|
-
index += 2;
|
|
1747
|
-
continue;
|
|
1748
|
-
}
|
|
1749
|
-
index++;
|
|
1750
|
-
if (char === '"')
|
|
1751
|
-
break;
|
|
1752
|
-
text += char;
|
|
1753
|
-
}
|
|
1754
|
-
out.push(text);
|
|
1755
|
-
} else {
|
|
1756
|
-
let text = "";
|
|
1757
|
-
while (index < inner.length && inner[index] !== ",") {
|
|
1758
|
-
text += inner[index];
|
|
1759
|
-
index++;
|
|
1760
|
-
}
|
|
1761
|
-
out.push(text === "NULL" ? null : text);
|
|
1762
|
-
}
|
|
1763
|
-
if (inner[index] === ",")
|
|
1764
|
-
index++;
|
|
1765
|
-
}
|
|
1766
|
-
return out;
|
|
1767
|
-
}
|
|
1768
2916
|
export {
|
|
1769
2917
|
toPgArrayLiteral,
|
|
1770
2918
|
strictMatchPattern,
|
|
1771
2919
|
startsWithPattern,
|
|
2920
|
+
sqliteTableConstraints,
|
|
1772
2921
|
sqliteDialect,
|
|
2922
|
+
sqliteColumnClauses,
|
|
2923
|
+
sqlUsesIncoming,
|
|
2924
|
+
sql,
|
|
1773
2925
|
renderStatement,
|
|
2926
|
+
renderSqliteRebuildObjects,
|
|
2927
|
+
renderSqliteForeignKeyInventory,
|
|
2928
|
+
renderSqliteColumnInventory,
|
|
2929
|
+
renderSqlFragment,
|
|
2930
|
+
renderRowLockClause,
|
|
1774
2931
|
renderRlsRole,
|
|
1775
2932
|
renderRlsReadinessQuery,
|
|
1776
2933
|
renderRlsProbeTable,
|
|
@@ -1785,23 +2942,42 @@ export {
|
|
|
1785
2942
|
renderRlsContext,
|
|
1786
2943
|
renderRlsColumnType,
|
|
1787
2944
|
renderRelationStateQuery,
|
|
2945
|
+
renderPredicate,
|
|
1788
2946
|
renderPostgresWorkload,
|
|
1789
2947
|
renderPostgresExplain,
|
|
2948
|
+
renderMysqlObjectMembers,
|
|
2949
|
+
renderMysqlCheckProbe,
|
|
2950
|
+
renderMigrationVisibility,
|
|
1790
2951
|
renderMigrationSessionSet,
|
|
1791
2952
|
renderMigrationSessionRestore,
|
|
1792
2953
|
renderMigrationSessionRead,
|
|
2954
|
+
renderMigrationServerVersion,
|
|
2955
|
+
renderMigrationObjectQuery,
|
|
2956
|
+
renderMigrationNamespace,
|
|
2957
|
+
renderMigrationInventory,
|
|
2958
|
+
renderMigrationHistoryQuery,
|
|
2959
|
+
renderMigrationDependencies,
|
|
1793
2960
|
renderIndexStateQuery,
|
|
1794
2961
|
renderFragment,
|
|
1795
2962
|
renderExpr,
|
|
2963
|
+
renderExclusionSetup,
|
|
2964
|
+
renderExclusionProbe,
|
|
2965
|
+
renderExclusionDdl,
|
|
2966
|
+
renderExclusionCatalog,
|
|
1796
2967
|
renderDiagnostic,
|
|
1797
2968
|
renderConstraintStateQuery,
|
|
2969
|
+
renderConstraintOwnedIndexDrop,
|
|
1798
2970
|
renderColumnStateQuery,
|
|
2971
|
+
renderCheckProbe,
|
|
2972
|
+
renderAdvisoryLock,
|
|
1799
2973
|
projectionOutputName,
|
|
1800
2974
|
postgresDialect,
|
|
1801
2975
|
parsePgArrayLiteral,
|
|
1802
2976
|
parseDateTimeText,
|
|
1803
2977
|
mysqlDialect,
|
|
2978
|
+
mapConflictColumns,
|
|
1804
2979
|
jsonParamCell,
|
|
2980
|
+
isSqlFragment,
|
|
1805
2981
|
isSqlDefaultCell,
|
|
1806
2982
|
isSetOperation,
|
|
1807
2983
|
isListSetOperation,
|
|
@@ -1815,9 +2991,13 @@ export {
|
|
|
1815
2991
|
escapeGlobPattern,
|
|
1816
2992
|
enumMembershipCodec,
|
|
1817
2993
|
endsWithPattern,
|
|
2994
|
+
describeRowLock,
|
|
1818
2995
|
decodeIsNoop,
|
|
2996
|
+
decode,
|
|
1819
2997
|
dbNowMarker,
|
|
1820
2998
|
containsPattern,
|
|
2999
|
+
compileSql,
|
|
3000
|
+
compileExclusion,
|
|
1821
3001
|
SQL_DIALECTS,
|
|
1822
3002
|
SQL_DEFAULT_CELL,
|
|
1823
3003
|
SQLITE_CODECS,
|
|
@@ -1834,7 +3014,9 @@ export {
|
|
|
1834
3014
|
MYSQL_CAPABILITIES,
|
|
1835
3015
|
JSON_TRANSPORT,
|
|
1836
3016
|
DIALECT_CODECS,
|
|
1837
|
-
DIALECT_CAPABILITIES
|
|
3017
|
+
DIALECT_CAPABILITIES,
|
|
3018
|
+
ADVISORY_LOCK_RESULT_COLUMN,
|
|
3019
|
+
ADVISORY_KEY_BOUNDS
|
|
1838
3020
|
};
|
|
1839
3021
|
|
|
1840
|
-
//# debugId=
|
|
3022
|
+
//# debugId=FBA51FB46584D1C064756E2164756E21
|