@vibeorm/sql 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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.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 +1544 -379
- package/dist/index.js.map +25 -12
- 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
|
},
|
|
@@ -139,248 +1120,92 @@ var sqliteDialect = {
|
|
|
139
1120
|
],
|
|
140
1121
|
jsonParamCast: "",
|
|
141
1122
|
lastInsertIdSql: null,
|
|
142
|
-
|
|
1123
|
+
rowLockSyntax: null,
|
|
1124
|
+
advisoryLockFunctions: null,
|
|
143
1125
|
rowDefaultKeyword: false,
|
|
144
1126
|
paramBudget: 900,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
name: "mysql",
|
|
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;
|
|
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')`;
|
|
297
1141
|
}
|
|
298
1142
|
};
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return value;
|
|
312
|
-
}
|
|
313
|
-
if (typeof value === "string")
|
|
314
|
-
return parseDateTimeText({ text: value }) ?? value;
|
|
315
|
-
return value;
|
|
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"
|
|
316
1155
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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)`;
|
|
342
1196
|
}
|
|
343
1197
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
Float: numberCodec,
|
|
350
|
-
Decimal: decimalCodec,
|
|
351
|
-
DateTime: dateNativeCodec,
|
|
352
|
-
Json: jsonCodec,
|
|
353
|
-
Bytes: passthroughCodec
|
|
354
|
-
};
|
|
355
|
-
var SQLITE_CODECS = {
|
|
356
|
-
String: passthroughCodec,
|
|
357
|
-
Boolean: booleanIntCodec,
|
|
358
|
-
Int: numberCodec,
|
|
359
|
-
BigInt: bigIntCodec,
|
|
360
|
-
Float: numberCodec,
|
|
361
|
-
Decimal: decimalCodec,
|
|
362
|
-
DateTime: dateTextCodec,
|
|
363
|
-
Json: jsonCodec,
|
|
364
|
-
Bytes: passthroughCodec
|
|
365
|
-
};
|
|
366
|
-
var MYSQL_CODECS = {
|
|
367
|
-
String: passthroughCodec,
|
|
368
|
-
Boolean: booleanNativeInCodec,
|
|
369
|
-
Int: numberCodec,
|
|
370
|
-
BigInt: bigIntCodec,
|
|
371
|
-
Float: numberCodec,
|
|
372
|
-
Decimal: mysqlDecimalCodec,
|
|
373
|
-
DateTime: mysqlDateCodec,
|
|
374
|
-
Json: jsonCodec,
|
|
375
|
-
Bytes: passthroughCodec
|
|
376
|
-
};
|
|
377
|
-
var DIALECT_CODECS = {
|
|
378
|
-
postgres: POSTGRES_CODECS,
|
|
379
|
-
sqlite: SQLITE_CODECS,
|
|
380
|
-
mysql: MYSQL_CODECS
|
|
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 });
|
|
@@ -775,7 +1581,7 @@ ORDER BY c.relname, t.tgname`,
|
|
|
775
1581
|
});
|
|
776
1582
|
function requireRls2(params) {
|
|
777
1583
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
778
|
-
throw
|
|
1584
|
+
throw unsupportedCapability7({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
779
1585
|
}
|
|
780
1586
|
}
|
|
781
1587
|
function renderRlsReadinessQuery(params) {
|
|
@@ -807,7 +1613,7 @@ function renderRlsPolicyPath(params) {
|
|
|
807
1613
|
}
|
|
808
1614
|
|
|
809
1615
|
// packages/sql/src/render.ts
|
|
810
|
-
import { internalError, unsupportedCapability as
|
|
1616
|
+
import { internalError as internalError2, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
811
1617
|
|
|
812
1618
|
// packages/sql/src/like.ts
|
|
813
1619
|
function escapeLikePattern(params) {
|
|
@@ -853,6 +1659,8 @@ function renderStatement(params) {
|
|
|
853
1659
|
}
|
|
854
1660
|
case "select":
|
|
855
1661
|
return renderSelect({ dialect, statement, collector });
|
|
1662
|
+
case "advisoryLock":
|
|
1663
|
+
return renderAdvisoryLock({ dialect, statement, collector });
|
|
856
1664
|
case "insert":
|
|
857
1665
|
return renderInsert({ dialect, statement, collector });
|
|
858
1666
|
case "update":
|
|
@@ -888,7 +1696,7 @@ function quoteCol(dialect, column) {
|
|
|
888
1696
|
const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
|
|
889
1697
|
if (column.castReal === true) {
|
|
890
1698
|
if (dialect.name !== "sqlite") {
|
|
891
|
-
throw
|
|
1699
|
+
throw internalError2({ message: "ColRef.castReal is a sqlite Decimal construct" });
|
|
892
1700
|
}
|
|
893
1701
|
return `CAST(${ref} AS REAL)`;
|
|
894
1702
|
}
|
|
@@ -902,7 +1710,7 @@ function quoteProjection(dialect, column) {
|
|
|
902
1710
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
903
1711
|
}
|
|
904
1712
|
if (dialect.name !== "postgres") {
|
|
905
|
-
throw
|
|
1713
|
+
throw internalError2({ message: "ColRef.transform is a postgres lateral-join construct" });
|
|
906
1714
|
}
|
|
907
1715
|
const ref = quoteCol(dialect, column);
|
|
908
1716
|
const alias = dialect.quoteIdent({ name: column.name });
|
|
@@ -918,10 +1726,10 @@ function renderTable(dialect, table) {
|
|
|
918
1726
|
}
|
|
919
1727
|
function assertCount(value, what) {
|
|
920
1728
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
921
|
-
throw
|
|
1729
|
+
throw internalError2({ message: `${what} must be a non-negative integer, got ${value}` });
|
|
922
1730
|
}
|
|
923
1731
|
}
|
|
924
|
-
var
|
|
1732
|
+
var COMPARISON_SQL2 = {
|
|
925
1733
|
eq: "=",
|
|
926
1734
|
ne: "<>",
|
|
927
1735
|
gt: ">",
|
|
@@ -950,7 +1758,7 @@ function renderAggRef(dialect, agg) {
|
|
|
950
1758
|
function assertScalarArrays(dialect, feature) {
|
|
951
1759
|
if (dialect.capabilities.scalarArrays)
|
|
952
1760
|
return;
|
|
953
|
-
throw
|
|
1761
|
+
throw unsupportedCapability8({
|
|
954
1762
|
dialect: dialect.name,
|
|
955
1763
|
feature,
|
|
956
1764
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -960,9 +1768,9 @@ function renderExpr(params) {
|
|
|
960
1768
|
const { dialect, expr, collector } = params;
|
|
961
1769
|
switch (expr.kind) {
|
|
962
1770
|
case "cmp": {
|
|
963
|
-
const op =
|
|
1771
|
+
const op = COMPARISON_SQL2[expr.op];
|
|
964
1772
|
if (op === undefined) {
|
|
965
|
-
throw
|
|
1773
|
+
throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
|
|
966
1774
|
}
|
|
967
1775
|
if (isDbNowMarker(expr.value)) {
|
|
968
1776
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
@@ -970,9 +1778,9 @@ function renderExpr(params) {
|
|
|
970
1778
|
return `${quoteCol(dialect, expr.column)} ${op} ${collector.add(expr.value)}`;
|
|
971
1779
|
}
|
|
972
1780
|
case "cmpCol": {
|
|
973
|
-
const op =
|
|
1781
|
+
const op = COMPARISON_SQL2[expr.op];
|
|
974
1782
|
if (op === undefined) {
|
|
975
|
-
throw
|
|
1783
|
+
throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
|
|
976
1784
|
}
|
|
977
1785
|
return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
|
|
978
1786
|
}
|
|
@@ -992,11 +1800,11 @@ function renderExpr(params) {
|
|
|
992
1800
|
}
|
|
993
1801
|
case "tupleIn": {
|
|
994
1802
|
if (expr.columns.length === 0) {
|
|
995
|
-
throw
|
|
1803
|
+
throw internalError2({ message: "tupleIn needs at least one column" });
|
|
996
1804
|
}
|
|
997
1805
|
for (const row of expr.rows) {
|
|
998
1806
|
if (row.length !== expr.columns.length) {
|
|
999
|
-
throw
|
|
1807
|
+
throw internalError2({
|
|
1000
1808
|
message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
|
|
1001
1809
|
});
|
|
1002
1810
|
}
|
|
@@ -1068,9 +1876,9 @@ function renderExpr(params) {
|
|
|
1068
1876
|
case "not":
|
|
1069
1877
|
return `NOT (${renderExpr({ dialect, expr: expr.operand, collector })})`;
|
|
1070
1878
|
case "aggCmp": {
|
|
1071
|
-
const op =
|
|
1879
|
+
const op = COMPARISON_SQL2[expr.op];
|
|
1072
1880
|
if (op === undefined) {
|
|
1073
|
-
throw
|
|
1881
|
+
throw internalError2({ message: `unknown comparison operator "${expr.op}"` });
|
|
1074
1882
|
}
|
|
1075
1883
|
return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
|
|
1076
1884
|
}
|
|
@@ -1098,6 +1906,8 @@ function renderExpr(params) {
|
|
|
1098
1906
|
const sub = renderSelect({ dialect, statement: expr.select, collector });
|
|
1099
1907
|
return `${expr.negate === true ? "NOT " : ""}EXISTS (${sub.text})`;
|
|
1100
1908
|
}
|
|
1909
|
+
case "composed":
|
|
1910
|
+
return `(${renderSqlFragment({ dialect, fragment: expr.fragment, collector })})`;
|
|
1101
1911
|
case "fragment":
|
|
1102
1912
|
return renderFragment({ dialect, fragment: expr.fragment, collector });
|
|
1103
1913
|
case "raw":
|
|
@@ -1134,7 +1944,7 @@ function jsonPathString(path) {
|
|
|
1134
1944
|
function jsonStringPattern(params) {
|
|
1135
1945
|
const { expr } = params;
|
|
1136
1946
|
if (typeof expr.value !== "string") {
|
|
1137
|
-
throw
|
|
1947
|
+
throw internalError2({ message: `json ${expr.op} expects a string operand` });
|
|
1138
1948
|
}
|
|
1139
1949
|
if (expr.op === "string_contains")
|
|
1140
1950
|
return containsPattern({ raw: expr.value });
|
|
@@ -1158,7 +1968,7 @@ function renderJsonLike(params) {
|
|
|
1158
1968
|
function renderJsonFilter(params) {
|
|
1159
1969
|
const { dialect, expr, collector } = params;
|
|
1160
1970
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
1161
|
-
throw
|
|
1971
|
+
throw unsupportedCapability8({
|
|
1162
1972
|
dialect: dialect.name,
|
|
1163
1973
|
feature: `the json filter "${expr.op}"`,
|
|
1164
1974
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -1213,7 +2023,7 @@ function renderJsonFilter(params) {
|
|
|
1213
2023
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
1214
2024
|
const tests = elements.map((element) => {
|
|
1215
2025
|
if (element === null || typeof element === "object" && element !== null) {
|
|
1216
|
-
throw
|
|
2026
|
+
throw unsupportedCapability8({
|
|
1217
2027
|
dialect: dialect.name,
|
|
1218
2028
|
feature: "json array_contains with object/array/null elements",
|
|
1219
2029
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -1225,7 +2035,7 @@ function renderJsonFilter(params) {
|
|
|
1225
2035
|
return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
|
|
1226
2036
|
}
|
|
1227
2037
|
default:
|
|
1228
|
-
throw
|
|
2038
|
+
throw internalError2({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
|
|
1229
2039
|
}
|
|
1230
2040
|
}
|
|
1231
2041
|
const column = quoteCol(dialect, expr.column);
|
|
@@ -1280,11 +2090,11 @@ function renderSelectExpr(params) {
|
|
|
1280
2090
|
function renderPositionalRow(params) {
|
|
1281
2091
|
const { dialect, join, subAlias } = params;
|
|
1282
2092
|
if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || join.select.rowNumber !== undefined) {
|
|
1283
|
-
throw
|
|
2093
|
+
throw internalError2({ message: "positional lateral rows require an explicit column-only projection" });
|
|
1284
2094
|
}
|
|
1285
2095
|
const names = join.select.columns.map((column) => projectionOutputName({ column }));
|
|
1286
2096
|
if (new Set(names).size !== names.length) {
|
|
1287
|
-
throw
|
|
2097
|
+
throw internalError2({ message: "positional lateral projection has duplicate output names" });
|
|
1288
2098
|
}
|
|
1289
2099
|
const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
|
|
1290
2100
|
const chunks = [];
|
|
@@ -1292,7 +2102,7 @@ function renderPositionalRow(params) {
|
|
|
1292
2102
|
chunks.push(`json_build_array(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
|
|
1293
2103
|
}
|
|
1294
2104
|
if (chunks.length > JSON_TRANSPORT.chunkSize) {
|
|
1295
|
-
throw
|
|
2105
|
+
throw internalError2({ message: "positional lateral projection exceeds the transport width limit" });
|
|
1296
2106
|
}
|
|
1297
2107
|
return chunks.length === 1 ? chunks[0] : `json_build_array(${chunks.join(", ")})`;
|
|
1298
2108
|
}
|
|
@@ -1301,7 +2111,7 @@ function renderLateralJoin(params) {
|
|
|
1301
2111
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
1302
2112
|
if (join.agg === "count") {
|
|
1303
2113
|
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
|
|
2114
|
+
throw internalError2({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
|
|
1305
2115
|
}
|
|
1306
2116
|
return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
|
|
1307
2117
|
}
|
|
@@ -1336,10 +2146,10 @@ function applyReturning(params) {
|
|
|
1336
2146
|
function renderSelect(params) {
|
|
1337
2147
|
const { dialect, statement, collector } = params;
|
|
1338
2148
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
1339
|
-
throw
|
|
2149
|
+
throw internalError2({ message: "only countAll can combine with a column list" });
|
|
1340
2150
|
}
|
|
1341
2151
|
if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
|
|
1342
|
-
throw
|
|
2152
|
+
throw internalError2({ message: "selectExpr and aggregates cannot combine" });
|
|
1343
2153
|
}
|
|
1344
2154
|
const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
|
|
1345
2155
|
if (statement.selectExpr !== undefined) {
|
|
@@ -1353,7 +2163,7 @@ function renderSelect(params) {
|
|
|
1353
2163
|
if (statement.rowNumber !== undefined) {
|
|
1354
2164
|
const rowNumber = statement.rowNumber;
|
|
1355
2165
|
if (rowNumber.partitionBy.length === 0) {
|
|
1356
|
-
throw
|
|
2166
|
+
throw internalError2({ message: "rowNumber needs at least one partition column" });
|
|
1357
2167
|
}
|
|
1358
2168
|
const partition = rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ");
|
|
1359
2169
|
const order = rowNumber.orderBy.length === 0 ? "" : ` ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
@@ -1362,7 +2172,7 @@ function renderSelect(params) {
|
|
|
1362
2172
|
let distinctPrefix = "";
|
|
1363
2173
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
1364
2174
|
if (!dialect.capabilities.distinctOn) {
|
|
1365
|
-
throw
|
|
2175
|
+
throw unsupportedCapability8({
|
|
1366
2176
|
dialect: dialect.name,
|
|
1367
2177
|
feature: "SELECT DISTINCT ON",
|
|
1368
2178
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -1374,7 +2184,7 @@ function renderSelect(params) {
|
|
|
1374
2184
|
let from;
|
|
1375
2185
|
if (statement.fromSelect !== undefined) {
|
|
1376
2186
|
if (statement.table.alias !== undefined) {
|
|
1377
|
-
throw
|
|
2187
|
+
throw internalError2({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
|
|
1378
2188
|
}
|
|
1379
2189
|
const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
|
|
1380
2190
|
from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
@@ -1385,7 +2195,7 @@ function renderSelect(params) {
|
|
|
1385
2195
|
if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
|
|
1386
2196
|
for (const join of statement.innerJoins) {
|
|
1387
2197
|
if (join.on.length === 0) {
|
|
1388
|
-
throw
|
|
2198
|
+
throw internalError2({ message: "inner join needs at least one ON pair" });
|
|
1389
2199
|
}
|
|
1390
2200
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
1391
2201
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
@@ -1393,7 +2203,7 @@ function renderSelect(params) {
|
|
|
1393
2203
|
}
|
|
1394
2204
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
1395
2205
|
if (!dialect.capabilities.lateralJoin) {
|
|
1396
|
-
throw
|
|
2206
|
+
throw unsupportedCapability8({
|
|
1397
2207
|
dialect: dialect.name,
|
|
1398
2208
|
feature: "LATERAL JOIN relation loading",
|
|
1399
2209
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -1427,22 +2237,24 @@ function renderSelect(params) {
|
|
|
1427
2237
|
}
|
|
1428
2238
|
text += ` OFFSET ${statement.offset}`;
|
|
1429
2239
|
}
|
|
1430
|
-
if (statement.
|
|
1431
|
-
|
|
2240
|
+
if (statement.rowLock !== undefined) {
|
|
2241
|
+
const clause = renderRowLockClause({ dialect, lock: statement.rowLock });
|
|
2242
|
+
if (clause !== null)
|
|
2243
|
+
text += ` ${clause}`;
|
|
1432
2244
|
}
|
|
1433
2245
|
return { text, values: collector.values, returning: "none" };
|
|
1434
2246
|
}
|
|
1435
2247
|
function renderInsert(params) {
|
|
1436
2248
|
const { dialect, statement, collector } = params;
|
|
1437
2249
|
if (statement.columns.length === 0) {
|
|
1438
|
-
throw
|
|
2250
|
+
throw internalError2({ message: "insert requires at least one column" });
|
|
1439
2251
|
}
|
|
1440
2252
|
if (statement.rows.length === 0) {
|
|
1441
|
-
throw
|
|
2253
|
+
throw internalError2({ message: "insert requires at least one row" });
|
|
1442
2254
|
}
|
|
1443
2255
|
for (const row of statement.rows) {
|
|
1444
2256
|
if (row.length !== statement.columns.length) {
|
|
1445
|
-
throw
|
|
2257
|
+
throw internalError2({
|
|
1446
2258
|
message: `insert row has ${row.length} values but ${statement.columns.length} columns`
|
|
1447
2259
|
});
|
|
1448
2260
|
}
|
|
@@ -1460,14 +2272,14 @@ function renderInsert(params) {
|
|
|
1460
2272
|
if (!isSqlDefaultCell(value))
|
|
1461
2273
|
return collector.add(value);
|
|
1462
2274
|
if (!dialect.rowDefaultKeyword) {
|
|
1463
|
-
throw
|
|
2275
|
+
throw internalError2({
|
|
1464
2276
|
message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
|
|
1465
2277
|
});
|
|
1466
2278
|
}
|
|
1467
2279
|
return "DEFAULT";
|
|
1468
2280
|
}).join(", ")})`).join(", ");
|
|
1469
2281
|
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))) {
|
|
2282
|
+
if (onConflict !== undefined && !("doNothing" in onConflict) && dialect.excludedRowRef.form === "rowAlias" && Object.values(onConflict.update).some((value) => isExcludedRef(value) || isSqlFragment(value) && sqlUsesIncoming({ fragment: value }))) {
|
|
1471
2283
|
text += ` AS ${dialect.quoteIdent({ name: dialect.excludedRowRef.alias })}`;
|
|
1472
2284
|
}
|
|
1473
2285
|
if (onConflict !== undefined) {
|
|
@@ -1476,7 +2288,7 @@ function renderInsert(params) {
|
|
|
1476
2288
|
let target = "";
|
|
1477
2289
|
if (onConflict.constraint !== undefined) {
|
|
1478
2290
|
if (dialect.name !== "postgres") {
|
|
1479
|
-
throw
|
|
2291
|
+
throw unsupportedCapability8({
|
|
1480
2292
|
dialect: dialect.name,
|
|
1481
2293
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
1482
2294
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -1491,7 +2303,7 @@ function renderInsert(params) {
|
|
|
1491
2303
|
}
|
|
1492
2304
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
1493
2305
|
} else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
1494
|
-
throw
|
|
2306
|
+
throw unsupportedCapability8({
|
|
1495
2307
|
dialect: dialect.name,
|
|
1496
2308
|
feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
|
|
1497
2309
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -1500,23 +2312,23 @@ function renderInsert(params) {
|
|
|
1500
2312
|
} else {
|
|
1501
2313
|
const updates = Object.entries(onConflict.update);
|
|
1502
2314
|
if (updates.length === 0) {
|
|
1503
|
-
throw
|
|
2315
|
+
throw internalError2({ message: "upsert update must set at least one column" });
|
|
1504
2316
|
}
|
|
1505
2317
|
const selfRef = dialect.quoteIdent({ name: statement.table.name });
|
|
1506
|
-
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true });
|
|
2318
|
+
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
|
|
1507
2319
|
if (dialect.upsertForm === "onConflict") {
|
|
1508
2320
|
if (onConflict.lastInsertIdColumn !== undefined) {
|
|
1509
|
-
throw
|
|
2321
|
+
throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
1510
2322
|
}
|
|
1511
2323
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
1512
|
-
throw
|
|
2324
|
+
throw unsupportedCapability8({
|
|
1513
2325
|
dialect: dialect.name,
|
|
1514
2326
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
1515
2327
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
1516
2328
|
});
|
|
1517
2329
|
}
|
|
1518
2330
|
if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
|
|
1519
|
-
throw
|
|
2331
|
+
throw internalError2({
|
|
1520
2332
|
message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
|
|
1521
2333
|
});
|
|
1522
2334
|
}
|
|
@@ -1525,14 +2337,14 @@ function renderInsert(params) {
|
|
|
1525
2337
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
1526
2338
|
} else {
|
|
1527
2339
|
if (onConflict.constraint !== undefined) {
|
|
1528
|
-
throw
|
|
2340
|
+
throw unsupportedCapability8({
|
|
1529
2341
|
dialect: dialect.name,
|
|
1530
2342
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
1531
2343
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
1532
2344
|
});
|
|
1533
2345
|
}
|
|
1534
2346
|
if (onConflict.conflictWhere !== undefined) {
|
|
1535
|
-
throw
|
|
2347
|
+
throw unsupportedCapability8({
|
|
1536
2348
|
dialect: dialect.name,
|
|
1537
2349
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
1538
2350
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -1550,7 +2362,7 @@ function renderUpdate(params) {
|
|
|
1550
2362
|
const { dialect, statement, collector } = params;
|
|
1551
2363
|
const assignments = Object.entries(statement.set);
|
|
1552
2364
|
if (assignments.length === 0) {
|
|
1553
|
-
throw
|
|
2365
|
+
throw internalError2({ message: "update must set at least one column" });
|
|
1554
2366
|
}
|
|
1555
2367
|
const setSql = renderAssignments({ dialect, entries: assignments, collector });
|
|
1556
2368
|
let text = `UPDATE ${renderTable(dialect, statement.table)} SET ${setSql}`;
|
|
@@ -1571,9 +2383,14 @@ function renderAssignments(params) {
|
|
|
1571
2383
|
return entries.map(([name, value]) => {
|
|
1572
2384
|
const column = dialect.quoteIdent({ name });
|
|
1573
2385
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
2386
|
+
if (isSqlFragment(value)) {
|
|
2387
|
+
if (params.allowExcluded !== true || selfRef === undefined)
|
|
2388
|
+
throw internalError2({ message: "SQL assignment fragments require a conflict update" });
|
|
2389
|
+
return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
|
|
2390
|
+
}
|
|
1574
2391
|
if (isExcludedRef(value)) {
|
|
1575
2392
|
if (params.allowExcluded !== true) {
|
|
1576
|
-
throw
|
|
2393
|
+
throw internalError2({
|
|
1577
2394
|
message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
|
|
1578
2395
|
});
|
|
1579
2396
|
}
|
|
@@ -1607,12 +2424,377 @@ function renderDelete(params) {
|
|
|
1607
2424
|
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
1608
2425
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
1609
2426
|
}
|
|
2427
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
2428
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
2429
|
+
function tableDefinitionParts(params) {
|
|
2430
|
+
const sql2 = params.sql;
|
|
2431
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql2))
|
|
2432
|
+
throw unavailable();
|
|
2433
|
+
const parts = [];
|
|
2434
|
+
let start = -1;
|
|
2435
|
+
let depth = 0;
|
|
2436
|
+
let quote;
|
|
2437
|
+
for (let index = 0;index < sql2.length; index++) {
|
|
2438
|
+
const char = sql2[index];
|
|
2439
|
+
if (quote !== undefined) {
|
|
2440
|
+
if (char === quote) {
|
|
2441
|
+
if (quote !== "]" && sql2[index + 1] === quote)
|
|
2442
|
+
index++;
|
|
2443
|
+
else
|
|
2444
|
+
quote = undefined;
|
|
2445
|
+
}
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
2449
|
+
quote = char;
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
if (char === "[") {
|
|
2453
|
+
quote = "]";
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
if (char === "-" && sql2[index + 1] === "-") {
|
|
2457
|
+
const end = sql2.indexOf(`
|
|
2458
|
+
`, index + 2);
|
|
2459
|
+
if (end < 0)
|
|
2460
|
+
break;
|
|
2461
|
+
index = end;
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
if (char === "/" && sql2[index + 1] === "*") {
|
|
2465
|
+
const end = sql2.indexOf("*/", index + 2);
|
|
2466
|
+
if (end < 0)
|
|
2467
|
+
break;
|
|
2468
|
+
index = end + 1;
|
|
2469
|
+
continue;
|
|
2470
|
+
}
|
|
2471
|
+
if (char === "(") {
|
|
2472
|
+
if (depth === 0)
|
|
2473
|
+
start = index + 1;
|
|
2474
|
+
depth++;
|
|
2475
|
+
} else if (char === ")") {
|
|
2476
|
+
depth--;
|
|
2477
|
+
if (depth === 0 && start >= 0) {
|
|
2478
|
+
parts.push(sql2.slice(start, index).trim());
|
|
2479
|
+
return { clauses: parts, suffix: sql2.slice(index + 1) };
|
|
2480
|
+
}
|
|
2481
|
+
} else if (char === "," && depth === 1) {
|
|
2482
|
+
parts.push(sql2.slice(start, index).trim());
|
|
2483
|
+
start = index + 1;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
throw unavailable();
|
|
2487
|
+
}
|
|
2488
|
+
function unavailable() {
|
|
2489
|
+
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
2490
|
+
}
|
|
2491
|
+
function sqliteColumnClauses(params) {
|
|
2492
|
+
const columns = [];
|
|
2493
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
2494
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
2495
|
+
if (token === null)
|
|
2496
|
+
throw unavailable();
|
|
2497
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
2498
|
+
continue;
|
|
2499
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
2500
|
+
columns.push({ name, sql: part });
|
|
2501
|
+
}
|
|
2502
|
+
return columns;
|
|
2503
|
+
}
|
|
2504
|
+
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
2505
|
+
import { unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
2506
|
+
function renderConstraintOwnedIndexDrop(params) {
|
|
2507
|
+
if (params.dialect.name !== "postgres")
|
|
2508
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
2509
|
+
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
2510
|
+
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
2511
|
+
}
|
|
2512
|
+
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
2513
|
+
import { unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
2514
|
+
function renderMysqlCheckProbe(params) {
|
|
2515
|
+
if (params.dialect.name !== "mysql")
|
|
2516
|
+
throw unsupportedCapability10({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
2517
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2518
|
+
const probe = q(params.probe);
|
|
2519
|
+
switch (params.operation) {
|
|
2520
|
+
case "clone":
|
|
2521
|
+
if (params.table === undefined)
|
|
2522
|
+
throw unsupportedCapability10({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
2523
|
+
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
2524
|
+
case "add": {
|
|
2525
|
+
if (params.expression === undefined || params.definition === undefined)
|
|
2526
|
+
throw unsupportedCapability10({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
2527
|
+
const parts = tableDefinitionParts({ sql: params.definition });
|
|
2528
|
+
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
2529
|
+
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
2530
|
+
`)}
|
|
2531
|
+
)${parts.suffix}`, values: [], returning: "none" };
|
|
2532
|
+
}
|
|
2533
|
+
case "read":
|
|
2534
|
+
return { text: `SHOW CREATE TABLE ${probe}`, values: [], returning: "none" };
|
|
2535
|
+
case "drop":
|
|
2536
|
+
return { text: `DROP TEMPORARY TABLE ${probe}`, values: [], returning: "none" };
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
// packages/sql/src/dialects/migration-history.ts
|
|
2540
|
+
function renderMigrationHistoryQuery(params) {
|
|
2541
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2542
|
+
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" };
|
|
2543
|
+
}
|
|
2544
|
+
// packages/sql/src/dialects/sqlite-constraints.ts
|
|
2545
|
+
var IDENTIFIER = '(?:"(?:[^" ]| |"")+"|`(?:[^`]|``)+`|\\[[^\\]]+\\]|[A-Za-z_][A-Za-z_0-9]*)';
|
|
2546
|
+
function identifier3(value) {
|
|
2547
|
+
const text = value.trim();
|
|
2548
|
+
if (text.startsWith('"'))
|
|
2549
|
+
return text.slice(1, -1).replaceAll('""', '"');
|
|
2550
|
+
if (text.startsWith("`"))
|
|
2551
|
+
return text.slice(1, -1).replaceAll("``", "`");
|
|
2552
|
+
if (text.startsWith("["))
|
|
2553
|
+
return text.slice(1, -1);
|
|
2554
|
+
return text;
|
|
2555
|
+
}
|
|
2556
|
+
function sqliteTableConstraints(params) {
|
|
2557
|
+
const result = [];
|
|
2558
|
+
for (const clause of tableDefinitionParts(params).clauses) {
|
|
2559
|
+
const named = new RegExp(`^CONSTRAINT\\s+(${IDENTIFIER})\\s+`, "i").exec(clause);
|
|
2560
|
+
const body = named === null ? clause : clause.slice(named[0].length);
|
|
2561
|
+
const kind = /^(CHECK|FOREIGN\s+KEY|UNIQUE|PRIMARY\s+KEY)\s*\(/i.exec(body);
|
|
2562
|
+
if (kind === null)
|
|
2563
|
+
continue;
|
|
2564
|
+
const type = /^CHECK$/i.test(kind[1]) ? "check" : /^FOREIGN/i.test(kind[1]) ? "foreignKey" : /^PRIMARY/i.test(kind[1]) ? "primaryKey" : "unique";
|
|
2565
|
+
const parts = tableDefinitionParts({ sql: `CREATE TABLE x ${body.slice(kind[0].indexOf("("))}` });
|
|
2566
|
+
const columns = type === "check" ? [] : parts.clauses.map(identifier3);
|
|
2567
|
+
result.push({ name: named === null ? `@sqlite:${type}:${result.length}` : identifier3(named[1]), named: named !== null, type, sql: body, columns });
|
|
2568
|
+
}
|
|
2569
|
+
return result;
|
|
2570
|
+
}
|
|
2571
|
+
// packages/sql/src/dialects/migration-visibility.ts
|
|
2572
|
+
import { unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
2573
|
+
function renderMigrationVisibility(params) {
|
|
2574
|
+
if (params.dialect.name !== "mysql")
|
|
2575
|
+
throw unsupportedCapability11({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
2576
|
+
const collector = new ParamCollector(params.dialect);
|
|
2577
|
+
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
2578
|
+
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" };
|
|
2579
|
+
}
|
|
2580
|
+
// packages/sql/src/dialects/migration-inventory.ts
|
|
2581
|
+
import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
2582
|
+
function renderSqliteColumnInventory(params) {
|
|
2583
|
+
if (params.dialect.name !== "sqlite")
|
|
2584
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
2585
|
+
const collector = new ParamCollector(params.dialect);
|
|
2586
|
+
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" };
|
|
2587
|
+
}
|
|
2588
|
+
function renderSqliteForeignKeyInventory(params) {
|
|
2589
|
+
if (params.dialect.name !== "sqlite")
|
|
2590
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
2591
|
+
const collector = new ParamCollector(params.dialect);
|
|
2592
|
+
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" };
|
|
2593
|
+
}
|
|
2594
|
+
function renderSqliteRebuildObjects(params) {
|
|
2595
|
+
if (params.dialect.name !== "sqlite")
|
|
2596
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
2597
|
+
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" };
|
|
2598
|
+
}
|
|
2599
|
+
function renderMigrationInventory(params) {
|
|
2600
|
+
const collector = new ParamCollector(params.dialect);
|
|
2601
|
+
const namespace = collector.add(params.namespace);
|
|
2602
|
+
if (params.dialect.name === "postgres") {
|
|
2603
|
+
return { text: `WITH relations AS (
|
|
2604
|
+
SELECT c.oid, c.relname, c.relkind FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
2605
|
+
WHERE n.nspname = ${namespace} AND c.relname <> '_vibe_migrations'
|
|
2606
|
+
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)
|
|
2607
|
+
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')
|
|
2608
|
+
)
|
|
2609
|
+
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')
|
|
2610
|
+
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')
|
|
2611
|
+
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')
|
|
2612
|
+
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
|
|
2613
|
+
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')
|
|
2614
|
+
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
|
|
2615
|
+
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')
|
|
2616
|
+
ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
2617
|
+
}
|
|
2618
|
+
if (params.dialect.name === "mysql") {
|
|
2619
|
+
const n = () => collector.add(params.namespace);
|
|
2620
|
+
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'
|
|
2621
|
+
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'
|
|
2622
|
+
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
|
|
2623
|
+
UNION ALL SELECT 'trigger', TRIGGER_NAME, EVENT_OBJECT_TABLE, NULL, NULL, NULL FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = ${n()}
|
|
2624
|
+
UNION ALL SELECT 'column', COLUMN_NAME, TABLE_NAME, NULL, NULL, NULL FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ${n()} AND TABLE_NAME <> '_vibe_migrations'
|
|
2625
|
+
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 <> '')
|
|
2626
|
+
UNION ALL SELECT 'function', ROUTINE_NAME, NULL, NULL, '[]', NULL FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${n()} AND ROUTINE_TYPE = 'FUNCTION'
|
|
2627
|
+
ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
2628
|
+
}
|
|
2629
|
+
const schema = params.dialect.quoteIdent({ name: params.namespace });
|
|
2630
|
+
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" };
|
|
2631
|
+
}
|
|
2632
|
+
// packages/sql/src/dialects/migration-objects.ts
|
|
2633
|
+
import { unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
|
|
2634
|
+
function renderMigrationObjectQuery(params) {
|
|
2635
|
+
const { dialect, identity: object } = params;
|
|
2636
|
+
const collector = new ParamCollector(dialect);
|
|
2637
|
+
const physical = "table" in object ? object.table : object.name;
|
|
2638
|
+
const namespace = collector.add(physical.namespace);
|
|
2639
|
+
const name = collector.add(physical.name);
|
|
2640
|
+
let text;
|
|
2641
|
+
if (dialect.name === "postgres") {
|
|
2642
|
+
const relation = `FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace`;
|
|
2643
|
+
const where = `n.nspname = ${namespace} AND c.relname = ${name}`;
|
|
2644
|
+
switch (object.kind) {
|
|
2645
|
+
case "constraint": {
|
|
2646
|
+
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"` : "";
|
|
2647
|
+
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)}`;
|
|
2648
|
+
break;
|
|
2649
|
+
}
|
|
2650
|
+
case "trigger":
|
|
2651
|
+
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`;
|
|
2652
|
+
break;
|
|
2653
|
+
case "function":
|
|
2654
|
+
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[]`;
|
|
2655
|
+
break;
|
|
2656
|
+
case "view":
|
|
2657
|
+
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')`;
|
|
2658
|
+
break;
|
|
2659
|
+
case "sequence":
|
|
2660
|
+
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}`;
|
|
2661
|
+
break;
|
|
2662
|
+
case "column":
|
|
2663
|
+
case "columnDefault":
|
|
2664
|
+
case "generatedColumn":
|
|
2665
|
+
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`;
|
|
2666
|
+
break;
|
|
2667
|
+
case "index":
|
|
2668
|
+
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)}`;
|
|
2669
|
+
break;
|
|
2670
|
+
case "table":
|
|
2671
|
+
text = `SELECT c.relkind::text AS "type", c.relrowsecurity AS "rls", c.relforcerowsecurity AS "forceRls" ${relation} WHERE ${where} AND c.relkind IN ('r', 'p')`;
|
|
2672
|
+
break;
|
|
2673
|
+
case "postgresExtension":
|
|
2674
|
+
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}`;
|
|
2675
|
+
break;
|
|
2676
|
+
}
|
|
2677
|
+
} else if (dialect.name === "mysql") {
|
|
2678
|
+
switch (object.kind) {
|
|
2679
|
+
case "view":
|
|
2680
|
+
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}`;
|
|
2681
|
+
break;
|
|
2682
|
+
case "trigger":
|
|
2683
|
+
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)}`;
|
|
2684
|
+
break;
|
|
2685
|
+
case "function":
|
|
2686
|
+
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'`;
|
|
2687
|
+
break;
|
|
2688
|
+
case "column":
|
|
2689
|
+
case "columnDefault":
|
|
2690
|
+
case "generatedColumn":
|
|
2691
|
+
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)}`;
|
|
2692
|
+
break;
|
|
2693
|
+
case "constraint":
|
|
2694
|
+
if (object.constraintType === "exclusion")
|
|
2695
|
+
throw unsupportedCapability13({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
2696
|
+
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)}`;
|
|
2697
|
+
break;
|
|
2698
|
+
case "index":
|
|
2699
|
+
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`;
|
|
2700
|
+
break;
|
|
2701
|
+
case "table":
|
|
2702
|
+
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}`;
|
|
2703
|
+
break;
|
|
2704
|
+
default:
|
|
2705
|
+
throw unsupportedCapability13({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
2706
|
+
}
|
|
2707
|
+
} else {
|
|
2708
|
+
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
2709
|
+
throw unsupportedCapability13({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
2710
|
+
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
2711
|
+
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
2712
|
+
if (["function", "sequence", "postgresExtension"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
2713
|
+
throw unsupportedCapability13({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
2714
|
+
const sqliteParams = new ParamCollector(dialect);
|
|
2715
|
+
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
2716
|
+
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)}`;
|
|
2717
|
+
return { text, values: sqliteParams.values, returning: "none" };
|
|
2718
|
+
}
|
|
2719
|
+
const objectName = object.kind === "trigger" || object.kind === "index" ? object.name : physical.name;
|
|
2720
|
+
text = `SELECT sql AS definition, type AS type FROM ${catalog} WHERE name = ${sqliteParams.add(objectName)} AND type = ${sqliteParams.add(kind)}`;
|
|
2721
|
+
return { text, values: sqliteParams.values, returning: "none" };
|
|
2722
|
+
}
|
|
2723
|
+
return { text, values: collector.values, returning: "none" };
|
|
2724
|
+
}
|
|
2725
|
+
function renderMysqlObjectMembers(params) {
|
|
2726
|
+
if (params.dialect.name !== "mysql")
|
|
2727
|
+
return;
|
|
2728
|
+
const object = params.identity;
|
|
2729
|
+
if (object.kind !== "function" && object.kind !== "index" && object.kind !== "constraint")
|
|
2730
|
+
return;
|
|
2731
|
+
const collector = new ParamCollector(params.dialect);
|
|
2732
|
+
const physical = "table" in object ? object.table : object.name;
|
|
2733
|
+
const namespace = collector.add(physical.namespace);
|
|
2734
|
+
const name = collector.add(physical.name);
|
|
2735
|
+
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`;
|
|
2736
|
+
return { text, values: collector.values, returning: "none" };
|
|
2737
|
+
}
|
|
2738
|
+
function renderMigrationDependencies(params) {
|
|
2739
|
+
if (params.dialect.name !== "postgres")
|
|
2740
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
2741
|
+
const object = params.identity;
|
|
2742
|
+
const physical = "table" in object ? object.table : object.name;
|
|
2743
|
+
const collector = new ParamCollector(params.dialect);
|
|
2744
|
+
const namespace = collector.add(physical.namespace);
|
|
2745
|
+
const name = collector.add(physical.name);
|
|
2746
|
+
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}`;
|
|
2747
|
+
let source;
|
|
2748
|
+
switch (object.kind) {
|
|
2749
|
+
case "constraint":
|
|
2750
|
+
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)}`;
|
|
2751
|
+
break;
|
|
2752
|
+
case "trigger":
|
|
2753
|
+
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)}`;
|
|
2754
|
+
break;
|
|
2755
|
+
case "columnDefault":
|
|
2756
|
+
case "generatedColumn":
|
|
2757
|
+
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)}`;
|
|
2758
|
+
break;
|
|
2759
|
+
case "column":
|
|
2760
|
+
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)}`;
|
|
2761
|
+
break;
|
|
2762
|
+
case "view":
|
|
2763
|
+
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})`;
|
|
2764
|
+
break;
|
|
2765
|
+
case "index":
|
|
2766
|
+
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)}`;
|
|
2767
|
+
break;
|
|
2768
|
+
case "function":
|
|
2769
|
+
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[]`;
|
|
2770
|
+
break;
|
|
2771
|
+
case "postgresExtension":
|
|
2772
|
+
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}`;
|
|
2773
|
+
break;
|
|
2774
|
+
default:
|
|
2775
|
+
source = `SELECT 'pg_class'::regclass AS classid, c.oid AS objid, 0 AS objsubid FROM pg_catalog.pg_class c WHERE c.oid = (${relation})`;
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2778
|
+
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" };
|
|
2779
|
+
}
|
|
2780
|
+
function renderMigrationServerVersion(params) {
|
|
2781
|
+
return { text: params.dialect.name === "sqlite" ? "SELECT sqlite_version() AS version" : "SELECT version() AS version", values: [], returning: "none" };
|
|
2782
|
+
}
|
|
2783
|
+
function renderMigrationNamespace(params) {
|
|
2784
|
+
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" };
|
|
2785
|
+
}
|
|
2786
|
+
function renderCheckProbe(params) {
|
|
2787
|
+
if (params.dialect.name !== "postgres")
|
|
2788
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
2789
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2790
|
+
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" };
|
|
2791
|
+
}
|
|
1610
2792
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
1611
|
-
import { VibeError as
|
|
2793
|
+
import { VibeError as VibeError9, unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
|
|
1612
2794
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
1613
2795
|
function requirePostgres2(params) {
|
|
1614
2796
|
if (params.dialect.name !== "postgres") {
|
|
1615
|
-
throw
|
|
2797
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: params.feature });
|
|
1616
2798
|
}
|
|
1617
2799
|
}
|
|
1618
2800
|
function renderMigrationSessionRead(params) {
|
|
@@ -1627,7 +2809,7 @@ function renderMigrationSessionSet(params) {
|
|
|
1627
2809
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
1628
2810
|
const { milliseconds, setting } = params;
|
|
1629
2811
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
1630
|
-
throw new
|
|
2812
|
+
throw new VibeError9({
|
|
1631
2813
|
code: "VIBE_MIGRATION",
|
|
1632
2814
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
1633
2815
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -1714,63 +2896,21 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
1714
2896
|
returning: "none"
|
|
1715
2897
|
};
|
|
1716
2898
|
}
|
|
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
2899
|
export {
|
|
1769
2900
|
toPgArrayLiteral,
|
|
1770
2901
|
strictMatchPattern,
|
|
1771
2902
|
startsWithPattern,
|
|
2903
|
+
sqliteTableConstraints,
|
|
1772
2904
|
sqliteDialect,
|
|
2905
|
+
sqliteColumnClauses,
|
|
2906
|
+
sqlUsesIncoming,
|
|
2907
|
+
sql,
|
|
1773
2908
|
renderStatement,
|
|
2909
|
+
renderSqliteRebuildObjects,
|
|
2910
|
+
renderSqliteForeignKeyInventory,
|
|
2911
|
+
renderSqliteColumnInventory,
|
|
2912
|
+
renderSqlFragment,
|
|
2913
|
+
renderRowLockClause,
|
|
1774
2914
|
renderRlsRole,
|
|
1775
2915
|
renderRlsReadinessQuery,
|
|
1776
2916
|
renderRlsProbeTable,
|
|
@@ -1785,23 +2925,42 @@ export {
|
|
|
1785
2925
|
renderRlsContext,
|
|
1786
2926
|
renderRlsColumnType,
|
|
1787
2927
|
renderRelationStateQuery,
|
|
2928
|
+
renderPredicate,
|
|
1788
2929
|
renderPostgresWorkload,
|
|
1789
2930
|
renderPostgresExplain,
|
|
2931
|
+
renderMysqlObjectMembers,
|
|
2932
|
+
renderMysqlCheckProbe,
|
|
2933
|
+
renderMigrationVisibility,
|
|
1790
2934
|
renderMigrationSessionSet,
|
|
1791
2935
|
renderMigrationSessionRestore,
|
|
1792
2936
|
renderMigrationSessionRead,
|
|
2937
|
+
renderMigrationServerVersion,
|
|
2938
|
+
renderMigrationObjectQuery,
|
|
2939
|
+
renderMigrationNamespace,
|
|
2940
|
+
renderMigrationInventory,
|
|
2941
|
+
renderMigrationHistoryQuery,
|
|
2942
|
+
renderMigrationDependencies,
|
|
1793
2943
|
renderIndexStateQuery,
|
|
1794
2944
|
renderFragment,
|
|
1795
2945
|
renderExpr,
|
|
2946
|
+
renderExclusionSetup,
|
|
2947
|
+
renderExclusionProbe,
|
|
2948
|
+
renderExclusionDdl,
|
|
2949
|
+
renderExclusionCatalog,
|
|
1796
2950
|
renderDiagnostic,
|
|
1797
2951
|
renderConstraintStateQuery,
|
|
2952
|
+
renderConstraintOwnedIndexDrop,
|
|
1798
2953
|
renderColumnStateQuery,
|
|
2954
|
+
renderCheckProbe,
|
|
2955
|
+
renderAdvisoryLock,
|
|
1799
2956
|
projectionOutputName,
|
|
1800
2957
|
postgresDialect,
|
|
1801
2958
|
parsePgArrayLiteral,
|
|
1802
2959
|
parseDateTimeText,
|
|
1803
2960
|
mysqlDialect,
|
|
2961
|
+
mapConflictColumns,
|
|
1804
2962
|
jsonParamCell,
|
|
2963
|
+
isSqlFragment,
|
|
1805
2964
|
isSqlDefaultCell,
|
|
1806
2965
|
isSetOperation,
|
|
1807
2966
|
isListSetOperation,
|
|
@@ -1815,9 +2974,13 @@ export {
|
|
|
1815
2974
|
escapeGlobPattern,
|
|
1816
2975
|
enumMembershipCodec,
|
|
1817
2976
|
endsWithPattern,
|
|
2977
|
+
describeRowLock,
|
|
1818
2978
|
decodeIsNoop,
|
|
2979
|
+
decode,
|
|
1819
2980
|
dbNowMarker,
|
|
1820
2981
|
containsPattern,
|
|
2982
|
+
compileSql,
|
|
2983
|
+
compileExclusion,
|
|
1821
2984
|
SQL_DIALECTS,
|
|
1822
2985
|
SQL_DEFAULT_CELL,
|
|
1823
2986
|
SQLITE_CODECS,
|
|
@@ -1834,7 +2997,9 @@ export {
|
|
|
1834
2997
|
MYSQL_CAPABILITIES,
|
|
1835
2998
|
JSON_TRANSPORT,
|
|
1836
2999
|
DIALECT_CODECS,
|
|
1837
|
-
DIALECT_CAPABILITIES
|
|
3000
|
+
DIALECT_CAPABILITIES,
|
|
3001
|
+
ADVISORY_LOCK_RESULT_COLUMN,
|
|
3002
|
+
ADVISORY_KEY_BOUNDS
|
|
1838
3003
|
};
|
|
1839
3004
|
|
|
1840
|
-
//# debugId=
|
|
3005
|
+
//# debugId=F9D013AFDDCCA77864756E2164756E21
|