@vibeorm/sql 3.0.0 → 4.0.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 +12 -5
- package/dist/codecs.d.ts +9 -0
- package/dist/dialect.d.ts +10 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +1429 -876
- package/dist/index.js.map +19 -15
- package/dist/parameter-snapshot.d.ts +6 -0
- package/dist/params.d.ts +7 -8
- package/dist/query/expressions.d.ts +70 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +1078 -0
- package/dist/query/index.js.map +16 -0
- package/dist/query/mutations.d.ts +15 -0
- package/dist/query/reusable.d.ts +17 -0
- package/dist/query/select.d.ts +11 -0
- package/dist/query/sources.d.ts +37 -0
- package/dist/query/state.d.ts +73 -0
- package/dist/query/types.d.ts +188 -0
- package/dist/query-plan.d.ts +97 -0
- package/dist/result-decoder.d.ts +41 -10
- package/dist/sql-bindings.d.ts +84 -0
- package/dist/sql-input.d.ts +98 -0
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -39,33 +39,870 @@ function renderExternalOwnershipCatalog(params) {
|
|
|
39
39
|
return { text, values: [], returning: "none" };
|
|
40
40
|
}
|
|
41
41
|
// packages/sql/src/dialects/external-dependents.ts
|
|
42
|
+
import { unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
43
|
+
|
|
44
|
+
// packages/sql/src/params.ts
|
|
42
45
|
import { unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
43
46
|
|
|
47
|
+
// packages/sql/src/sql-bindings.ts
|
|
48
|
+
import { VibeError as VibeError5, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
49
|
+
|
|
50
|
+
// packages/sql/src/parameter-snapshot.ts
|
|
51
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
52
|
+
|
|
53
|
+
// packages/sql/src/sql-input.ts
|
|
54
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
55
|
+
|
|
56
|
+
// packages/sql/src/result-decoder.ts
|
|
57
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
58
|
+
|
|
59
|
+
// packages/sql/src/pg-array.ts
|
|
60
|
+
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
61
|
+
function toPgArrayLiteral(params) {
|
|
62
|
+
const { values } = params;
|
|
63
|
+
const parts = values.map((value) => {
|
|
64
|
+
if (value === null || value === undefined)
|
|
65
|
+
return "NULL";
|
|
66
|
+
const text = String(value);
|
|
67
|
+
return PG_ARRAY_QUOTE.test(text) ? `"${text.replace(/["\\]/g, (char) => `\\${char}`)}"` : text;
|
|
68
|
+
});
|
|
69
|
+
return `{${parts.join(",")}}`;
|
|
70
|
+
}
|
|
71
|
+
function parsePgArrayLiteral(params) {
|
|
72
|
+
const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
|
|
73
|
+
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
74
|
+
return null;
|
|
75
|
+
const inner = literal.slice(1, -1);
|
|
76
|
+
if (inner.length === 0)
|
|
77
|
+
return [];
|
|
78
|
+
const out = [];
|
|
79
|
+
let index = 0;
|
|
80
|
+
while (index < inner.length) {
|
|
81
|
+
if (inner[index] === '"') {
|
|
82
|
+
let text = "";
|
|
83
|
+
index++;
|
|
84
|
+
while (index < inner.length) {
|
|
85
|
+
const char = inner[index];
|
|
86
|
+
if (char === "\\") {
|
|
87
|
+
text += inner[index + 1] ?? "";
|
|
88
|
+
index += 2;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
index++;
|
|
92
|
+
if (char === '"')
|
|
93
|
+
break;
|
|
94
|
+
text += char;
|
|
95
|
+
}
|
|
96
|
+
out.push(text);
|
|
97
|
+
} else {
|
|
98
|
+
let text = "";
|
|
99
|
+
while (index < inner.length && inner[index] !== ",") {
|
|
100
|
+
text += inner[index];
|
|
101
|
+
index++;
|
|
102
|
+
}
|
|
103
|
+
out.push(text === "NULL" ? null : text);
|
|
104
|
+
}
|
|
105
|
+
if (inner[index] === ",")
|
|
106
|
+
index++;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
function parsePgArrayLiteralNested(params) {
|
|
111
|
+
const text = params.literal.replace(/^(?:\[-?\d+:-?\d+\])+=/, "");
|
|
112
|
+
const cursor = { index: 0 };
|
|
113
|
+
const parsed = parseDimension({ text, cursor });
|
|
114
|
+
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
115
|
+
}
|
|
116
|
+
function parseDimension(params) {
|
|
117
|
+
const { text, cursor } = params;
|
|
118
|
+
if (text[cursor.index] !== "{")
|
|
119
|
+
return null;
|
|
120
|
+
cursor.index++;
|
|
121
|
+
const out = [];
|
|
122
|
+
if (text[cursor.index] === "}") {
|
|
123
|
+
cursor.index++;
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
while (cursor.index < text.length) {
|
|
127
|
+
if (text[cursor.index] === "{") {
|
|
128
|
+
const nested = parseDimension({ text, cursor });
|
|
129
|
+
if (nested === null)
|
|
130
|
+
return null;
|
|
131
|
+
out.push(nested);
|
|
132
|
+
} else if (text[cursor.index] === '"') {
|
|
133
|
+
let element = "";
|
|
134
|
+
cursor.index++;
|
|
135
|
+
while (cursor.index < text.length && text[cursor.index] !== '"') {
|
|
136
|
+
if (text[cursor.index] === "\\")
|
|
137
|
+
cursor.index++;
|
|
138
|
+
element += text[cursor.index] ?? "";
|
|
139
|
+
cursor.index++;
|
|
140
|
+
}
|
|
141
|
+
if (text[cursor.index] !== '"')
|
|
142
|
+
return null;
|
|
143
|
+
cursor.index++;
|
|
144
|
+
out.push(element);
|
|
145
|
+
} else {
|
|
146
|
+
let element = "";
|
|
147
|
+
while (cursor.index < text.length && text[cursor.index] !== "," && text[cursor.index] !== "}") {
|
|
148
|
+
element += text[cursor.index];
|
|
149
|
+
cursor.index++;
|
|
150
|
+
}
|
|
151
|
+
out.push(element === "NULL" ? null : element);
|
|
152
|
+
}
|
|
153
|
+
if (text[cursor.index] === ",") {
|
|
154
|
+
cursor.index++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (text[cursor.index] !== "}")
|
|
158
|
+
return null;
|
|
159
|
+
cursor.index++;
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// packages/sql/src/codecs.ts
|
|
166
|
+
import { VibeError, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
|
|
167
|
+
function enumMembershipCodec(params) {
|
|
168
|
+
const { model, field, enum: enumName } = params;
|
|
169
|
+
const members = new Set(params.members);
|
|
170
|
+
return {
|
|
171
|
+
encode: (value) => {
|
|
172
|
+
if (typeof value === "string" && members.has(value))
|
|
173
|
+
return value;
|
|
174
|
+
throw new VibeError({
|
|
175
|
+
code: "VIBE_VALIDATION",
|
|
176
|
+
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
177
|
+
meta: { model, field, enum: enumName }
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
decode: (value) => {
|
|
181
|
+
if (typeof value === "string" && members.has(value))
|
|
182
|
+
return value;
|
|
183
|
+
throw invalidStoredData({ model, field, enum: enumName, value });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function decodeIsNoop(params) {
|
|
188
|
+
const { scalarType, wire } = params;
|
|
189
|
+
if (wire === undefined)
|
|
190
|
+
return false;
|
|
191
|
+
if (scalarType === "DateTime")
|
|
192
|
+
return false;
|
|
193
|
+
if (scalarType === "Json")
|
|
194
|
+
return wire.json === "parsed";
|
|
195
|
+
if (scalarType === "Int" || scalarType === "Float")
|
|
196
|
+
return wire.numbers === "native";
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
var identity = (value) => value;
|
|
200
|
+
var passthroughCodec = { encode: identity, decode: identity };
|
|
201
|
+
var numberCodec = {
|
|
202
|
+
encode: identity,
|
|
203
|
+
decode: (value) => typeof value === "string" ? Number(value) : value
|
|
204
|
+
};
|
|
205
|
+
var bigIntCodec = {
|
|
206
|
+
encode: (value) => typeof value === "bigint" ? value.toString() : value,
|
|
207
|
+
decode: (value) => {
|
|
208
|
+
if (typeof value === "bigint")
|
|
209
|
+
return value;
|
|
210
|
+
if (typeof value === "string" && /^[+-]?\d+$/.test(value.trim()))
|
|
211
|
+
return BigInt(value.trim());
|
|
212
|
+
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
213
|
+
return BigInt(value);
|
|
214
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
var decimalCodec = {
|
|
218
|
+
encode: (value) => typeof value === "number" || typeof value === "bigint" ? String(value) : value,
|
|
219
|
+
decode: identity
|
|
220
|
+
};
|
|
221
|
+
var mysqlDecimalCodec = {
|
|
222
|
+
encode: decimalCodec.encode,
|
|
223
|
+
decode: (value) => {
|
|
224
|
+
if (typeof value !== "string" || !value.includes("."))
|
|
225
|
+
return value;
|
|
226
|
+
const trimmed = value.replace(/0+$/, "").replace(/\.$/, "");
|
|
227
|
+
return trimmed === "" || trimmed === "-" ? "0" : trimmed;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
231
|
+
var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
|
|
232
|
+
function parseDateTimeText(params) {
|
|
233
|
+
const trimmed = params.text.trim();
|
|
234
|
+
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
235
|
+
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
236
|
+
if (!Number.isNaN(asUtc.getTime()))
|
|
237
|
+
return asUtc;
|
|
238
|
+
}
|
|
239
|
+
const zoned = PG_ZONED_DATETIME.exec(trimmed);
|
|
240
|
+
if (zoned !== null) {
|
|
241
|
+
const [, date, time, fraction, hours, minutes] = zoned;
|
|
242
|
+
const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
|
|
243
|
+
if (!Number.isNaN(iso.getTime()))
|
|
244
|
+
return iso;
|
|
245
|
+
}
|
|
246
|
+
const parsed = new Date(trimmed);
|
|
247
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
248
|
+
}
|
|
249
|
+
var dateNativeCodec = {
|
|
250
|
+
encode: (value) => {
|
|
251
|
+
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
252
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
253
|
+
}
|
|
254
|
+
return value;
|
|
255
|
+
},
|
|
256
|
+
decode: (value) => {
|
|
257
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
258
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
259
|
+
return date;
|
|
260
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var dateTextCodec = {
|
|
264
|
+
encode: (value) => {
|
|
265
|
+
dateNativeCodec.encode(value);
|
|
266
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
267
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
268
|
+
return date.toISOString();
|
|
269
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
270
|
+
},
|
|
271
|
+
decode: dateNativeCodec.decode
|
|
272
|
+
};
|
|
273
|
+
var mysqlDateCodec = {
|
|
274
|
+
encode: (value) => {
|
|
275
|
+
dateNativeCodec.encode(value);
|
|
276
|
+
if (value instanceof Date)
|
|
277
|
+
return value;
|
|
278
|
+
if (typeof value === "number") {
|
|
279
|
+
const parsed = new Date(value);
|
|
280
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
281
|
+
return parsed;
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
if (typeof value === "string")
|
|
285
|
+
return parseDateTimeText({ text: value }) ?? value;
|
|
286
|
+
return value;
|
|
287
|
+
},
|
|
288
|
+
decode: dateNativeCodec.decode
|
|
289
|
+
};
|
|
290
|
+
var MILLISECONDS_PER_DAY = 86400000;
|
|
291
|
+
var TIME_OF_DAY_TEXT = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/;
|
|
292
|
+
var pad = (value, width) => String(value).padStart(width, "0");
|
|
293
|
+
var utcMillisecondOfDay = (date) => (date.getTime() % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
|
|
294
|
+
var timeOfDayText = (millisecond) => `${pad(Math.floor(millisecond / 3600000), 2)}:${pad(Math.floor(millisecond / 60000) % 60, 2)}:${pad(Math.floor(millisecond / 1000) % 60, 2)}.${pad(millisecond % 1000, 3)}`;
|
|
295
|
+
function makeTimeOfDayCodec(params) {
|
|
296
|
+
const suffix = params.zoned ? "+00" : "";
|
|
297
|
+
return Object.freeze({
|
|
298
|
+
encode: (value) => {
|
|
299
|
+
dateNativeCodec.encode(value);
|
|
300
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
301
|
+
if (date === undefined || !Number.isFinite(date.getTime())) {
|
|
302
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
|
|
303
|
+
}
|
|
304
|
+
return `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
305
|
+
},
|
|
306
|
+
decode: (value) => {
|
|
307
|
+
if (value instanceof Date && Number.isFinite(value.getTime()))
|
|
308
|
+
return new Date(utcMillisecondOfDay(value));
|
|
309
|
+
const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
|
|
310
|
+
if (match !== null) {
|
|
311
|
+
const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
|
|
312
|
+
const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
|
|
313
|
+
const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
|
|
314
|
+
if (millisecond <= MILLISECONDS_PER_DAY) {
|
|
315
|
+
return new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "a time-of-day DateTime column returned a value that is not a time of day", meta: { value } });
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
var timeCodec = makeTimeOfDayCodec({ zoned: false });
|
|
323
|
+
var timetzCodec = makeTimeOfDayCodec({ zoned: true });
|
|
324
|
+
function timeOfDayCodec(params) {
|
|
325
|
+
const type = timeOfDayType({ nativeType: params.nativeType });
|
|
326
|
+
return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
|
|
327
|
+
}
|
|
328
|
+
var booleanIntCodec = {
|
|
329
|
+
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
330
|
+
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
331
|
+
};
|
|
332
|
+
var booleanNativeInCodec = {
|
|
333
|
+
encode: identity,
|
|
334
|
+
decode: (value) => value === "0" ? false : value === "1" ? true : booleanIntCodec.decode(value)
|
|
335
|
+
};
|
|
336
|
+
function parseJsonText(params) {
|
|
337
|
+
const { text, context } = params;
|
|
338
|
+
try {
|
|
339
|
+
return JSON.parse(text);
|
|
340
|
+
} catch (cause) {
|
|
341
|
+
if (context !== undefined) {
|
|
342
|
+
throw new VibeError({
|
|
343
|
+
code: "VIBE_INTERNAL",
|
|
344
|
+
message: "a bound Json field returned text that is not valid JSON",
|
|
345
|
+
meta: { ...context }
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
throw new VibeError({
|
|
349
|
+
code: "VIBE_INTERNAL",
|
|
350
|
+
message: "a Json column returned text that is not valid JSON",
|
|
351
|
+
meta: { value: text },
|
|
352
|
+
cause
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
var jsonCodec = {
|
|
357
|
+
encode: (value) => JSON.stringify(value),
|
|
358
|
+
decode: (value) => {
|
|
359
|
+
if (typeof value !== "string")
|
|
360
|
+
return value;
|
|
361
|
+
return parseJsonText({ text: value });
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
function shortestFloat32(params) {
|
|
365
|
+
const { value } = params;
|
|
366
|
+
if (value === 0 || !Number.isFinite(value))
|
|
367
|
+
return value;
|
|
368
|
+
const target = Math.fround(value);
|
|
369
|
+
for (let precision = 1;precision <= 9; precision++) {
|
|
370
|
+
const candidate = Number(value.toPrecision(precision));
|
|
371
|
+
if (Math.fround(candidate) === target)
|
|
372
|
+
return candidate;
|
|
373
|
+
}
|
|
374
|
+
return value;
|
|
375
|
+
}
|
|
376
|
+
var float32Codec = {
|
|
377
|
+
encode: identity,
|
|
378
|
+
decode: (value) => {
|
|
379
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
380
|
+
return typeof number === "number" ? shortestFloat32({ value: number }) : number;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
var booleanFromText = (text) => text === "t" ? true : text === "f" ? false : text;
|
|
384
|
+
var bytesFromText = (text) => /^\\x(?:[0-9a-fA-F]{2})*$/.test(text) ? new Uint8Array(Buffer.from(text.slice(2), "hex")) : text;
|
|
385
|
+
function postgresArrayElementFromText(params) {
|
|
386
|
+
switch (params.scalarType) {
|
|
387
|
+
case "Boolean":
|
|
388
|
+
return booleanFromText;
|
|
389
|
+
case "Bytes":
|
|
390
|
+
return bytesFromText;
|
|
391
|
+
case "Json":
|
|
392
|
+
return jsonCodec.decode;
|
|
393
|
+
default:
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
var POSTGRES_CODECS = {
|
|
398
|
+
String: passthroughCodec,
|
|
399
|
+
Boolean: passthroughCodec,
|
|
400
|
+
Int: numberCodec,
|
|
401
|
+
BigInt: bigIntCodec,
|
|
402
|
+
Float: numberCodec,
|
|
403
|
+
Decimal: decimalCodec,
|
|
404
|
+
DateTime: dateNativeCodec,
|
|
405
|
+
Json: jsonCodec,
|
|
406
|
+
Bytes: passthroughCodec
|
|
407
|
+
};
|
|
408
|
+
var SQLITE_CODECS = {
|
|
409
|
+
String: passthroughCodec,
|
|
410
|
+
Boolean: booleanIntCodec,
|
|
411
|
+
Int: numberCodec,
|
|
412
|
+
BigInt: bigIntCodec,
|
|
413
|
+
Float: numberCodec,
|
|
414
|
+
Decimal: decimalCodec,
|
|
415
|
+
DateTime: dateTextCodec,
|
|
416
|
+
Json: jsonCodec,
|
|
417
|
+
Bytes: passthroughCodec
|
|
418
|
+
};
|
|
419
|
+
var MYSQL_CODECS = {
|
|
420
|
+
String: passthroughCodec,
|
|
421
|
+
Boolean: booleanNativeInCodec,
|
|
422
|
+
Int: numberCodec,
|
|
423
|
+
BigInt: bigIntCodec,
|
|
424
|
+
Float: numberCodec,
|
|
425
|
+
Decimal: mysqlDecimalCodec,
|
|
426
|
+
DateTime: mysqlDateCodec,
|
|
427
|
+
Json: jsonCodec,
|
|
428
|
+
Bytes: passthroughCodec
|
|
429
|
+
};
|
|
430
|
+
var DIALECT_CODECS = {
|
|
431
|
+
postgres: POSTGRES_CODECS,
|
|
432
|
+
sqlite: SQLITE_CODECS,
|
|
433
|
+
mysql: MYSQL_CODECS
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// packages/sql/src/result-decoder.ts
|
|
437
|
+
var scalarContracts = new WeakMap;
|
|
438
|
+
function resultScalarContract(params) {
|
|
439
|
+
return scalarContracts.get(params.decoder);
|
|
440
|
+
}
|
|
441
|
+
function registerScalar(params) {
|
|
442
|
+
if (params.contract !== undefined)
|
|
443
|
+
scalarContracts.set(params.decoder, Object.freeze(params.contract));
|
|
444
|
+
return params.decoder;
|
|
445
|
+
}
|
|
446
|
+
function wrappedContract(params) {
|
|
447
|
+
const contract = scalarContracts.get(params.decoder);
|
|
448
|
+
if (contract === undefined || params.list === true && contract.list)
|
|
449
|
+
return;
|
|
450
|
+
return { ...contract, ...params.list === true ? { list: true, nullable: false } : {}, ...params.nullable === true ? { nullable: true } : {} };
|
|
451
|
+
}
|
|
452
|
+
function refuse(encoding, message) {
|
|
453
|
+
throw new VibeError2({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
454
|
+
}
|
|
455
|
+
function scalar(type, valid) {
|
|
456
|
+
const convert = (params, encoding) => {
|
|
457
|
+
const { value, dialect } = params;
|
|
458
|
+
if (value === null || value === undefined)
|
|
459
|
+
refuse(encoding, `A non-null ${type} projection received null or a missing column`);
|
|
460
|
+
if (type === "BigInt" && !(typeof value === "bigint" || typeof value === "string" && /^[+-]?\d+$/.test(value) || typeof value === "number" && Number.isSafeInteger(value))) {
|
|
461
|
+
refuse(encoding, "BigInt requires exact integer text, bigint, or a safe integer");
|
|
462
|
+
}
|
|
463
|
+
if (type === "Boolean" && value !== true && value !== false && value !== 0 && value !== 1 && !(!encoding && dialect === "mysql" && (value === "0" || value === "1")))
|
|
464
|
+
refuse(encoding, "Boolean requires true, false, 0, or 1");
|
|
465
|
+
if (type === "Decimal" && typeof value === "number")
|
|
466
|
+
refuse(encoding, "Decimal requires exact decimal text, not a floating-point number");
|
|
467
|
+
if ((type === "Int" || type === "Float") && typeof value === "string" && !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) {
|
|
468
|
+
refuse(encoding, `Invalid ${type} text`);
|
|
469
|
+
}
|
|
470
|
+
const codec = DIALECT_CODECS[dialect][type];
|
|
471
|
+
const result = type === "Json" && params.wire?.json === "parsed" ? value : codec.decode(value);
|
|
472
|
+
if (!valid(result))
|
|
473
|
+
refuse(encoding, `Invalid ${type} projection value`);
|
|
474
|
+
return result;
|
|
475
|
+
};
|
|
476
|
+
return registerScalar({ contract: { scalar: type, nullable: false, list: false }, decoder: Object.freeze({
|
|
477
|
+
decode: (params) => convert(params, false),
|
|
478
|
+
encode: (params) => {
|
|
479
|
+
const value = type === "Json" ? params.value : convert(params, true);
|
|
480
|
+
if (value === undefined || value === null)
|
|
481
|
+
refuse(true, `A non-null ${type} parameter cannot be null or undefined`);
|
|
482
|
+
return DIALECT_CODECS[params.dialect][type].encode(value);
|
|
483
|
+
}
|
|
484
|
+
}) });
|
|
485
|
+
}
|
|
486
|
+
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
487
|
+
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
488
|
+
var extendedDecoder = registerScalar({ contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
|
|
489
|
+
decode: (params) => {
|
|
490
|
+
if (params.value === "infinity" || params.value === Infinity)
|
|
491
|
+
return "infinity";
|
|
492
|
+
if (params.value === "-infinity" || params.value === -Infinity)
|
|
493
|
+
return "-infinity";
|
|
494
|
+
return dateDecoder.decode(params);
|
|
495
|
+
},
|
|
496
|
+
encode: (params) => {
|
|
497
|
+
if (params.value === "infinity" || params.value === "-infinity") {
|
|
498
|
+
if (params.dialect !== "postgres")
|
|
499
|
+
throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
500
|
+
return params.value;
|
|
501
|
+
}
|
|
502
|
+
return dateDecoder.encode(params);
|
|
503
|
+
}
|
|
504
|
+
}) });
|
|
505
|
+
function hasEncoder(decoder) {
|
|
506
|
+
return "encode" in decoder && typeof decoder.encode === "function";
|
|
507
|
+
}
|
|
508
|
+
function nullable({ decoder }) {
|
|
509
|
+
const read = (params) => params.value === null ? null : decoder.decode(params);
|
|
510
|
+
if (!hasEncoder(decoder))
|
|
511
|
+
return Object.freeze({ decode: read });
|
|
512
|
+
return registerScalar({ contract: wrappedContract({ decoder, nullable: true }), decoder: Object.freeze({
|
|
513
|
+
decode: read,
|
|
514
|
+
encode: (params) => params.value === null ? null : decoder.encode(params)
|
|
515
|
+
}) });
|
|
516
|
+
}
|
|
517
|
+
function array({ decoder }) {
|
|
518
|
+
const bidirectional = hasEncoder(decoder);
|
|
519
|
+
const read = (params) => {
|
|
520
|
+
const raw = params.value;
|
|
521
|
+
let value;
|
|
522
|
+
if (params.dialect === "postgres" && typeof raw === "string") {
|
|
523
|
+
if (bidirectional)
|
|
524
|
+
value = parsePgArrayLiteral({ literal: raw });
|
|
525
|
+
else {
|
|
526
|
+
const elements = parsePgArrayLiteralNested({ literal: raw });
|
|
527
|
+
if (elements !== null && elements.some((element) => Array.isArray(element)))
|
|
528
|
+
refuse(false, "Read-only array decoders support one-dimensional SQL arrays only");
|
|
529
|
+
value = elements;
|
|
530
|
+
}
|
|
531
|
+
} else
|
|
532
|
+
value = ArrayBuffer.isView(raw) && "length" in raw ? Array.from(raw) : raw;
|
|
533
|
+
if (!Array.isArray(value))
|
|
534
|
+
refuse(false, "Expected an array projection");
|
|
535
|
+
return value.map((element) => decoder.decode({ ...params, value: element }));
|
|
536
|
+
};
|
|
537
|
+
if (!hasEncoder(decoder))
|
|
538
|
+
return Object.freeze({ decode: read });
|
|
539
|
+
return registerScalar({ contract: wrappedContract({ decoder, list: true }), decoder: Object.freeze({
|
|
540
|
+
decode: read,
|
|
541
|
+
encode: (params) => {
|
|
542
|
+
if (!Array.isArray(params.value))
|
|
543
|
+
refuse(true, "Expected an array parameter");
|
|
544
|
+
return params.value.map((value) => decoder.encode({ ...params, value }));
|
|
545
|
+
}
|
|
546
|
+
}) });
|
|
547
|
+
}
|
|
548
|
+
var encode = Object.freeze({
|
|
549
|
+
array: ({ encoder }) => Object.freeze({
|
|
550
|
+
encode: (params) => {
|
|
551
|
+
if (!Array.isArray(params.value))
|
|
552
|
+
refuse(true, "Expected an array parameter");
|
|
553
|
+
return Array.from(params.value, (value) => encoder.encode({ ...params, value }));
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
});
|
|
557
|
+
var decode = Object.freeze({
|
|
558
|
+
string: stringDecoder,
|
|
559
|
+
boolean: scalar("Boolean", (value) => typeof value === "boolean"),
|
|
560
|
+
int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
|
|
561
|
+
float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
|
|
562
|
+
bigInt: scalar("BigInt", (value) => typeof value === "bigint"),
|
|
563
|
+
decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
|
|
564
|
+
dateTime: dateDecoder,
|
|
565
|
+
extendedDateTime: extendedDecoder,
|
|
566
|
+
json: scalar("Json", (value) => value !== undefined),
|
|
567
|
+
bytes: scalar("Bytes", (value) => value instanceof Uint8Array),
|
|
568
|
+
nullable,
|
|
569
|
+
array,
|
|
570
|
+
enum: ({ values }) => {
|
|
571
|
+
const members = new Set(values);
|
|
572
|
+
return registerScalar({ contract: { scalar: "String", nullable: false, list: false }, decoder: Object.freeze({
|
|
573
|
+
decode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(false, "Unknown enum projection member"),
|
|
574
|
+
encode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(true, "Unknown enum parameter member")
|
|
575
|
+
}) });
|
|
576
|
+
},
|
|
577
|
+
object: ({ fields }) => {
|
|
578
|
+
const entries = Object.entries(fields);
|
|
579
|
+
const convert = (params, encoding) => {
|
|
580
|
+
if (typeof params.value !== "object" || params.value === null || Array.isArray(params.value))
|
|
581
|
+
refuse(encoding, "Expected a result row object");
|
|
582
|
+
const row = params.value;
|
|
583
|
+
return Object.fromEntries(entries.map(([name, decoder]) => {
|
|
584
|
+
if (!Object.hasOwn(row, name))
|
|
585
|
+
refuse(encoding, `Missing projection column "${name}"`);
|
|
586
|
+
return [name, encoding ? decoder.encode({ ...params, value: row[name] }) : decoder.decode({ ...params, value: row[name] })];
|
|
587
|
+
}));
|
|
588
|
+
};
|
|
589
|
+
return Object.freeze({ decode: (params) => convert(params, false), encode: (params) => convert(params, true) });
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// packages/sql/src/sql-input.ts
|
|
594
|
+
var inputs = new WeakMap;
|
|
595
|
+
var tokens = new WeakMap;
|
|
596
|
+
var scopes = new WeakMap;
|
|
597
|
+
function invalid(message) {
|
|
598
|
+
throw new VibeError3({ code: "VIBE_VALIDATION", message });
|
|
599
|
+
}
|
|
600
|
+
function isSqlParam(value) {
|
|
601
|
+
return typeof value === "object" && value !== null && tokens.has(value);
|
|
602
|
+
}
|
|
603
|
+
function isSqlInput(value) {
|
|
604
|
+
return typeof value === "object" && value !== null && inputs.has(value);
|
|
605
|
+
}
|
|
606
|
+
function createSqlInput(params) {
|
|
607
|
+
if (typeof params.contract.encoder?.encode !== "function")
|
|
608
|
+
invalid("SQL input contracts require a parameter encoder");
|
|
609
|
+
const input = Object.freeze(Object.create(null));
|
|
610
|
+
inputs.set(input, Object.freeze({
|
|
611
|
+
...params.contract,
|
|
612
|
+
...params.contract.enumValues === undefined ? {} : { enumValues: Object.freeze([...params.contract.enumValues]) },
|
|
613
|
+
...params.contract.enumType === undefined ? {} : { enumType: Object.freeze({ ...params.contract.enumType }) }
|
|
614
|
+
}));
|
|
615
|
+
return input;
|
|
616
|
+
}
|
|
617
|
+
function sqlInputContract(params) {
|
|
618
|
+
const value = params.input;
|
|
619
|
+
if (typeof value !== "object" || value === null)
|
|
620
|
+
invalid("SQL input declarations require a decoder or qb.input descriptor");
|
|
621
|
+
const declared = inputs.get(value);
|
|
622
|
+
if (declared !== undefined)
|
|
623
|
+
return declared;
|
|
624
|
+
if (!("encode" in value) || !("decode" in value) || typeof value.encode !== "function" || typeof value.decode !== "function")
|
|
625
|
+
invalid("Expected an authentic SQL input descriptor or bidirectional decoder");
|
|
626
|
+
const decoder = value;
|
|
627
|
+
const physical = resultScalarContract({ decoder });
|
|
628
|
+
return Object.freeze({ encoder: decoder, scalar: physical?.scalar ?? "Unknown", nullable: physical?.nullable ?? true, list: physical?.list ?? false });
|
|
629
|
+
}
|
|
630
|
+
function createSqlInputScope(params) {
|
|
631
|
+
if (typeof params.isHandle !== "function")
|
|
632
|
+
invalid("SQL input scopes require the builder's handle check");
|
|
633
|
+
const declarations = params.inputs;
|
|
634
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations))
|
|
635
|
+
invalid("Reusable SQL requires an input declaration record");
|
|
636
|
+
const prototype = Object.getPrototypeOf(declarations);
|
|
637
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
638
|
+
invalid("SQL input declarations require a plain record");
|
|
639
|
+
const keys = Reflect.ownKeys(declarations);
|
|
640
|
+
const entries = keys.map((key) => {
|
|
641
|
+
if (typeof key !== "string" || key.length === 0 || key.includes("\x00"))
|
|
642
|
+
invalid("SQL input names must be nonempty strings without NUL");
|
|
643
|
+
const descriptor = Object.getOwnPropertyDescriptor(declarations, key);
|
|
644
|
+
if (!("value" in descriptor))
|
|
645
|
+
invalid("SQL input declarations cannot contain accessors");
|
|
646
|
+
return [key, sqlInputContract({ input: descriptor.value })];
|
|
647
|
+
});
|
|
648
|
+
const scope = Object.freeze(Object.create(null));
|
|
649
|
+
scopes.set(scope, { open: true, keys: Object.freeze(entries.map(([key]) => key)), isHandle: params.isHandle });
|
|
650
|
+
const record = Object.create(null);
|
|
651
|
+
for (const [key, contract] of entries) {
|
|
652
|
+
const param = Object.freeze(Object.create(null));
|
|
653
|
+
tokens.set(param, { scope, key, contract });
|
|
654
|
+
record[key] = param;
|
|
655
|
+
}
|
|
656
|
+
return { scope, params: Object.freeze(record) };
|
|
657
|
+
}
|
|
658
|
+
function sealSqlInputScope(params) {
|
|
659
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
660
|
+
state.open = false;
|
|
661
|
+
}
|
|
662
|
+
function assertSqlParamBuilding(params) {
|
|
663
|
+
const state = tokens.get(params.param) ?? invalid("Expected an authentic SQL input token");
|
|
664
|
+
if (scopes.get(state.scope)?.open !== true)
|
|
665
|
+
invalid("SQL input tokens cannot escape their reusable build callback");
|
|
666
|
+
}
|
|
667
|
+
function sqlParamContract(params) {
|
|
668
|
+
return (tokens.get(params.param) ?? invalid("Expected an authentic SQL input token")).contract;
|
|
669
|
+
}
|
|
670
|
+
function sqlParamKey(params) {
|
|
671
|
+
const state = tokens.get(params.param) ?? invalid("Expected an authentic SQL input token");
|
|
672
|
+
if (state.scope !== params.scope)
|
|
673
|
+
invalid("A reusable query cannot capture another template's input token");
|
|
674
|
+
return state.key;
|
|
675
|
+
}
|
|
676
|
+
function sqlInputScopeHandle(params) {
|
|
677
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
678
|
+
return state.isHandle(params.value);
|
|
679
|
+
}
|
|
680
|
+
function sqlInputScopeKeys(params) {
|
|
681
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
682
|
+
if (state.open)
|
|
683
|
+
invalid("A reusable SQL build must finish before compilation");
|
|
684
|
+
return state.keys;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// packages/sql/src/parameter-snapshot.ts
|
|
688
|
+
function snapshotSqlValue(value, ancestors = new Set, isHandle) {
|
|
689
|
+
const invalid = (message) => {
|
|
690
|
+
throw new VibeError4({ code: "VIBE_VALIDATION", message });
|
|
691
|
+
};
|
|
692
|
+
if (isSqlParam(value))
|
|
693
|
+
return invalid("SQL input tokens are valid only in admitted reusable builder positions");
|
|
694
|
+
if (isHandle !== undefined && typeof value === "object" && value !== null && isHandle(value))
|
|
695
|
+
return invalid("SQL builder, plan, template, input and fragment handles are not execution values");
|
|
696
|
+
if (value === undefined)
|
|
697
|
+
return invalid("SQL values cannot be undefined; omit a defaulted column or use null");
|
|
698
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "bigint")
|
|
699
|
+
return value;
|
|
700
|
+
if (typeof value === "number")
|
|
701
|
+
return value;
|
|
702
|
+
if (value instanceof Date)
|
|
703
|
+
return Number.isFinite(value.getTime()) ? new Date(value.getTime()) : invalid("SQL dates must be finite");
|
|
704
|
+
if (value instanceof Uint8Array)
|
|
705
|
+
return new Uint8Array(value);
|
|
706
|
+
if (typeof value !== "object")
|
|
707
|
+
return invalid("Unsupported SQL parameter value");
|
|
708
|
+
if (ancestors.has(value))
|
|
709
|
+
return invalid("Cyclic SQL parameter values are unsupported");
|
|
710
|
+
const array = Array.isArray(value);
|
|
711
|
+
const prototype = Object.getPrototypeOf(value);
|
|
712
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
|
|
713
|
+
return invalid("SQL parameters require standard arrays or plain objects");
|
|
714
|
+
const nested = new Set([...ancestors, value]);
|
|
715
|
+
const snapshot = array ? [] : Object.create(prototype === null ? null : Object.prototype);
|
|
716
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
717
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
718
|
+
if (!("value" in descriptor))
|
|
719
|
+
return invalid("SQL parameter objects and arrays cannot contain accessors");
|
|
720
|
+
Object.defineProperty(snapshot, key, { ...descriptor, value: snapshotSqlValue(descriptor.value, nested, isHandle) });
|
|
721
|
+
}
|
|
722
|
+
return snapshot;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// packages/sql/src/sql-bindings.ts
|
|
726
|
+
var recordings = new WeakMap;
|
|
727
|
+
var programs = new WeakMap;
|
|
728
|
+
function invalid2(message) {
|
|
729
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message });
|
|
730
|
+
}
|
|
731
|
+
function unsupported(message) {
|
|
732
|
+
throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message });
|
|
733
|
+
}
|
|
734
|
+
function sqlParameterTransport(params) {
|
|
735
|
+
if (isNativeEnumArrayParameter(params.value))
|
|
736
|
+
return { kind: "native-enum-array", type: params.value.type };
|
|
737
|
+
if (isJsonListParameter(params.value))
|
|
738
|
+
return { kind: "json-list", storage: params.value.storage, whole: typeof params.value.value !== "string" };
|
|
739
|
+
return { kind: "plain" };
|
|
740
|
+
}
|
|
741
|
+
function renderSqlParameterTransport(params) {
|
|
742
|
+
const { dialect, placeholder, transport } = params;
|
|
743
|
+
if (transport.kind === "plain")
|
|
744
|
+
return placeholder;
|
|
745
|
+
if (!dialect.capabilities.scalarArrays)
|
|
746
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "scalarArrays" });
|
|
747
|
+
if (transport.kind === "native-enum-array")
|
|
748
|
+
return `${placeholder}::${dialect.quoteIdent({ name: transport.type.namespace })}.${dialect.quoteIdent({ name: transport.type.name })}[]`;
|
|
749
|
+
return `${placeholder}::text::${transport.storage}${transport.whole ? "[]" : ""}`;
|
|
750
|
+
}
|
|
751
|
+
function sqlParameterWireValue(params) {
|
|
752
|
+
if (isNativeEnumArrayParameter(params.value))
|
|
753
|
+
return params.value.value;
|
|
754
|
+
if (isJsonListParameter(params.value))
|
|
755
|
+
return typeof params.value.value === "string" ? params.value.value : toPgArrayLiteral({ values: params.value.value });
|
|
756
|
+
return params.value;
|
|
757
|
+
}
|
|
758
|
+
function isSqlBindingCollector(params) {
|
|
759
|
+
return recordings.has(params.collector);
|
|
760
|
+
}
|
|
761
|
+
function startSqlBindingCollection(params) {
|
|
762
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
763
|
+
if (recordings.has(params.collector))
|
|
764
|
+
invalid2("SQL binding collection was already started");
|
|
765
|
+
recordings.set(params.collector, { dialect: params.dialect, scope: params.scope, steps: [], contracts: new Map, used: new Set });
|
|
766
|
+
}
|
|
767
|
+
function contractShape(contract) {
|
|
768
|
+
return JSON.stringify({
|
|
769
|
+
scalar: contract.scalar,
|
|
770
|
+
nullable: contract.nullable,
|
|
771
|
+
list: contract.list,
|
|
772
|
+
nativeType: contract.nativeType,
|
|
773
|
+
enumValues: contract.enumValues,
|
|
774
|
+
enumType: contract.enumType === undefined ? undefined : [contract.enumType.namespace, contract.enumType.name],
|
|
775
|
+
jsonStorage: contract.jsonStorage
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
function sameContract(left, right) {
|
|
779
|
+
return left.encoder === right.encoder && left.columnIdentity === right.columnIdentity && contractShape(left) === contractShape(right);
|
|
780
|
+
}
|
|
781
|
+
function recordSqlBinding(params) {
|
|
782
|
+
const state = recordings.get(params.collector) ?? invalid2("SQL input tokens require execution through their reusable template");
|
|
783
|
+
let key;
|
|
784
|
+
if (params.source.kind === "input") {
|
|
785
|
+
key = sqlParamKey({ param: params.source.param, scope: state.scope });
|
|
786
|
+
if (params.contract === undefined)
|
|
787
|
+
invalid2("Named SQL inputs require an effective encoding contract");
|
|
788
|
+
const previous = state.contracts.get(key);
|
|
789
|
+
if (previous !== undefined && !sameContract(previous, params.contract))
|
|
790
|
+
invalid2(`SQL input "${key}" has incompatible encoding contracts`);
|
|
791
|
+
state.contracts.set(key, params.contract);
|
|
792
|
+
state.used.add(key);
|
|
793
|
+
}
|
|
794
|
+
const transport = params.transport.kind === "native-enum-array" ? Object.freeze({ ...params.transport, type: Object.freeze({ ...params.transport.type }) }) : Object.freeze({ ...params.transport });
|
|
795
|
+
const source = params.source.kind === "literal" ? Object.freeze({ kind: "literal", value: snapshotSqlValue(params.source.value) }) : Object.freeze({ ...params.source });
|
|
796
|
+
const placeholder = params.collector.reserve();
|
|
797
|
+
const text = renderSqlParameterTransport({ dialect: state.dialect, placeholder, transport });
|
|
798
|
+
state.steps.push(Object.freeze({ kind: "slot", source, key, transport, encode: params.encode, rejectNull: params.rejectNull === true, ...params.validate === undefined ? {} : { validate: params.validate } }));
|
|
799
|
+
return text;
|
|
800
|
+
}
|
|
801
|
+
function recordSqlBindingCheck(params) {
|
|
802
|
+
const state = recordings.get(params.collector) ?? invalid2("Expected a structural SQL collector");
|
|
803
|
+
state.steps.push(Object.freeze({ kind: "check", check: params.check }));
|
|
804
|
+
}
|
|
805
|
+
function finishSqlBindingCollection(params) {
|
|
806
|
+
const state = recordings.get(params.collector) ?? invalid2("Expected a structural SQL collector");
|
|
807
|
+
const keys = sqlInputScopeKeys({ scope: state.scope });
|
|
808
|
+
for (const key of keys)
|
|
809
|
+
if (!state.used.has(key))
|
|
810
|
+
invalid2(`Declared SQL input "${key}" is unused by the finished query`);
|
|
811
|
+
const program = Object.freeze(Object.create(null));
|
|
812
|
+
programs.set(program, { dialect: state.dialect, keys, steps: Object.freeze([...state.steps]) });
|
|
813
|
+
return program;
|
|
814
|
+
}
|
|
815
|
+
function inputSnapshot(params) {
|
|
816
|
+
const value = params.value === undefined && params.keys.length === 0 ? Object.create(null) : params.value;
|
|
817
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
818
|
+
invalid2("Reusable SQL parameters require an exact plain record");
|
|
819
|
+
const prototype = Object.getPrototypeOf(value);
|
|
820
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
821
|
+
invalid2("Reusable SQL parameters require a plain record without inherited inputs");
|
|
822
|
+
const own = Reflect.ownKeys(value);
|
|
823
|
+
if (own.length !== params.keys.length || own.some((key) => typeof key !== "string" || !params.keys.includes(key)))
|
|
824
|
+
invalid2("Reusable SQL parameters must contain exactly the declared names");
|
|
825
|
+
const descriptors = params.keys.map((key) => {
|
|
826
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
827
|
+
if (descriptor === undefined || !("value" in descriptor) || descriptor.value === undefined)
|
|
828
|
+
invalid2(`SQL input "${key}" must be an own defined data property`);
|
|
829
|
+
return { key, value: descriptor.value };
|
|
830
|
+
});
|
|
831
|
+
const snapshot = Object.create(null);
|
|
832
|
+
for (const entry of descriptors)
|
|
833
|
+
snapshot[entry.key] = snapshotSqlValue(entry.value, new Set, params.isHandle);
|
|
834
|
+
return snapshot;
|
|
835
|
+
}
|
|
836
|
+
function sameTransport(left, right) {
|
|
837
|
+
if (left.kind !== right.kind)
|
|
838
|
+
return false;
|
|
839
|
+
if (left.kind === "native-enum-array" && right.kind === "native-enum-array")
|
|
840
|
+
return left.type.namespace === right.type.namespace && left.type.name === right.type.name;
|
|
841
|
+
if (left.kind === "json-list" && right.kind === "json-list")
|
|
842
|
+
return left.storage === right.storage && left.whole === right.whole;
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
function bindSqlBindingProgram(params) {
|
|
846
|
+
const state = programs.get(params.program) ?? invalid2("Expected an authentic SQL binding program");
|
|
847
|
+
const inputs = inputSnapshot({ keys: state.keys, value: params.params, isHandle: params.isHandle });
|
|
848
|
+
const encodedInputs = new Map;
|
|
849
|
+
const values = [];
|
|
850
|
+
for (const step of state.steps) {
|
|
851
|
+
if (step.kind === "check") {
|
|
852
|
+
step.check();
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const value = step.source.kind === "literal" ? step.source.value : inputs[step.key];
|
|
856
|
+
step.validate?.(value);
|
|
857
|
+
if (step.rejectNull && value === null)
|
|
858
|
+
invalid2("SQL comparison and membership inputs cannot be SQL NULL; use isNull");
|
|
859
|
+
let encoded;
|
|
860
|
+
if (step.key !== undefined && encodedInputs.has(step.key))
|
|
861
|
+
encoded = encodedInputs.get(step.key);
|
|
862
|
+
else {
|
|
863
|
+
encoded = snapshotSqlValue(step.encode({ value: snapshotSqlValue(value), dialect: state.dialect }));
|
|
864
|
+
if (step.key !== undefined)
|
|
865
|
+
encodedInputs.set(step.key, encoded);
|
|
866
|
+
}
|
|
867
|
+
step.validate?.(encoded);
|
|
868
|
+
if (step.rejectNull && encoded === null)
|
|
869
|
+
invalid2("SQL comparison and membership encoders cannot produce SQL NULL; use isNull");
|
|
870
|
+
if (encoded !== null && !sameTransport(step.transport, sqlParameterTransport({ value: encoded })))
|
|
871
|
+
unsupported("A reusable SQL encoder produced an undeclared parameter transport shape");
|
|
872
|
+
values.push(snapshotSqlValue(sqlParameterWireValue({ value: encoded })));
|
|
873
|
+
}
|
|
874
|
+
return values;
|
|
875
|
+
}
|
|
876
|
+
|
|
44
877
|
// packages/sql/src/params.ts
|
|
45
|
-
import { unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
46
878
|
class ParamCollector {
|
|
47
879
|
dialect;
|
|
880
|
+
reserved = 0;
|
|
48
881
|
values;
|
|
49
882
|
constructor(dialect) {
|
|
50
883
|
this.dialect = dialect;
|
|
51
884
|
this.values = [];
|
|
52
885
|
}
|
|
886
|
+
reserve() {
|
|
887
|
+
const index = isSqlBindingCollector({ collector: this }) ? ++this.reserved : this.values.length;
|
|
888
|
+
return this.dialect.placeholder({ index });
|
|
889
|
+
}
|
|
890
|
+
count() {
|
|
891
|
+
return isSqlBindingCollector({ collector: this }) ? this.reserved : this.values.length;
|
|
892
|
+
}
|
|
53
893
|
add(value) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
this.values.push(value);
|
|
68
|
-
return this.dialect.placeholder({ index: this.values.length });
|
|
894
|
+
const transport = sqlParameterTransport({ value });
|
|
895
|
+
if (isSqlBindingCollector({ collector: this }))
|
|
896
|
+
return recordSqlBinding({
|
|
897
|
+
collector: this,
|
|
898
|
+
source: { kind: "literal", value },
|
|
899
|
+
transport,
|
|
900
|
+
encode: (params) => params.value
|
|
901
|
+
});
|
|
902
|
+
if (transport.kind !== "plain" && !this.dialect.capabilities.scalarArrays)
|
|
903
|
+
throw unsupportedCapability3({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
904
|
+
this.values.push(sqlParameterWireValue({ value }));
|
|
905
|
+
return renderSqlParameterTransport({ dialect: this.dialect, placeholder: this.reserve(), transport });
|
|
69
906
|
}
|
|
70
907
|
addMany(values) {
|
|
71
908
|
return values.map((value) => this.add(value));
|
|
@@ -75,7 +912,7 @@ class ParamCollector {
|
|
|
75
912
|
// packages/sql/src/dialects/external-dependents.ts
|
|
76
913
|
function renderExternalDependents(params) {
|
|
77
914
|
if (params.dialect.name !== "postgres")
|
|
78
|
-
throw
|
|
915
|
+
throw unsupportedCapability4({ dialect: params.dialect.name, feature: "externalObjects" });
|
|
79
916
|
const collector = new ParamCollector(params.dialect);
|
|
80
917
|
const requests = params.targets.map((target, position) => ({ position, kind: target.kind, namespace: target.namespace, name: target.name, column: target.kind === "column" ? target.column : null }));
|
|
81
918
|
const request = collector.add(JSON.stringify(requests));
|
|
@@ -117,11 +954,11 @@ ORDER BY 1, 2, 3, 4`;
|
|
|
117
954
|
return { text, values: collector.values, returning: "none" };
|
|
118
955
|
}
|
|
119
956
|
// packages/sql/src/dialects/postgres-physical-catalog.ts
|
|
120
|
-
import { unsupportedCapability as
|
|
957
|
+
import { unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
121
958
|
function renderPostgresPhysicalCatalog(params) {
|
|
122
959
|
const { dialect } = params;
|
|
123
960
|
if (dialect.name !== "postgres")
|
|
124
|
-
throw
|
|
961
|
+
throw unsupportedCapability5({ dialect: dialect.name, feature: "postgresPhysicalCatalog" });
|
|
125
962
|
const collector = new ParamCollector(dialect);
|
|
126
963
|
const namespace = collector.add(params.namespace);
|
|
127
964
|
const extensionOwnedFilter = `NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON c.oid = d.objid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE d.classid = 'pg_class'::regclass AND d.deptype = 'e' AND n.nspname = ${namespace} AND c.relname = table_name)`;
|
|
@@ -155,11 +992,11 @@ function renderPostgresPhysicalCatalog(params) {
|
|
|
155
992
|
return { text, values: collector.values, returning: "none" };
|
|
156
993
|
}
|
|
157
994
|
// packages/sql/src/dialects/postgres-index-keys.ts
|
|
158
|
-
import { unsupportedCapability as
|
|
995
|
+
import { unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
159
996
|
function renderPostgresIndexKeys(params) {
|
|
160
997
|
const { dialect } = params;
|
|
161
998
|
if (dialect.name !== "postgres")
|
|
162
|
-
throw
|
|
999
|
+
throw unsupportedCapability6({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
163
1000
|
const collector = new ParamCollector(dialect);
|
|
164
1001
|
const scope = params.table !== undefined ? `n.nspname = ${collector.add(params.table.namespace)} AND c.relname = ${collector.add(params.table.name)}` : params.namespaces === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : params.namespaces.length === 0 ? "false" : `n.nspname IN (${[...new Set(params.namespaces)].map((namespace) => collector.add(namespace)).join(", ")})`;
|
|
165
1002
|
const defaultResolution = `LEFT JOIN LATERAL (WITH RECURSIVE input_types AS (SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t WHERE t.oid = a.atttypid UNION ALL SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t JOIN input_types previous ON t.oid = previous.typbasetype WHERE previous.typtype = 'd'), candidates AS (SELECT candidate.oid, CASE WHEN candidate.opcintype = input.oid THEN 0 WHEN class_input.typispreferred AND class_input.typcategory = input.typcategory THEN 1 ELSE 2 END AS priority FROM pg_catalog.pg_opclass candidate JOIN pg_catalog.pg_type class_input ON class_input.oid = candidate.opcintype CROSS JOIN input_types input WHERE input.typtype <> 'd' AND candidate.opcdefault AND candidate.opcmethod = idx.relam AND (candidate.opcintype = input.oid OR EXISTS (SELECT 1 FROM pg_catalog.pg_cast cast_entry WHERE cast_entry.castsource = input.oid AND cast_entry.casttarget = candidate.opcintype AND cast_entry.castmethod = 'b' AND cast_entry.castcontext = 'i') OR input.typtype = 'e' AND candidate.opcintype = 'pg_catalog.anyenum'::regtype OR input.typelem <> 0 AND candidate.opcintype = 'pg_catalog.anyarray'::regtype)) SELECT (array_agg(candidate.oid))[1] AS oid FROM candidates candidate WHERE candidate.priority = (SELECT min(priority) FROM candidates) HAVING count(*) = 1) resolved_default ON true`;
|
|
@@ -170,18 +1007,18 @@ function renderPostgresIndexKeys(params) {
|
|
|
170
1007
|
};
|
|
171
1008
|
}
|
|
172
1009
|
// packages/sql/src/index-columns.ts
|
|
173
|
-
import { VibeError, isSqlObjectIdentity, unsupportedCapability as
|
|
1010
|
+
import { VibeError as VibeError6, isSqlObjectIdentity, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
174
1011
|
function renderIndexColumns(params) {
|
|
175
1012
|
const { dialect, columns, unique } = params;
|
|
176
1013
|
const hasOperatorClass = columns.some((column) => column.operatorClass !== undefined);
|
|
177
1014
|
if (hasOperatorClass && !dialect.capabilities.indexOperatorClasses)
|
|
178
|
-
throw
|
|
1015
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
179
1016
|
if (hasOperatorClass && unique)
|
|
180
|
-
throw
|
|
1017
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "uniqueIndexOperatorClasses", hint: "custom unique operator classes can change client selector equality" });
|
|
181
1018
|
return columns.map((column) => {
|
|
182
1019
|
const operatorClass = column.operatorClass;
|
|
183
1020
|
if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass })) {
|
|
184
|
-
throw new
|
|
1021
|
+
throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "Index operator class must be a qualified SQL name { namespace, name }", meta: { rule: "index-operator-class-identity", column: column.name } });
|
|
185
1022
|
}
|
|
186
1023
|
const ops = operatorClass === undefined ? "" : ` ${dialect.quoteIdent({ name: operatorClass.namespace })}.${dialect.quoteIdent({ name: operatorClass.name })}`;
|
|
187
1024
|
return `${dialect.quoteIdent({ name: column.name })}${ops}${column.desc ? " DESC" : ""}`;
|
|
@@ -233,7 +1070,7 @@ UNION ALL SELECT DISTINCT 'partition', TABLE_NAME, TABLE_SCHEMA, NULL FROM infor
|
|
|
233
1070
|
return { text: `SELECT 'table-options' AS kind, name, schema AS namespace, NULL AS parent FROM pragma_table_list WHERE schema = ${namespace} AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND (type = 'virtual' OR wr <> 0 OR strict <> 0)`, values: collector.values, returning: "none" };
|
|
234
1071
|
}
|
|
235
1072
|
// packages/sql/src/dialects/migration-shadow.ts
|
|
236
|
-
import { assertSqlNamespace, unsupportedCapability as
|
|
1073
|
+
import { assertSqlNamespace, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
237
1074
|
var SHADOW_DATABASE = Object.freeze({ schema: "_vibeorm_shadow", table: "_vibe_shadow", lock: 1447645267, timeoutSeconds: 60 });
|
|
238
1075
|
var SHADOW_CAPABILITIES = Object.freeze({
|
|
239
1076
|
postgres: Object.freeze({ serverProvisioning: true, dedicated: true }),
|
|
@@ -243,7 +1080,7 @@ var SHADOW_CAPABILITIES = Object.freeze({
|
|
|
243
1080
|
function renderShadowOperation(params) {
|
|
244
1081
|
const { dialect, operation } = params;
|
|
245
1082
|
if (!SHADOW_CAPABILITIES[dialect.name].serverProvisioning)
|
|
246
|
-
throw
|
|
1083
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "shadowDatabaseProvisioning" });
|
|
247
1084
|
const pg = dialect.name === "postgres";
|
|
248
1085
|
const collector = new ParamCollector(dialect);
|
|
249
1086
|
const marker = pg ? `${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}.${dialect.quoteIdent({ name: SHADOW_DATABASE.table })}` : dialect.quoteIdent({ name: SHADOW_DATABASE.table });
|
|
@@ -268,7 +1105,7 @@ function renderShadowOperation(params) {
|
|
|
268
1105
|
ORDER BY ordinal_position`);
|
|
269
1106
|
case "grants": {
|
|
270
1107
|
if (pg)
|
|
271
|
-
throw
|
|
1108
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
|
|
272
1109
|
return result("SHOW GRANTS FOR CURRENT_USER");
|
|
273
1110
|
}
|
|
274
1111
|
case "markerCreate":
|
|
@@ -299,7 +1136,7 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
|
|
|
299
1136
|
case "resetPostgres":
|
|
300
1137
|
case "resetDedicatedPostgres": {
|
|
301
1138
|
if (!pg)
|
|
302
|
-
throw
|
|
1139
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "postgresShadowReset" });
|
|
303
1140
|
const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
|
|
304
1141
|
for (const namespace of owned)
|
|
305
1142
|
assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
|
|
@@ -311,12 +1148,12 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
|
|
|
311
1148
|
}
|
|
312
1149
|
case "mysqlForeignKeys": {
|
|
313
1150
|
if (pg)
|
|
314
|
-
throw
|
|
1151
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
315
1152
|
return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
|
|
316
1153
|
}
|
|
317
1154
|
case "dropMysqlObject": {
|
|
318
1155
|
if (pg)
|
|
319
|
-
throw
|
|
1156
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
320
1157
|
return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
|
|
321
1158
|
}
|
|
322
1159
|
}
|
|
@@ -358,10 +1195,10 @@ import {
|
|
|
358
1195
|
} from "@vibeorm/schema";
|
|
359
1196
|
|
|
360
1197
|
// packages/sql/src/identifiers.ts
|
|
361
|
-
import { VibeError as
|
|
1198
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
362
1199
|
function assertNoNul(params) {
|
|
363
1200
|
if (params.name.includes("\x00")) {
|
|
364
|
-
throw new
|
|
1201
|
+
throw new VibeError7({
|
|
365
1202
|
code: "VIBE_VALIDATION",
|
|
366
1203
|
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
367
1204
|
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
@@ -402,800 +1239,449 @@ var sqliteDialect = {
|
|
|
402
1239
|
"array_ends_with"
|
|
403
1240
|
],
|
|
404
1241
|
jsonParamCast: "",
|
|
1242
|
+
valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "INTEGER", Int: "INTEGER", BigInt: "INTEGER", Float: "REAL", Decimal: "TEXT", DateTime: "TEXT", Json: "TEXT", Bytes: "BLOB" }),
|
|
405
1243
|
lastInsertIdSql: null,
|
|
406
1244
|
rowLockSyntax: null,
|
|
407
1245
|
advisoryLockFunctions: null,
|
|
408
|
-
rowDefaultKeyword: false,
|
|
409
|
-
statementChain: null,
|
|
410
|
-
paramBudget: 900,
|
|
411
|
-
booleanLiteral: { true: "1", false: "0" },
|
|
412
|
-
quoteIdent({ name }) {
|
|
413
|
-
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
414
|
-
},
|
|
415
|
-
quoteStringLiteral({ value }) {
|
|
416
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
417
|
-
},
|
|
418
|
-
placeholder() {
|
|
419
|
-
return "?";
|
|
420
|
-
},
|
|
421
|
-
renderDbNow({ offsetSeconds, collector }) {
|
|
422
|
-
if (offsetSeconds === 0)
|
|
423
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
424
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
429
|
-
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
430
|
-
function tableDefinitionParts(params) {
|
|
431
|
-
const sql = params.sql;
|
|
432
|
-
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
433
|
-
throw unavailable();
|
|
434
|
-
const parts = [];
|
|
435
|
-
let start = -1;
|
|
436
|
-
let depth = 0;
|
|
437
|
-
let quote;
|
|
438
|
-
for (let index = 0;index < sql.length; index++) {
|
|
439
|
-
const char = sql[index];
|
|
440
|
-
if (quote !== undefined) {
|
|
441
|
-
if (char === quote) {
|
|
442
|
-
if (quote !== "]" && sql[index + 1] === quote)
|
|
443
|
-
index++;
|
|
444
|
-
else
|
|
445
|
-
quote = undefined;
|
|
446
|
-
}
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
450
|
-
quote = char;
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
|
-
if (char === "[") {
|
|
454
|
-
quote = "]";
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
if (char === "-" && sql[index + 1] === "-") {
|
|
458
|
-
const end = sql.indexOf(`
|
|
459
|
-
`, index + 2);
|
|
460
|
-
if (end < 0)
|
|
461
|
-
break;
|
|
462
|
-
index = end;
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (char === "/" && sql[index + 1] === "*") {
|
|
466
|
-
const end = sql.indexOf("*/", index + 2);
|
|
467
|
-
if (end < 0)
|
|
468
|
-
break;
|
|
469
|
-
index = end + 1;
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
if (char === "(") {
|
|
473
|
-
if (depth === 0)
|
|
474
|
-
start = index + 1;
|
|
475
|
-
depth++;
|
|
476
|
-
} else if (char === ")") {
|
|
477
|
-
depth--;
|
|
478
|
-
if (depth === 0 && start >= 0) {
|
|
479
|
-
parts.push(sql.slice(start, index).trim());
|
|
480
|
-
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
481
|
-
}
|
|
482
|
-
} else if (char === "," && depth === 1) {
|
|
483
|
-
parts.push(sql.slice(start, index).trim());
|
|
484
|
-
start = index + 1;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
throw unavailable();
|
|
488
|
-
}
|
|
489
|
-
function unavailable() {
|
|
490
|
-
return new VibeError3({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
491
|
-
}
|
|
492
|
-
function sqliteColumnClauses(params) {
|
|
493
|
-
const columns = [];
|
|
494
|
-
for (const part of tableDefinitionParts(params).clauses) {
|
|
495
|
-
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
496
|
-
if (token === null)
|
|
497
|
-
throw unavailable();
|
|
498
|
-
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
499
|
-
continue;
|
|
500
|
-
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
501
|
-
columns.push({ name, sql: part });
|
|
502
|
-
}
|
|
503
|
-
return columns;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// packages/sql/src/dialects/sqlite-fts.ts
|
|
507
|
-
function updateStatements(params) {
|
|
508
|
-
const quote = (name) => sqliteDialect.quoteIdent({ name });
|
|
509
|
-
const columns = params.columns.map((column) => quote(column)).join(", ");
|
|
510
|
-
const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
511
|
-
const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
512
|
-
const changes = [
|
|
513
|
-
"old.rowid IS NOT new.rowid",
|
|
514
|
-
...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
|
|
515
|
-
].join(" OR ");
|
|
516
|
-
const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
|
|
517
|
-
const body = `BEGIN
|
|
518
|
-
INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
|
|
519
|
-
INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
|
|
520
|
-
END`;
|
|
521
|
-
const tail = `${target} WHEN (${changes}) ${body}`;
|
|
522
|
-
return {
|
|
523
|
-
create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
|
|
524
|
-
definition: `CREATE TRIGGER ${tail}`,
|
|
525
|
-
legacyDefinition: `CREATE TRIGGER ${target} ${body}`
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
function renderSqliteFtsUpdateTrigger(params) {
|
|
529
|
-
const { create, definition } = updateStatements(params);
|
|
530
|
-
return { create, definition };
|
|
531
|
-
}
|
|
532
|
-
function sqliteFtsUpdateUpgrade(params) {
|
|
533
|
-
const quoted = '"(?:[^"]|"")+"';
|
|
534
|
-
const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
|
|
535
|
-
for (const match of params.create.matchAll(body)) {
|
|
536
|
-
const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
|
|
537
|
-
const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
|
|
538
|
-
if (params.name !== `${ftsTable}_au`)
|
|
539
|
-
continue;
|
|
540
|
-
const statements = updateStatements({ table: params.table, ftsTable, columns });
|
|
541
|
-
if (params.create !== statements.create)
|
|
542
|
-
continue;
|
|
543
|
-
const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
|
|
544
|
-
if (installed === undefined)
|
|
545
|
-
continue;
|
|
546
|
-
if (params.tableDefinition === undefined)
|
|
547
|
-
return { kind: "tableRequired", ftsTable };
|
|
548
|
-
const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
|
|
549
|
-
if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
|
|
550
|
-
continue;
|
|
551
|
-
return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
|
|
552
|
-
}
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
// packages/sql/src/dialects/sqlite-session.ts
|
|
556
|
-
var SQLITE_SESSION_SQL = Object.freeze({
|
|
557
|
-
lockingMode: "PRAGMA main.locking_mode",
|
|
558
|
-
exclusive: "PRAGMA main.locking_mode = EXCLUSIVE",
|
|
559
|
-
normal: "PRAGMA main.locking_mode = NORMAL",
|
|
560
|
-
acquire: "BEGIN EXCLUSIVE",
|
|
561
|
-
commit: "COMMIT",
|
|
562
|
-
rollback: "ROLLBACK",
|
|
563
|
-
touch: "SELECT name FROM main.sqlite_schema LIMIT 1",
|
|
564
|
-
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
565
|
-
});
|
|
566
|
-
// packages/sql/src/composable.ts
|
|
567
|
-
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
568
|
-
var fragments = new WeakMap;
|
|
569
|
-
function invalid(message) {
|
|
570
|
-
throw new VibeError4({ code: "VIBE_VALIDATION", message });
|
|
571
|
-
}
|
|
572
|
-
function construct(parts) {
|
|
573
|
-
const snapshot = Object.freeze([...parts]);
|
|
574
|
-
const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
|
|
575
|
-
value: () => ({ parts: snapshot })
|
|
576
|
-
}));
|
|
577
|
-
fragments.set(fragment, snapshot);
|
|
578
|
-
return fragment;
|
|
579
|
-
}
|
|
580
|
-
function isSqlFragment(value) {
|
|
581
|
-
return typeof value === "object" && value !== null && fragments.has(value);
|
|
582
|
-
}
|
|
583
|
-
function partsOf(value) {
|
|
584
|
-
return fragments.get(value) ?? invalid("Expected a constructed SQL fragment");
|
|
585
|
-
}
|
|
586
|
-
function containsGreatest(fragment) {
|
|
587
|
-
return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
|
|
588
|
-
}
|
|
589
|
-
function identifier(parts) {
|
|
590
|
-
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
591
|
-
invalid("SQL identifiers require non-empty, NUL-free name components");
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
function template(strings, ...values) {
|
|
595
|
-
if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
|
|
596
|
-
invalid("Use sql as a template tag");
|
|
597
|
-
}
|
|
598
|
-
const parts = [];
|
|
599
|
-
for (let index = 0;index < strings.length; index++) {
|
|
600
|
-
parts.push(strings[index]);
|
|
601
|
-
if (index < values.length) {
|
|
602
|
-
const value = values[index];
|
|
603
|
-
if (isSqlFragment(value))
|
|
604
|
-
parts.push(...partsOf(value));
|
|
605
|
-
else
|
|
606
|
-
parts.push(Object.freeze({ value }));
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
return construct(parts);
|
|
610
|
-
}
|
|
611
|
-
var sql = Object.freeze(Object.assign(template, {
|
|
612
|
-
text: ({ text }) => {
|
|
613
|
-
if (typeof text !== "string" || text.includes("\x00"))
|
|
614
|
-
invalid("SQL text must be a NUL-free string");
|
|
615
|
-
return construct([text]);
|
|
616
|
-
},
|
|
617
|
-
ident: ({ parts }) => {
|
|
618
|
-
identifier(parts);
|
|
619
|
-
return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
|
|
620
|
-
},
|
|
621
|
-
value: ({ value, decoder }) => construct([Object.freeze({ value, ...decoder === undefined ? {} : { decoder } })]),
|
|
622
|
-
join: ({ items, separator }) => {
|
|
623
|
-
if (!Array.isArray(items))
|
|
624
|
-
invalid("sql.join requires an array");
|
|
625
|
-
const between = separator === undefined ? [", "] : partsOf(separator);
|
|
626
|
-
const parts = [];
|
|
627
|
-
items.forEach((value, index) => {
|
|
628
|
-
if (index > 0)
|
|
629
|
-
parts.push(...between);
|
|
630
|
-
parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value })]);
|
|
631
|
-
});
|
|
632
|
-
return construct(parts);
|
|
1246
|
+
rowDefaultKeyword: false,
|
|
1247
|
+
statementChain: null,
|
|
1248
|
+
paramBudget: 900,
|
|
1249
|
+
booleanLiteral: { true: "1", false: "0" },
|
|
1250
|
+
quoteIdent({ name }) {
|
|
1251
|
+
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
633
1252
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
return construct([Object.freeze({ row: "existing", column })]);
|
|
1253
|
+
quoteStringLiteral({ value }) {
|
|
1254
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
637
1255
|
},
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
return construct([Object.freeze({ row: "incoming", column })]);
|
|
1256
|
+
placeholder() {
|
|
1257
|
+
return "?";
|
|
641
1258
|
},
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1259
|
+
jsonTextProjection({ expression }) {
|
|
1260
|
+
return expression;
|
|
1261
|
+
},
|
|
1262
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
1263
|
+
if (offsetSeconds === 0)
|
|
1264
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1265
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
646
1266
|
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return collector.add(encode(part));
|
|
670
|
-
if ("ident" in part)
|
|
671
|
-
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
672
|
-
if ("row" in part) {
|
|
673
|
-
if (conflict === undefined)
|
|
674
|
-
invalid("Existing/incoming row references require a conflict update");
|
|
675
|
-
const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
|
|
676
|
-
if (name === undefined)
|
|
677
|
-
invalid(`Unknown conflict-expression field "${part.column}"`);
|
|
678
|
-
const ref = dialect.excludedRowRef;
|
|
679
|
-
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
680
|
-
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
1270
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
1271
|
+
function tableDefinitionParts(params) {
|
|
1272
|
+
const sql = params.sql;
|
|
1273
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
1274
|
+
throw unavailable();
|
|
1275
|
+
const parts = [];
|
|
1276
|
+
let start = -1;
|
|
1277
|
+
let depth = 0;
|
|
1278
|
+
let quote;
|
|
1279
|
+
for (let index = 0;index < sql.length; index++) {
|
|
1280
|
+
const char = sql[index];
|
|
1281
|
+
if (quote !== undefined) {
|
|
1282
|
+
if (char === quote) {
|
|
1283
|
+
if (quote !== "]" && sql[index + 1] === quote)
|
|
1284
|
+
index++;
|
|
1285
|
+
else
|
|
1286
|
+
quote = undefined;
|
|
1287
|
+
}
|
|
1288
|
+
continue;
|
|
681
1289
|
}
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
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)`));
|
|
1290
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
1291
|
+
quote = char;
|
|
1292
|
+
continue;
|
|
686
1293
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}).join("");
|
|
691
|
-
}
|
|
692
|
-
function mapConflictColumns(params) {
|
|
693
|
-
return construct(partsOf(params.fragment).map((part) => {
|
|
694
|
-
if (typeof part === "string")
|
|
695
|
-
return part;
|
|
696
|
-
if ("row" in part) {
|
|
697
|
-
const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
|
|
698
|
-
if (column === undefined)
|
|
699
|
-
invalid(`Unknown conflict-expression field "${part.column}"`);
|
|
700
|
-
return Object.freeze({ ...part, column });
|
|
1294
|
+
if (char === "[") {
|
|
1295
|
+
quote = "]";
|
|
1296
|
+
continue;
|
|
701
1297
|
}
|
|
702
|
-
if ("
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return PG_ARRAY_QUOTE.test(text) ? `"${text.replace(/["\\]/g, (char) => `\\${char}`)}"` : text;
|
|
727
|
-
});
|
|
728
|
-
return `{${parts.join(",")}}`;
|
|
729
|
-
}
|
|
730
|
-
function parsePgArrayLiteral(params) {
|
|
731
|
-
const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
|
|
732
|
-
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
733
|
-
return null;
|
|
734
|
-
const inner = literal.slice(1, -1);
|
|
735
|
-
if (inner.length === 0)
|
|
736
|
-
return [];
|
|
737
|
-
const out = [];
|
|
738
|
-
let index = 0;
|
|
739
|
-
while (index < inner.length) {
|
|
740
|
-
if (inner[index] === '"') {
|
|
741
|
-
let text = "";
|
|
742
|
-
index++;
|
|
743
|
-
while (index < inner.length) {
|
|
744
|
-
const char = inner[index];
|
|
745
|
-
if (char === "\\") {
|
|
746
|
-
text += inner[index + 1] ?? "";
|
|
747
|
-
index += 2;
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
index++;
|
|
751
|
-
if (char === '"')
|
|
752
|
-
break;
|
|
753
|
-
text += char;
|
|
754
|
-
}
|
|
755
|
-
out.push(text);
|
|
756
|
-
} else {
|
|
757
|
-
let text = "";
|
|
758
|
-
while (index < inner.length && inner[index] !== ",") {
|
|
759
|
-
text += inner[index];
|
|
760
|
-
index++;
|
|
1298
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
1299
|
+
const end = sql.indexOf(`
|
|
1300
|
+
`, index + 2);
|
|
1301
|
+
if (end < 0)
|
|
1302
|
+
break;
|
|
1303
|
+
index = end;
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
1307
|
+
const end = sql.indexOf("*/", index + 2);
|
|
1308
|
+
if (end < 0)
|
|
1309
|
+
break;
|
|
1310
|
+
index = end + 1;
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
if (char === "(") {
|
|
1314
|
+
if (depth === 0)
|
|
1315
|
+
start = index + 1;
|
|
1316
|
+
depth++;
|
|
1317
|
+
} else if (char === ")") {
|
|
1318
|
+
depth--;
|
|
1319
|
+
if (depth === 0 && start >= 0) {
|
|
1320
|
+
parts.push(sql.slice(start, index).trim());
|
|
1321
|
+
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
761
1322
|
}
|
|
762
|
-
|
|
1323
|
+
} else if (char === "," && depth === 1) {
|
|
1324
|
+
parts.push(sql.slice(start, index).trim());
|
|
1325
|
+
start = index + 1;
|
|
763
1326
|
}
|
|
764
|
-
if (inner[index] === ",")
|
|
765
|
-
index++;
|
|
766
1327
|
}
|
|
767
|
-
|
|
1328
|
+
throw unavailable();
|
|
768
1329
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
771
|
-
const cursor = { index: 0 };
|
|
772
|
-
const parsed = parseDimension({ text, cursor });
|
|
773
|
-
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
1330
|
+
function unavailable() {
|
|
1331
|
+
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
774
1332
|
}
|
|
775
|
-
function
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
cursor.index++;
|
|
783
|
-
return out;
|
|
784
|
-
}
|
|
785
|
-
while (cursor.index < text.length) {
|
|
786
|
-
if (text[cursor.index] === "{") {
|
|
787
|
-
const nested = parseDimension({ text, cursor });
|
|
788
|
-
if (nested === null)
|
|
789
|
-
return null;
|
|
790
|
-
out.push(nested);
|
|
791
|
-
} else if (text[cursor.index] === '"') {
|
|
792
|
-
let element = "";
|
|
793
|
-
cursor.index++;
|
|
794
|
-
while (cursor.index < text.length && text[cursor.index] !== '"') {
|
|
795
|
-
if (text[cursor.index] === "\\")
|
|
796
|
-
cursor.index++;
|
|
797
|
-
element += text[cursor.index] ?? "";
|
|
798
|
-
cursor.index++;
|
|
799
|
-
}
|
|
800
|
-
if (text[cursor.index] !== '"')
|
|
801
|
-
return null;
|
|
802
|
-
cursor.index++;
|
|
803
|
-
out.push(element);
|
|
804
|
-
} else {
|
|
805
|
-
let element = "";
|
|
806
|
-
while (cursor.index < text.length && text[cursor.index] !== "," && text[cursor.index] !== "}") {
|
|
807
|
-
element += text[cursor.index];
|
|
808
|
-
cursor.index++;
|
|
809
|
-
}
|
|
810
|
-
out.push(element === "NULL" ? null : element);
|
|
811
|
-
}
|
|
812
|
-
if (text[cursor.index] === ",") {
|
|
813
|
-
cursor.index++;
|
|
1333
|
+
function sqliteColumnClauses(params) {
|
|
1334
|
+
const columns = [];
|
|
1335
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
1336
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
1337
|
+
if (token === null)
|
|
1338
|
+
throw unavailable();
|
|
1339
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
814
1340
|
continue;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
return null;
|
|
818
|
-
cursor.index++;
|
|
819
|
-
return out;
|
|
1341
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
1342
|
+
columns.push({ name, sql: part });
|
|
820
1343
|
}
|
|
821
|
-
return
|
|
1344
|
+
return columns;
|
|
822
1345
|
}
|
|
823
1346
|
|
|
824
|
-
// packages/sql/src/
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
const
|
|
828
|
-
const
|
|
1347
|
+
// packages/sql/src/dialects/sqlite-fts.ts
|
|
1348
|
+
function updateStatements(params) {
|
|
1349
|
+
const quote = (name) => sqliteDialect.quoteIdent({ name });
|
|
1350
|
+
const columns = params.columns.map((column) => quote(column)).join(", ");
|
|
1351
|
+
const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
1352
|
+
const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
1353
|
+
const changes = [
|
|
1354
|
+
"old.rowid IS NOT new.rowid",
|
|
1355
|
+
...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
|
|
1356
|
+
].join(" OR ");
|
|
1357
|
+
const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
|
|
1358
|
+
const body = `BEGIN
|
|
1359
|
+
INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
|
|
1360
|
+
INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
|
|
1361
|
+
END`;
|
|
1362
|
+
const tail = `${target} WHEN (${changes}) ${body}`;
|
|
829
1363
|
return {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
throw new VibeError5({
|
|
834
|
-
code: "VIBE_VALIDATION",
|
|
835
|
-
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
836
|
-
meta: { model, field, enum: enumName }
|
|
837
|
-
});
|
|
838
|
-
},
|
|
839
|
-
decode: (value) => {
|
|
840
|
-
if (typeof value === "string" && members.has(value))
|
|
841
|
-
return value;
|
|
842
|
-
throw invalidStoredData({ model, field, enum: enumName, value });
|
|
843
|
-
}
|
|
1364
|
+
create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
|
|
1365
|
+
definition: `CREATE TRIGGER ${tail}`,
|
|
1366
|
+
legacyDefinition: `CREATE TRIGGER ${target} ${body}`
|
|
844
1367
|
};
|
|
845
1368
|
}
|
|
846
|
-
function
|
|
847
|
-
const {
|
|
848
|
-
|
|
849
|
-
return false;
|
|
850
|
-
if (scalarType === "DateTime")
|
|
851
|
-
return false;
|
|
852
|
-
if (scalarType === "Json")
|
|
853
|
-
return wire.json === "parsed";
|
|
854
|
-
if (scalarType === "Int" || scalarType === "Float")
|
|
855
|
-
return wire.numbers === "native";
|
|
856
|
-
return false;
|
|
1369
|
+
function renderSqliteFtsUpdateTrigger(params) {
|
|
1370
|
+
const { create, definition } = updateStatements(params);
|
|
1371
|
+
return { create, definition };
|
|
857
1372
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
};
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1373
|
+
function sqliteFtsUpdateUpgrade(params) {
|
|
1374
|
+
const quoted = '"(?:[^"]|"")+"';
|
|
1375
|
+
const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
|
|
1376
|
+
for (const match of params.create.matchAll(body)) {
|
|
1377
|
+
const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
|
|
1378
|
+
const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
|
|
1379
|
+
if (params.name !== `${ftsTable}_au`)
|
|
1380
|
+
continue;
|
|
1381
|
+
const statements = updateStatements({ table: params.table, ftsTable, columns });
|
|
1382
|
+
if (params.create !== statements.create)
|
|
1383
|
+
continue;
|
|
1384
|
+
const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
|
|
1385
|
+
if (installed === undefined)
|
|
1386
|
+
continue;
|
|
1387
|
+
if (params.tableDefinition === undefined)
|
|
1388
|
+
return { kind: "tableRequired", ftsTable };
|
|
1389
|
+
const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
|
|
1390
|
+
if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
|
|
1391
|
+
continue;
|
|
1392
|
+
return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
|
|
874
1393
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
// packages/sql/src/dialects/sqlite-session.ts
|
|
1397
|
+
var SQLITE_SESSION_SQL = Object.freeze({
|
|
1398
|
+
lockingMode: "PRAGMA main.locking_mode",
|
|
1399
|
+
exclusive: "PRAGMA main.locking_mode = EXCLUSIVE",
|
|
1400
|
+
normal: "PRAGMA main.locking_mode = NORMAL",
|
|
1401
|
+
acquire: "BEGIN EXCLUSIVE",
|
|
1402
|
+
commit: "COMMIT",
|
|
1403
|
+
rollback: "ROLLBACK",
|
|
1404
|
+
touch: "SELECT name FROM main.sqlite_schema LIMIT 1",
|
|
1405
|
+
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
1406
|
+
});
|
|
1407
|
+
// packages/sql/src/composable.ts
|
|
1408
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1409
|
+
var fragments = new WeakMap;
|
|
1410
|
+
function invalid3(message) {
|
|
1411
|
+
throw new VibeError9({ code: "VIBE_VALIDATION", message });
|
|
1412
|
+
}
|
|
1413
|
+
function construct(parts) {
|
|
1414
|
+
const snapshot = Object.freeze([...parts]);
|
|
1415
|
+
const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
|
|
1416
|
+
value: () => ({ parts: snapshot })
|
|
1417
|
+
}));
|
|
1418
|
+
fragments.set(fragment, snapshot);
|
|
1419
|
+
return fragment;
|
|
1420
|
+
}
|
|
1421
|
+
function isSqlFragment(value) {
|
|
1422
|
+
return typeof value === "object" && value !== null && fragments.has(value);
|
|
1423
|
+
}
|
|
1424
|
+
function partsOf(value) {
|
|
1425
|
+
return fragments.get(value) ?? invalid3("Expected a constructed SQL fragment");
|
|
1426
|
+
}
|
|
1427
|
+
function containsGreatest(fragment) {
|
|
1428
|
+
return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
|
|
1429
|
+
}
|
|
1430
|
+
function identifier(parts) {
|
|
1431
|
+
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
1432
|
+
invalid3("SQL identifiers require non-empty, NUL-free name components");
|
|
887
1433
|
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const trimmed = params.text.trim();
|
|
893
|
-
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
894
|
-
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
895
|
-
if (!Number.isNaN(asUtc.getTime()))
|
|
896
|
-
return asUtc;
|
|
1434
|
+
}
|
|
1435
|
+
function template(strings, ...values) {
|
|
1436
|
+
if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
|
|
1437
|
+
invalid3("Use sql as a template tag");
|
|
897
1438
|
}
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1439
|
+
const parts = [];
|
|
1440
|
+
for (let index = 0;index < strings.length; index++) {
|
|
1441
|
+
parts.push(strings[index]);
|
|
1442
|
+
if (index < values.length) {
|
|
1443
|
+
const value = values[index];
|
|
1444
|
+
if (isSqlFragment(value))
|
|
1445
|
+
parts.push(...partsOf(value));
|
|
1446
|
+
else
|
|
1447
|
+
parts.push(Object.freeze({ value: snapshotSqlValue(value) }));
|
|
1448
|
+
}
|
|
904
1449
|
}
|
|
905
|
-
|
|
906
|
-
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
1450
|
+
return construct(parts);
|
|
907
1451
|
}
|
|
908
|
-
var
|
|
909
|
-
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
return value;
|
|
1452
|
+
var sql = Object.freeze(Object.assign(template, {
|
|
1453
|
+
text: ({ text }) => {
|
|
1454
|
+
if (typeof text !== "string" || text.includes("\x00"))
|
|
1455
|
+
invalid3("SQL text must be a NUL-free string");
|
|
1456
|
+
return construct([text]);
|
|
914
1457
|
},
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return date;
|
|
919
|
-
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
var dateTextCodec = {
|
|
923
|
-
encode: (value) => {
|
|
924
|
-
dateNativeCodec.encode(value);
|
|
925
|
-
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
926
|
-
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
927
|
-
return date.toISOString();
|
|
928
|
-
throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
1458
|
+
ident: ({ parts }) => {
|
|
1459
|
+
identifier(parts);
|
|
1460
|
+
return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
|
|
929
1461
|
},
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
if (typeof value === "string")
|
|
944
|
-
return parseDateTimeText({ text: value }) ?? value;
|
|
945
|
-
return value;
|
|
1462
|
+
value: ({ value, decoder }) => construct([Object.freeze({ value: snapshotSqlValue(value), ...decoder === undefined ? {} : { decoder } })]),
|
|
1463
|
+
join: ({ items, separator }) => {
|
|
1464
|
+
if (!Array.isArray(items))
|
|
1465
|
+
invalid3("sql.join requires an array");
|
|
1466
|
+
const between = separator === undefined ? [", "] : partsOf(separator);
|
|
1467
|
+
const parts = [];
|
|
1468
|
+
items.forEach((value, index) => {
|
|
1469
|
+
if (index > 0)
|
|
1470
|
+
parts.push(...between);
|
|
1471
|
+
parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value: snapshotSqlValue(value) })]);
|
|
1472
|
+
});
|
|
1473
|
+
return construct(parts);
|
|
946
1474
|
},
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1475
|
+
existing: ({ column }) => {
|
|
1476
|
+
identifier([column]);
|
|
1477
|
+
return construct([Object.freeze({ row: "existing", column })]);
|
|
1478
|
+
},
|
|
1479
|
+
incoming: ({ column }) => {
|
|
1480
|
+
identifier([column]);
|
|
1481
|
+
return construct([Object.freeze({ row: "incoming", column })]);
|
|
1482
|
+
},
|
|
1483
|
+
greatest: ({ values }) => {
|
|
1484
|
+
if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
|
|
1485
|
+
invalid3("sql.greatest requires two SQL fragments");
|
|
1486
|
+
return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
|
|
1487
|
+
}
|
|
1488
|
+
}));
|
|
1489
|
+
function renderSqlFragment(params) {
|
|
1490
|
+
const { dialect, collector, conflict } = params;
|
|
1491
|
+
const render = (fragment) => renderSqlFragment({ ...params, fragment });
|
|
1492
|
+
const encode = (part) => {
|
|
1493
|
+
if (part.value === undefined)
|
|
1494
|
+
invalid3("SQL parameters cannot be undefined; use null explicitly");
|
|
1495
|
+
return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
|
|
1496
|
+
};
|
|
1497
|
+
const recording = isSqlBindingCollector({ collector });
|
|
1498
|
+
const recordValue = (part) => recordSqlBinding({
|
|
1499
|
+
collector,
|
|
1500
|
+
source: { kind: "literal", value: part.value },
|
|
1501
|
+
transport: part.decoder === undefined ? sqlParameterTransport({ value: part.value }) : { kind: "plain" },
|
|
1502
|
+
encode: ({ value, dialect: target }) => part.decoder === undefined ? value : part.decoder.encode({ value, dialect: target.name })
|
|
1503
|
+
});
|
|
1504
|
+
const frameOperand = (fragment) => {
|
|
1505
|
+
const parts = partsOf(fragment).filter((part) => part !== "");
|
|
1506
|
+
const only = parts.length === 1 ? parts[0] : undefined;
|
|
1507
|
+
if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
|
|
1508
|
+
if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
|
|
1509
|
+
throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
|
|
1510
|
+
if (only.value === null) {
|
|
1511
|
+
if (only.decoder !== undefined)
|
|
1512
|
+
recordSqlBindingCheck({ collector, check: () => {
|
|
1513
|
+
if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
|
|
1514
|
+
throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
|
|
1515
|
+
} });
|
|
1516
|
+
return "NULL";
|
|
976
1517
|
}
|
|
977
|
-
|
|
1518
|
+
const text = recordValue(only);
|
|
1519
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
1520
|
+
}
|
|
1521
|
+
const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
|
|
1522
|
+
if (value !== undefined && value.encoded === null)
|
|
1523
|
+
return "NULL";
|
|
1524
|
+
const text = value === undefined ? render(fragment) : collector.add(value.encoded);
|
|
1525
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
1526
|
+
};
|
|
1527
|
+
return partsOf(params.fragment).map((part) => {
|
|
1528
|
+
if (typeof part === "string")
|
|
1529
|
+
return part;
|
|
1530
|
+
if ("value" in part)
|
|
1531
|
+
return recording ? recordValue(part) : collector.add(encode(part));
|
|
1532
|
+
if ("ident" in part)
|
|
1533
|
+
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
1534
|
+
if ("row" in part) {
|
|
1535
|
+
if (conflict === undefined)
|
|
1536
|
+
invalid3("Existing/incoming row references require a conflict update");
|
|
1537
|
+
const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
|
|
1538
|
+
if (name === undefined)
|
|
1539
|
+
invalid3(`Unknown conflict-expression field "${part.column}"`);
|
|
1540
|
+
const ref = dialect.excludedRowRef;
|
|
1541
|
+
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
1542
|
+
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
978
1543
|
}
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1544
|
+
if (dialect.greatestForm === "native")
|
|
1545
|
+
return `GREATEST(${part.greatest.map(render).join(", ")})`;
|
|
1546
|
+
if (!part.greatest.some(containsGreatest)) {
|
|
1547
|
+
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)`));
|
|
1548
|
+
}
|
|
1549
|
+
const [left, right] = part.greatest.map(frameOperand);
|
|
1550
|
+
const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
|
|
1551
|
+
return `(SELECT CASE WHEN ${a} IS NULL THEN ${b} WHEN ${b} IS NULL THEN ${a} WHEN ${a} >= ${b} THEN ${a} ELSE ${b} END FROM (SELECT ${left} AS ${a}, ${right} AS ${b}) AS ${frame})`;
|
|
1552
|
+
}).join("");
|
|
986
1553
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
encode: (value) => JSON.stringify(value),
|
|
997
|
-
decode: (value) => {
|
|
998
|
-
if (typeof value !== "string")
|
|
999
|
-
return value;
|
|
1000
|
-
try {
|
|
1001
|
-
return JSON.parse(value);
|
|
1002
|
-
} catch (cause) {
|
|
1003
|
-
throw new VibeError5({
|
|
1004
|
-
code: "VIBE_INTERNAL",
|
|
1005
|
-
message: "a Json column returned text that is not valid JSON",
|
|
1006
|
-
meta: { value },
|
|
1007
|
-
cause
|
|
1008
|
-
});
|
|
1554
|
+
function mapConflictColumns(params) {
|
|
1555
|
+
return construct(partsOf(params.fragment).map((part) => {
|
|
1556
|
+
if (typeof part === "string")
|
|
1557
|
+
return part;
|
|
1558
|
+
if ("row" in part) {
|
|
1559
|
+
const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
|
|
1560
|
+
if (column === undefined)
|
|
1561
|
+
invalid3(`Unknown conflict-expression field "${part.column}"`);
|
|
1562
|
+
return Object.freeze({ ...part, column });
|
|
1009
1563
|
}
|
|
1010
|
-
|
|
1011
|
-
};
|
|
1012
|
-
|
|
1564
|
+
if ("greatest" in part)
|
|
1565
|
+
return Object.freeze({ greatest: Object.freeze(part.greatest.map((fragment) => mapConflictColumns({ ...params, fragment }))) });
|
|
1566
|
+
return part;
|
|
1567
|
+
}));
|
|
1568
|
+
}
|
|
1569
|
+
function compileSql(params) {
|
|
1570
|
+
const collector = new ParamCollector(params.dialect);
|
|
1571
|
+
const text = renderSqlFragment({ dialect: params.dialect, fragment: params.query, collector });
|
|
1572
|
+
return { text, values: collector.values };
|
|
1573
|
+
}
|
|
1574
|
+
function sqlUsesIncoming(params) {
|
|
1575
|
+
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
1576
|
+
}
|
|
1577
|
+
// packages/sql/src/query-plan.ts
|
|
1578
|
+
import { VibeError as VibeError10 } from "@vibeorm/schema";
|
|
1579
|
+
var queries = new WeakMap;
|
|
1580
|
+
var commands = new WeakMap;
|
|
1581
|
+
var reusableQueries = new WeakMap;
|
|
1582
|
+
var reusableCommands = new WeakMap;
|
|
1583
|
+
function invalid4(message) {
|
|
1584
|
+
throw new VibeError10({ code: "VIBE_VALIDATION", message });
|
|
1585
|
+
}
|
|
1586
|
+
function createSqlQuery(params) {
|
|
1587
|
+
if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
|
|
1588
|
+
invalid4("SQL queries require a renderer and result decoder");
|
|
1589
|
+
const query = Object.freeze(Object.create(null));
|
|
1590
|
+
queries.set(query, Object.freeze({ render: params.render, decoder: params.decoder }));
|
|
1591
|
+
return query;
|
|
1592
|
+
}
|
|
1593
|
+
function createSqlCommand(params) {
|
|
1594
|
+
if (typeof params.render !== "function")
|
|
1595
|
+
invalid4("SQL commands require a renderer");
|
|
1596
|
+
const command = Object.freeze(Object.create(null));
|
|
1597
|
+
commands.set(command, params.render);
|
|
1598
|
+
return command;
|
|
1599
|
+
}
|
|
1600
|
+
function isSqlQuery(value) {
|
|
1601
|
+
return typeof value === "object" && value !== null && queries.has(value);
|
|
1602
|
+
}
|
|
1603
|
+
function isSqlCommand(value) {
|
|
1604
|
+
return typeof value === "object" && value !== null && commands.has(value);
|
|
1605
|
+
}
|
|
1606
|
+
function isReusableSqlQuery(value) {
|
|
1607
|
+
return typeof value === "object" && value !== null && reusableQueries.has(value);
|
|
1608
|
+
}
|
|
1609
|
+
function isReusableSqlCommand(value) {
|
|
1610
|
+
return typeof value === "object" && value !== null && reusableCommands.has(value);
|
|
1611
|
+
}
|
|
1612
|
+
function createReusableSqlQuery(params) {
|
|
1613
|
+
const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
|
|
1614
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
1615
|
+
const query = Object.freeze(Object.create(null));
|
|
1616
|
+
reusableQueries.set(query, { ...state, scope: params.scope, cache: new WeakMap });
|
|
1617
|
+
return query;
|
|
1618
|
+
}
|
|
1619
|
+
function createReusableSqlCommand(params) {
|
|
1620
|
+
const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
|
|
1621
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
1622
|
+
const query = Object.freeze(Object.create(null));
|
|
1623
|
+
reusableCommands.set(query, { render, scope: params.scope, cache: new WeakMap });
|
|
1624
|
+
return query;
|
|
1625
|
+
}
|
|
1626
|
+
function compiledTemplate(params) {
|
|
1627
|
+
const cached = params.state.cache.get(params.dialect);
|
|
1628
|
+
if (cached !== undefined)
|
|
1629
|
+
return cached;
|
|
1630
|
+
const collector = new ParamCollector(params.dialect);
|
|
1631
|
+
startSqlBindingCollection({ collector, dialect: params.dialect, scope: params.state.scope });
|
|
1632
|
+
const text = params.state.render({ dialect: params.dialect, collector });
|
|
1633
|
+
if (collector.count() > params.dialect.paramBudget)
|
|
1634
|
+
invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1635
|
+
const program = finishSqlBindingCollection({ collector });
|
|
1636
|
+
const compiled = Object.freeze({ text, program });
|
|
1637
|
+
params.state.cache.set(params.dialect, compiled);
|
|
1638
|
+
return compiled;
|
|
1639
|
+
}
|
|
1640
|
+
function executionHandle(params) {
|
|
1013
1641
|
const { value } = params;
|
|
1014
|
-
|
|
1015
|
-
return value;
|
|
1016
|
-
const target = Math.fround(value);
|
|
1017
|
-
for (let precision = 1;precision <= 9; precision++) {
|
|
1018
|
-
const candidate = Number(value.toPrecision(precision));
|
|
1019
|
-
if (Math.fround(candidate) === target)
|
|
1020
|
-
return candidate;
|
|
1021
|
-
}
|
|
1022
|
-
return value;
|
|
1642
|
+
return queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) || isSqlFragment(value) || isSqlInput(value) || sqlInputScopeHandle(params);
|
|
1023
1643
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const number = typeof value === "string" ? Number(value) : value;
|
|
1028
|
-
return typeof number === "number" ? shortestFloat32({ value: number }) : number;
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
var booleanFromText = (text) => text === "t" ? true : text === "f" ? false : text;
|
|
1032
|
-
var bytesFromText = (text) => /^\\x(?:[0-9a-fA-F]{2})*$/.test(text) ? new Uint8Array(Buffer.from(text.slice(2), "hex")) : text;
|
|
1033
|
-
function postgresArrayElementFromText(params) {
|
|
1034
|
-
switch (params.scalarType) {
|
|
1035
|
-
case "Boolean":
|
|
1036
|
-
return booleanFromText;
|
|
1037
|
-
case "Bytes":
|
|
1038
|
-
return bytesFromText;
|
|
1039
|
-
case "Json":
|
|
1040
|
-
return jsonCodec.decode;
|
|
1041
|
-
default:
|
|
1042
|
-
return null;
|
|
1043
|
-
}
|
|
1644
|
+
function bindTemplate(params) {
|
|
1645
|
+
const { scope } = params.state;
|
|
1646
|
+
return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
|
|
1044
1647
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
BigInt: bigIntCodec,
|
|
1050
|
-
Float: numberCodec,
|
|
1051
|
-
Decimal: decimalCodec,
|
|
1052
|
-
DateTime: dateNativeCodec,
|
|
1053
|
-
Json: jsonCodec,
|
|
1054
|
-
Bytes: passthroughCodec
|
|
1055
|
-
};
|
|
1056
|
-
var SQLITE_CODECS = {
|
|
1057
|
-
String: passthroughCodec,
|
|
1058
|
-
Boolean: booleanIntCodec,
|
|
1059
|
-
Int: numberCodec,
|
|
1060
|
-
BigInt: bigIntCodec,
|
|
1061
|
-
Float: numberCodec,
|
|
1062
|
-
Decimal: decimalCodec,
|
|
1063
|
-
DateTime: dateTextCodec,
|
|
1064
|
-
Json: jsonCodec,
|
|
1065
|
-
Bytes: passthroughCodec
|
|
1066
|
-
};
|
|
1067
|
-
var MYSQL_CODECS = {
|
|
1068
|
-
String: passthroughCodec,
|
|
1069
|
-
Boolean: booleanNativeInCodec,
|
|
1070
|
-
Int: numberCodec,
|
|
1071
|
-
BigInt: bigIntCodec,
|
|
1072
|
-
Float: numberCodec,
|
|
1073
|
-
Decimal: mysqlDecimalCodec,
|
|
1074
|
-
DateTime: mysqlDateCodec,
|
|
1075
|
-
Json: jsonCodec,
|
|
1076
|
-
Bytes: passthroughCodec
|
|
1077
|
-
};
|
|
1078
|
-
var DIALECT_CODECS = {
|
|
1079
|
-
postgres: POSTGRES_CODECS,
|
|
1080
|
-
sqlite: SQLITE_CODECS,
|
|
1081
|
-
mysql: MYSQL_CODECS
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
// packages/sql/src/result-decoder.ts
|
|
1085
|
-
function refuse(encoding, message) {
|
|
1086
|
-
throw new VibeError6({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
1648
|
+
function compileReusableSqlQuery(params) {
|
|
1649
|
+
const state = reusableQueries.get(params.query) ?? invalid4("Expected an authentic reusable SQL row query");
|
|
1650
|
+
const compiled = compiledTemplate({ dialect: params.dialect, state });
|
|
1651
|
+
return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }), decoder: state.decoder };
|
|
1087
1652
|
}
|
|
1088
|
-
function
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
};
|
|
1109
|
-
return Object.freeze({
|
|
1110
|
-
decode: (params) => convert(params, false),
|
|
1111
|
-
encode: (params) => {
|
|
1112
|
-
const value = type === "Json" ? params.value : convert(params, true);
|
|
1113
|
-
if (value === undefined || value === null)
|
|
1114
|
-
refuse(true, `A non-null ${type} parameter cannot be null or undefined`);
|
|
1115
|
-
return DIALECT_CODECS[params.dialect][type].encode(value);
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1653
|
+
function compileReusableSqlCommand(params) {
|
|
1654
|
+
const state = reusableCommands.get(params.query) ?? invalid4("Expected an authentic reusable SQL command");
|
|
1655
|
+
const compiled = compiledTemplate({ dialect: params.dialect, state });
|
|
1656
|
+
return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }) };
|
|
1657
|
+
}
|
|
1658
|
+
function compileSqlQuery(params) {
|
|
1659
|
+
const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
|
|
1660
|
+
const collector = new ParamCollector(params.dialect);
|
|
1661
|
+
const text = state.render({ dialect: params.dialect, collector });
|
|
1662
|
+
if (collector.values.length > params.dialect.paramBudget)
|
|
1663
|
+
invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1664
|
+
return { text, values: collector.values, decoder: state.decoder };
|
|
1665
|
+
}
|
|
1666
|
+
function compileSqlCommand(params) {
|
|
1667
|
+
const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
|
|
1668
|
+
const collector = new ParamCollector(params.dialect);
|
|
1669
|
+
const text = render({ dialect: params.dialect, collector });
|
|
1670
|
+
if (collector.values.length > params.dialect.paramBudget)
|
|
1671
|
+
invalid4("SQL command exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1672
|
+
return { text, values: collector.values };
|
|
1118
1673
|
}
|
|
1119
|
-
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
1120
|
-
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
1121
|
-
var extendedDecoder = Object.freeze({
|
|
1122
|
-
decode: (params) => {
|
|
1123
|
-
if (params.value === "infinity" || params.value === Infinity)
|
|
1124
|
-
return "infinity";
|
|
1125
|
-
if (params.value === "-infinity" || params.value === -Infinity)
|
|
1126
|
-
return "-infinity";
|
|
1127
|
-
return dateDecoder.decode(params);
|
|
1128
|
-
},
|
|
1129
|
-
encode: (params) => {
|
|
1130
|
-
if (params.value === "infinity" || params.value === "-infinity") {
|
|
1131
|
-
if (params.dialect !== "postgres")
|
|
1132
|
-
throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
1133
|
-
return params.value;
|
|
1134
|
-
}
|
|
1135
|
-
return dateDecoder.encode(params);
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
|
-
var decode = Object.freeze({
|
|
1139
|
-
string: stringDecoder,
|
|
1140
|
-
boolean: scalar("Boolean", (value) => typeof value === "boolean"),
|
|
1141
|
-
int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
|
|
1142
|
-
float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
|
|
1143
|
-
bigInt: scalar("BigInt", (value) => typeof value === "bigint"),
|
|
1144
|
-
decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
|
|
1145
|
-
dateTime: dateDecoder,
|
|
1146
|
-
extendedDateTime: extendedDecoder,
|
|
1147
|
-
json: scalar("Json", (value) => value !== undefined),
|
|
1148
|
-
bytes: scalar("Bytes", (value) => value instanceof Uint8Array),
|
|
1149
|
-
nullable: ({ decoder }) => Object.freeze({
|
|
1150
|
-
decode: (params) => params.value === null ? null : decoder.decode(params),
|
|
1151
|
-
encode: (params) => params.value === null ? null : decoder.encode(params)
|
|
1152
|
-
}),
|
|
1153
|
-
array: ({ decoder }) => Object.freeze({
|
|
1154
|
-
decode: (params) => {
|
|
1155
|
-
const raw = params.value;
|
|
1156
|
-
const value = params.dialect === "postgres" && typeof raw === "string" ? parsePgArrayLiteral({ literal: raw }) : ArrayBuffer.isView(raw) && ("length" in raw) ? Array.from(raw) : raw;
|
|
1157
|
-
if (!Array.isArray(value))
|
|
1158
|
-
refuse(false, "Expected an array projection");
|
|
1159
|
-
return value.map((element) => decoder.decode({ ...params, value: element }));
|
|
1160
|
-
},
|
|
1161
|
-
encode: (params) => {
|
|
1162
|
-
if (!Array.isArray(params.value))
|
|
1163
|
-
refuse(true, "Expected an array parameter");
|
|
1164
|
-
return params.value.map((value) => decoder.encode({ ...params, value }));
|
|
1165
|
-
}
|
|
1166
|
-
}),
|
|
1167
|
-
enum: ({ values }) => {
|
|
1168
|
-
const members = new Set(values);
|
|
1169
|
-
return Object.freeze({
|
|
1170
|
-
decode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(false, "Unknown enum projection member"),
|
|
1171
|
-
encode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(true, "Unknown enum parameter member")
|
|
1172
|
-
});
|
|
1173
|
-
},
|
|
1174
|
-
object: ({ fields }) => {
|
|
1175
|
-
const entries = Object.entries(fields);
|
|
1176
|
-
const convert = (params, encoding) => {
|
|
1177
|
-
if (typeof params.value !== "object" || params.value === null || Array.isArray(params.value))
|
|
1178
|
-
refuse(encoding, "Expected a result row object");
|
|
1179
|
-
const row = params.value;
|
|
1180
|
-
return Object.fromEntries(entries.map(([name, decoder]) => {
|
|
1181
|
-
if (!Object.hasOwn(row, name))
|
|
1182
|
-
refuse(encoding, `Missing projection column "${name}"`);
|
|
1183
|
-
return [name, encoding ? decoder.encode({ ...params, value: row[name] }) : decoder.decode({ ...params, value: row[name] })];
|
|
1184
|
-
}));
|
|
1185
|
-
};
|
|
1186
|
-
return Object.freeze({ decode: (params) => convert(params, false), encode: (params) => convert(params, true) });
|
|
1187
|
-
}
|
|
1188
|
-
});
|
|
1189
1674
|
// packages/sql/src/exclusion.ts
|
|
1190
|
-
import { VibeError as
|
|
1675
|
+
import { VibeError as VibeError12, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
1191
1676
|
|
|
1192
1677
|
// packages/sql/src/predicate.ts
|
|
1193
|
-
import { VibeError as
|
|
1678
|
+
import { VibeError as VibeError11, assertPredicateCapabilities, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
1194
1679
|
function renderPredicate(params) {
|
|
1680
|
+
assertPredicateCapabilities({ predicate: params.predicate, dialect: params.dialect.name, usage: params.usage });
|
|
1195
1681
|
if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
|
|
1196
|
-
throw
|
|
1682
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
1197
1683
|
if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
|
|
1198
|
-
throw
|
|
1684
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "partialIndexes" });
|
|
1199
1685
|
}
|
|
1200
1686
|
const context = {
|
|
1201
1687
|
model: params.model,
|
|
@@ -1212,7 +1698,8 @@ var PRECEDENCE = {
|
|
|
1212
1698
|
not: 3,
|
|
1213
1699
|
compare: 4,
|
|
1214
1700
|
inList: 4,
|
|
1215
|
-
isNull: 4
|
|
1701
|
+
isNull: 4,
|
|
1702
|
+
isTrue: 4
|
|
1216
1703
|
};
|
|
1217
1704
|
var COMPARISON_SQL = {
|
|
1218
1705
|
eq: "=",
|
|
@@ -1230,7 +1717,7 @@ var INT64_MIN = -(2n ** 63n);
|
|
|
1230
1717
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
1231
1718
|
function refuse2(params) {
|
|
1232
1719
|
const { context, rule, message } = params;
|
|
1233
|
-
throw new
|
|
1720
|
+
throw new VibeError11({
|
|
1234
1721
|
code: "VIBE_SCHEMA_INVALID",
|
|
1235
1722
|
message: `${context.site} on "${context.model.name}": ${message}`,
|
|
1236
1723
|
meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
|
|
@@ -1263,14 +1750,16 @@ function renderNode(params) {
|
|
|
1263
1750
|
}
|
|
1264
1751
|
case "not":
|
|
1265
1752
|
return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
|
|
1753
|
+
case "isTrue":
|
|
1754
|
+
return `COALESCE(${renderNode({ node: node.operand, context })}, FALSE)`;
|
|
1266
1755
|
case "isNull": {
|
|
1267
|
-
const field = resolveField({ name: node.field.name, context });
|
|
1756
|
+
const field = resolveField({ name: node.field.name, context, nullTest: true });
|
|
1268
1757
|
return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
|
|
1269
1758
|
}
|
|
1270
1759
|
case "compare": {
|
|
1271
|
-
const left =
|
|
1272
|
-
const right = renderOperand({ operand: node.right, field: left, context });
|
|
1273
|
-
return `${
|
|
1760
|
+
const left = renderExpression({ expression: node.left, context });
|
|
1761
|
+
const right = renderOperand({ operand: node.right, field: left.field, context });
|
|
1762
|
+
return `${left.text} ${COMPARISON_SQL[node.op]} ${right}`;
|
|
1274
1763
|
}
|
|
1275
1764
|
case "inList": {
|
|
1276
1765
|
if (node.values.length === 0) {
|
|
@@ -1314,7 +1803,7 @@ function resolveField(params) {
|
|
|
1314
1803
|
meta: { field: name }
|
|
1315
1804
|
});
|
|
1316
1805
|
}
|
|
1317
|
-
if (field.type === "Json" || field.type === "Bytes") {
|
|
1806
|
+
if (params.nullTest !== true && (field.type === "Json" || field.type === "Bytes")) {
|
|
1318
1807
|
refuse2({
|
|
1319
1808
|
context,
|
|
1320
1809
|
rule: "predicate-unsupported-type",
|
|
@@ -1323,7 +1812,7 @@ function resolveField(params) {
|
|
|
1323
1812
|
});
|
|
1324
1813
|
}
|
|
1325
1814
|
if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
|
|
1326
|
-
throw
|
|
1815
|
+
throw unsupportedCapability9({
|
|
1327
1816
|
dialect: "mysql",
|
|
1328
1817
|
feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
|
|
1329
1818
|
hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
|
|
@@ -1340,22 +1829,43 @@ function compareColumn(params) {
|
|
|
1340
1829
|
const quoted = quoteColumn({ field, context });
|
|
1341
1830
|
return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
|
|
1342
1831
|
}
|
|
1832
|
+
function renderExpression(params) {
|
|
1833
|
+
const { expression, context } = params;
|
|
1834
|
+
if (expression.kind === "field") {
|
|
1835
|
+
const field = resolveField({ name: expression.name, context });
|
|
1836
|
+
return { text: compareColumn({ field, context }), field };
|
|
1837
|
+
}
|
|
1838
|
+
if (expression.kind === "jsonText") {
|
|
1839
|
+
const field = resolveField({ name: expression.field.name, context, nullTest: true });
|
|
1840
|
+
if (field.type !== "Json")
|
|
1841
|
+
refuse2({ context, rule: "predicate-type-mismatch", message: `jsonText requires a scalar Json field, not "${field.name}"` });
|
|
1842
|
+
if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00"))) {
|
|
1843
|
+
refuse2({ context, rule: "predicate-json-path", message: "jsonText requires a string-only path without NUL bytes" });
|
|
1844
|
+
}
|
|
1845
|
+
const path = expression.path.map((value) => context.dialect.quoteStringLiteral({ value })).join(", ");
|
|
1846
|
+
return { text: `(${quoteColumn({ field, context })} #>> ARRAY[${path}]::text[])`, field: { ...field, type: "String" } };
|
|
1847
|
+
}
|
|
1848
|
+
const inner = renderExpression({ expression: expression.expression, context });
|
|
1849
|
+
if (inner.field.type !== "String")
|
|
1850
|
+
refuse2({ context, rule: "predicate-type-mismatch", message: "length requires a text expression" });
|
|
1851
|
+
return { text: `length(${inner.text})`, field: { ...inner.field, type: "Int" } };
|
|
1852
|
+
}
|
|
1343
1853
|
function renderOperand(params) {
|
|
1344
1854
|
const { operand, field, context } = params;
|
|
1345
1855
|
if (operand.kind === "value")
|
|
1346
1856
|
return renderLiteral({ value: operand, field, context });
|
|
1347
|
-
const other =
|
|
1857
|
+
const other = renderExpression({ expression: operand, context });
|
|
1348
1858
|
const leftType = fieldTypeIdentity({ type: field.type });
|
|
1349
|
-
const rightType = fieldTypeIdentity({ type: other.type });
|
|
1859
|
+
const rightType = fieldTypeIdentity({ type: other.field.type });
|
|
1350
1860
|
if (leftType !== rightType) {
|
|
1351
1861
|
refuse2({
|
|
1352
1862
|
context,
|
|
1353
1863
|
rule: "predicate-type-mismatch",
|
|
1354
|
-
message: `compares "${field.name}" (${leftType}) with "${other.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
|
|
1355
|
-
meta: { field: field.name, other: other.name, fieldType: leftType, otherType: rightType }
|
|
1864
|
+
message: `compares "${field.name}" (${leftType}) with "${other.field.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
|
|
1865
|
+
meta: { field: field.name, other: other.field.name, fieldType: leftType, otherType: rightType }
|
|
1356
1866
|
});
|
|
1357
1867
|
}
|
|
1358
|
-
return
|
|
1868
|
+
return other.text;
|
|
1359
1869
|
}
|
|
1360
1870
|
function renderLiteral(params) {
|
|
1361
1871
|
const { field, context } = params;
|
|
@@ -1493,23 +2003,23 @@ function describe(params) {
|
|
|
1493
2003
|
}
|
|
1494
2004
|
|
|
1495
2005
|
// packages/sql/src/exclusion.ts
|
|
1496
|
-
function
|
|
1497
|
-
throw new
|
|
2006
|
+
function invalid5(rule) {
|
|
2007
|
+
throw new VibeError12({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
1498
2008
|
}
|
|
1499
2009
|
function compileExclusion(params) {
|
|
1500
2010
|
const { exclusion: spec, model, dialect } = params;
|
|
1501
2011
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1502
|
-
throw
|
|
2012
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1503
2013
|
if (!spec.name || spec.name.includes("\x00"))
|
|
1504
|
-
|
|
2014
|
+
invalid5("name");
|
|
1505
2015
|
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
1506
|
-
|
|
2016
|
+
invalid5("range-type");
|
|
1507
2017
|
if (!["[)", "[]", "()", "(]"].includes(spec.range.bounds))
|
|
1508
|
-
|
|
2018
|
+
invalid5("bounds");
|
|
1509
2019
|
const field = (name) => {
|
|
1510
2020
|
const value = model.fields.find((entry) => entry.name === name);
|
|
1511
2021
|
if (value === undefined || value.isList || value.isUnsupported)
|
|
1512
|
-
|
|
2022
|
+
invalid5("field");
|
|
1513
2023
|
return value;
|
|
1514
2024
|
};
|
|
1515
2025
|
const endpoint = (value) => {
|
|
@@ -1517,17 +2027,17 @@ function compileExclusion(params) {
|
|
|
1517
2027
|
return "NULL";
|
|
1518
2028
|
const infinity = "infinity" in value ? value.infinity : value.nullAs;
|
|
1519
2029
|
if (infinity !== undefined && (spec.range.type === "int8range" || !["infinity", "-infinity"].includes(infinity)))
|
|
1520
|
-
|
|
2030
|
+
invalid5("infinity");
|
|
1521
2031
|
const literal = infinity === undefined ? undefined : `${dialect.quoteStringLiteral({ value: infinity })}::${spec.range.type === "tsrange" ? "timestamp" : "timestamptz"}`;
|
|
1522
2032
|
if ("infinity" in value)
|
|
1523
2033
|
return literal;
|
|
1524
2034
|
const column = field(value.field);
|
|
1525
2035
|
if (fieldTypeIdentity2({ type: column.type }) !== (spec.range.type === "int8range" ? "BigInt" : "DateTime"))
|
|
1526
|
-
|
|
2036
|
+
invalid5("range-field-type");
|
|
1527
2037
|
if (column.type === "DateTime") {
|
|
1528
2038
|
const timezone = /timestamptz|timestamp.*with time zone/i.test(column.nativeType ?? "timestamptz");
|
|
1529
2039
|
if (timezone !== (spec.range.type === "tstzrange"))
|
|
1530
|
-
|
|
2040
|
+
invalid5("range-timezone");
|
|
1531
2041
|
}
|
|
1532
2042
|
const sql = dialect.quoteIdent({ name: column.dbName ?? column.name });
|
|
1533
2043
|
return literal === undefined ? sql : `COALESCE(${sql}, ${literal})`;
|
|
@@ -1535,15 +2045,15 @@ function compileExclusion(params) {
|
|
|
1535
2045
|
const keys = spec.keys.map((key) => {
|
|
1536
2046
|
if ("expression" in key) {
|
|
1537
2047
|
if (!key.expression.trim() || key.expression.includes("\x00") || key.fields.length === 0)
|
|
1538
|
-
|
|
2048
|
+
invalid5("expression");
|
|
1539
2049
|
key.fields.forEach(field);
|
|
1540
2050
|
return `(${key.expression}) WITH =`;
|
|
1541
2051
|
}
|
|
1542
2052
|
const column = field(key.field);
|
|
1543
2053
|
if (column.isOptional)
|
|
1544
|
-
|
|
2054
|
+
invalid5("nullable-key-requires-expression");
|
|
1545
2055
|
if (!isEnumType2(column.type) && !["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
|
|
1546
|
-
|
|
2056
|
+
invalid5("key-type");
|
|
1547
2057
|
return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
|
|
1548
2058
|
});
|
|
1549
2059
|
keys.push(`${spec.range.type}(${endpoint(spec.range.lower)}, ${endpoint(spec.range.upper)}, ${dialect.quoteStringLiteral({ value: spec.range.bounds })}) WITH &&`);
|
|
@@ -1552,13 +2062,13 @@ function compileExclusion(params) {
|
|
|
1552
2062
|
}
|
|
1553
2063
|
function renderExclusionSetup(params) {
|
|
1554
2064
|
if (!params.dialect.capabilities.exclusionConstraints)
|
|
1555
|
-
throw
|
|
2065
|
+
throw unsupportedCapability10({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
1556
2066
|
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
1557
2067
|
}
|
|
1558
2068
|
function renderExclusionDdl(params) {
|
|
1559
2069
|
const { dialect, table, exclusion, operation } = params;
|
|
1560
2070
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1561
|
-
throw
|
|
2071
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1562
2072
|
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
1563
2073
|
if (operation === "drop")
|
|
1564
2074
|
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
@@ -1567,14 +2077,14 @@ function renderExclusionDdl(params) {
|
|
|
1567
2077
|
function renderExclusionCatalog(params) {
|
|
1568
2078
|
const { dialect, probe } = params;
|
|
1569
2079
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1570
|
-
throw
|
|
2080
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1571
2081
|
const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
1572
2082
|
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`;
|
|
1573
2083
|
}
|
|
1574
2084
|
function renderExclusionProbe(params) {
|
|
1575
2085
|
const { dialect, table, probe, definition } = params;
|
|
1576
2086
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1577
|
-
throw
|
|
2087
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1578
2088
|
return [`CREATE TEMP TABLE ${dialect.quoteIdent({ name: probe })} (LIKE ${dialect.quoteIdent({ name: params.namespace ?? "public" })}.${dialect.quoteIdent({ name: table })})`, `ALTER TABLE ${dialect.quoteIdent({ name: probe })} ADD CONSTRAINT ${dialect.quoteIdent({ name: `${probe}_x` })} ${definition}`];
|
|
1579
2089
|
}
|
|
1580
2090
|
// packages/sql/src/ast.ts
|
|
@@ -1653,7 +2163,7 @@ function chainVetoToken(params) {
|
|
|
1653
2163
|
return;
|
|
1654
2164
|
}
|
|
1655
2165
|
// packages/sql/src/locks.ts
|
|
1656
|
-
import { internalError, unsupportedCapability as
|
|
2166
|
+
import { internalError, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
1657
2167
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
1658
2168
|
strengths: Object.freeze({
|
|
1659
2169
|
update: "FOR UPDATE",
|
|
@@ -1673,7 +2183,7 @@ function renderRowLockClause(params) {
|
|
|
1673
2183
|
if (syntax === null) {
|
|
1674
2184
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
1675
2185
|
return null;
|
|
1676
|
-
throw
|
|
2186
|
+
throw unsupportedCapability11({
|
|
1677
2187
|
dialect: dialect.name,
|
|
1678
2188
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
1679
2189
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -1681,7 +2191,7 @@ function renderRowLockClause(params) {
|
|
|
1681
2191
|
}
|
|
1682
2192
|
const strengthText = syntax.strengths[lock.strength];
|
|
1683
2193
|
if (strengthText === undefined) {
|
|
1684
|
-
throw
|
|
2194
|
+
throw unsupportedCapability11({
|
|
1685
2195
|
dialect: dialect.name,
|
|
1686
2196
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
1687
2197
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -1689,7 +2199,7 @@ function renderRowLockClause(params) {
|
|
|
1689
2199
|
}
|
|
1690
2200
|
const waitText = syntax.wait[lock.wait];
|
|
1691
2201
|
if (waitText === undefined) {
|
|
1692
|
-
throw
|
|
2202
|
+
throw unsupportedCapability11({
|
|
1693
2203
|
dialect: dialect.name,
|
|
1694
2204
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
1695
2205
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -1708,7 +2218,7 @@ function renderAdvisoryLock(params) {
|
|
|
1708
2218
|
const { dialect, statement, collector } = params;
|
|
1709
2219
|
const functions = dialect.advisoryLockFunctions;
|
|
1710
2220
|
if (functions === null) {
|
|
1711
|
-
throw
|
|
2221
|
+
throw unsupportedCapability11({
|
|
1712
2222
|
dialect: dialect.name,
|
|
1713
2223
|
feature: "advisory locks",
|
|
1714
2224
|
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`
|
|
@@ -1739,7 +2249,7 @@ function assertInt32(params) {
|
|
|
1739
2249
|
var JSON_TRANSPORT = Object.freeze({ chunkSize: 100 });
|
|
1740
2250
|
function projectionOutputName(params) {
|
|
1741
2251
|
const { column } = params;
|
|
1742
|
-
return column.transform === undefined ? column.alias ?? column.name : column.name;
|
|
2252
|
+
return column.transform === undefined || column.transform === "jsonText" ? column.alias ?? column.name : column.name;
|
|
1743
2253
|
}
|
|
1744
2254
|
// packages/sql/src/dialects/postgres.ts
|
|
1745
2255
|
var postgresDialect = {
|
|
@@ -1773,6 +2283,7 @@ var postgresDialect = {
|
|
|
1773
2283
|
"array_ends_with"
|
|
1774
2284
|
],
|
|
1775
2285
|
jsonParamCast: "::text::jsonb",
|
|
2286
|
+
valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "BOOLEAN", Int: "INTEGER", BigInt: "BIGINT", Float: "DOUBLE PRECISION", Decimal: "NUMERIC", DateTime: "TIMESTAMPTZ", Json: "JSONB", Bytes: "BYTEA" }),
|
|
1776
2287
|
lastInsertIdSql: null,
|
|
1777
2288
|
rowLockSyntax: {
|
|
1778
2289
|
strengths: Object.freeze({
|
|
@@ -1807,6 +2318,9 @@ var postgresDialect = {
|
|
|
1807
2318
|
placeholder({ index }) {
|
|
1808
2319
|
return `$${index}`;
|
|
1809
2320
|
},
|
|
2321
|
+
jsonTextProjection({ expression }) {
|
|
2322
|
+
return `${expression}::text`;
|
|
2323
|
+
},
|
|
1810
2324
|
renderDbNow({ offsetSeconds, collector }) {
|
|
1811
2325
|
if (offsetSeconds === 0)
|
|
1812
2326
|
return "now()";
|
|
@@ -1846,6 +2360,7 @@ var mysqlDialect = {
|
|
|
1846
2360
|
"array_ends_with"
|
|
1847
2361
|
],
|
|
1848
2362
|
jsonParamCast: "",
|
|
2363
|
+
valueCastTypes: Object.freeze({ String: "CHAR", Boolean: "UNSIGNED", Int: "SIGNED", BigInt: "SIGNED", Float: "DOUBLE", Decimal: "DECIMAL(65, 30)", DateTime: "DATETIME(3)", Json: "JSON", Bytes: "BINARY" }),
|
|
1849
2364
|
lastInsertIdSql: "SELECT LAST_INSERT_ID() AS `id`",
|
|
1850
2365
|
rowLockSyntax: {
|
|
1851
2366
|
strengths: Object.freeze({ update: "FOR UPDATE", share: "FOR SHARE" }),
|
|
@@ -1865,6 +2380,9 @@ var mysqlDialect = {
|
|
|
1865
2380
|
placeholder() {
|
|
1866
2381
|
return "?";
|
|
1867
2382
|
},
|
|
2383
|
+
jsonTextProjection({ expression }) {
|
|
2384
|
+
return `CAST(${expression} AS CHAR)`;
|
|
2385
|
+
},
|
|
1868
2386
|
renderDbNow({ offsetSeconds, collector }) {
|
|
1869
2387
|
if (offsetSeconds === 0)
|
|
1870
2388
|
return "NOW(6)";
|
|
@@ -1881,7 +2399,7 @@ function getSqlDialect(params) {
|
|
|
1881
2399
|
return SQL_DIALECTS[params.dialect];
|
|
1882
2400
|
}
|
|
1883
2401
|
// packages/sql/src/dialects/diagnostics.ts
|
|
1884
|
-
import { unsupportedCapability as
|
|
2402
|
+
import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
1885
2403
|
var QUERIES = Object.freeze({
|
|
1886
2404
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
1887
2405
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -1889,7 +2407,7 @@ var QUERIES = Object.freeze({
|
|
|
1889
2407
|
function renderDiagnostic(params) {
|
|
1890
2408
|
const query = QUERIES[params.query];
|
|
1891
2409
|
if (params.dialect.name !== query.dialect) {
|
|
1892
|
-
throw
|
|
2410
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
1893
2411
|
}
|
|
1894
2412
|
return { text: query.text, values: [], returning: "none" };
|
|
1895
2413
|
}
|
|
@@ -1898,7 +2416,7 @@ function isFixedDiagnostic(params) {
|
|
|
1898
2416
|
}
|
|
1899
2417
|
|
|
1900
2418
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
1901
|
-
import { VibeError as
|
|
2419
|
+
import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
|
|
1902
2420
|
function renderPostgresExplain(params) {
|
|
1903
2421
|
const { dialect, query, options } = params;
|
|
1904
2422
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -1933,7 +2451,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
1933
2451
|
});
|
|
1934
2452
|
function requirePostgres(params) {
|
|
1935
2453
|
if (params.dialect.name !== "postgres") {
|
|
1936
|
-
throw
|
|
2454
|
+
throw unsupportedCapability13({
|
|
1937
2455
|
dialect: params.dialect.name,
|
|
1938
2456
|
feature: params.feature,
|
|
1939
2457
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -1951,7 +2469,7 @@ function renderPostgresWorkload(params) {
|
|
|
1951
2469
|
if (query === "statementSnapshot") {
|
|
1952
2470
|
const bounded = limit ?? 20;
|
|
1953
2471
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
1954
|
-
throw new
|
|
2472
|
+
throw new VibeError13({
|
|
1955
2473
|
code: "VIBE_CONFIG",
|
|
1956
2474
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
1957
2475
|
meta: { limit }
|
|
@@ -1970,7 +2488,7 @@ function renderPostgresWorkload(params) {
|
|
|
1970
2488
|
function requireArg(params) {
|
|
1971
2489
|
const { value, name, query } = params;
|
|
1972
2490
|
if (value === undefined || value.length === 0) {
|
|
1973
|
-
throw new
|
|
2491
|
+
throw new VibeError13({
|
|
1974
2492
|
code: "VIBE_CONFIG",
|
|
1975
2493
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
1976
2494
|
meta: { query, argument: name }
|
|
@@ -1980,10 +2498,10 @@ function requireArg(params) {
|
|
|
1980
2498
|
}
|
|
1981
2499
|
|
|
1982
2500
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
1983
|
-
import { unsupportedCapability as
|
|
2501
|
+
import { unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
1984
2502
|
|
|
1985
2503
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
1986
|
-
import { RlsContextError, VibeError as
|
|
2504
|
+
import { RlsContextError, VibeError as VibeError14, assertValidSchemaIR, unsupportedCapability as unsupportedCapability14, validateRlsContextIR } from "@vibeorm/schema";
|
|
1987
2505
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
1988
2506
|
var COLUMN_TYPES = {
|
|
1989
2507
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -1996,18 +2514,18 @@ var COLUMN_TYPES = {
|
|
|
1996
2514
|
};
|
|
1997
2515
|
function requireRls(params) {
|
|
1998
2516
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
1999
|
-
throw
|
|
2517
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
2000
2518
|
}
|
|
2001
2519
|
}
|
|
2002
2520
|
function identifier2(params) {
|
|
2003
2521
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
2004
|
-
throw new
|
|
2522
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
2005
2523
|
}
|
|
2006
2524
|
return params.dialect.quoteIdent({ name: params.name });
|
|
2007
2525
|
}
|
|
2008
2526
|
function tableRef(params) {
|
|
2009
2527
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
2010
|
-
throw
|
|
2528
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
2011
2529
|
}
|
|
2012
2530
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
2013
2531
|
}
|
|
@@ -2048,14 +2566,14 @@ function renderRlsColumnType(params) {
|
|
|
2048
2566
|
if (native !== undefined)
|
|
2049
2567
|
return native;
|
|
2050
2568
|
}
|
|
2051
|
-
throw
|
|
2569
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
2052
2570
|
}
|
|
2053
2571
|
function protectedModel(params) {
|
|
2054
2572
|
requireRls(params);
|
|
2055
2573
|
assertValidSchemaIR({ schema: params.schema });
|
|
2056
2574
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
2057
2575
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
2058
|
-
throw new
|
|
2576
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
2059
2577
|
}
|
|
2060
2578
|
return model;
|
|
2061
2579
|
}
|
|
@@ -2088,7 +2606,7 @@ function predicate(params) {
|
|
|
2088
2606
|
}).join(" AND ");
|
|
2089
2607
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
2090
2608
|
}
|
|
2091
|
-
throw new
|
|
2609
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
2092
2610
|
}
|
|
2093
2611
|
function renderRlsPolicy(params) {
|
|
2094
2612
|
const model = protectedModel(params);
|
|
@@ -2122,7 +2640,7 @@ function renderRlsContext(params) {
|
|
|
2122
2640
|
function renderRlsRole(params) {
|
|
2123
2641
|
requireRls(params);
|
|
2124
2642
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
2125
|
-
throw new
|
|
2643
|
+
throw new VibeError14({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
2126
2644
|
}
|
|
2127
2645
|
const collector = new ParamCollector(params.dialect);
|
|
2128
2646
|
return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
|
|
@@ -2274,7 +2792,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
2274
2792
|
});
|
|
2275
2793
|
function requireRls2(params) {
|
|
2276
2794
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
2277
|
-
throw
|
|
2795
|
+
throw unsupportedCapability15({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
2278
2796
|
}
|
|
2279
2797
|
}
|
|
2280
2798
|
function renderRlsReadinessQuery(params) {
|
|
@@ -2306,7 +2824,7 @@ function renderRlsPolicyPath(params) {
|
|
|
2306
2824
|
}
|
|
2307
2825
|
|
|
2308
2826
|
// packages/sql/src/render.ts
|
|
2309
|
-
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as
|
|
2827
|
+
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2310
2828
|
|
|
2311
2829
|
// packages/sql/src/like.ts
|
|
2312
2830
|
function escapeLikePattern(params) {
|
|
@@ -2425,6 +2943,11 @@ function quoteProjection(dialect, column) {
|
|
|
2425
2943
|
return base;
|
|
2426
2944
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
2427
2945
|
}
|
|
2946
|
+
if (column.transform === "jsonText") {
|
|
2947
|
+
const ref = quoteCol(dialect, column);
|
|
2948
|
+
const alias = dialect.quoteIdent({ name: column.alias ?? column.name });
|
|
2949
|
+
return `${dialect.jsonTextProjection({ expression: ref })} AS ${alias}`;
|
|
2950
|
+
}
|
|
2428
2951
|
if (dialect.name !== "postgres") {
|
|
2429
2952
|
throw internalError2({ message: "ColRef.transform is a postgres construct" });
|
|
2430
2953
|
}
|
|
@@ -2481,7 +3004,7 @@ function renderAggRef(dialect, agg) {
|
|
|
2481
3004
|
function assertScalarArrays(dialect, feature) {
|
|
2482
3005
|
if (dialect.capabilities.scalarArrays)
|
|
2483
3006
|
return;
|
|
2484
|
-
throw
|
|
3007
|
+
throw unsupportedCapability16({
|
|
2485
3008
|
dialect: dialect.name,
|
|
2486
3009
|
feature,
|
|
2487
3010
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -2711,7 +3234,7 @@ function renderJsonLike(params) {
|
|
|
2711
3234
|
function renderJsonFilter(params) {
|
|
2712
3235
|
const { dialect, expr, collector } = params;
|
|
2713
3236
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
2714
|
-
throw
|
|
3237
|
+
throw unsupportedCapability16({
|
|
2715
3238
|
dialect: dialect.name,
|
|
2716
3239
|
feature: `the json filter "${expr.op}"`,
|
|
2717
3240
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -2778,7 +3301,7 @@ function renderJsonFilter(params) {
|
|
|
2778
3301
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
2779
3302
|
const tests = elements.map((element) => {
|
|
2780
3303
|
if (element === null || typeof element === "object" && element !== null) {
|
|
2781
|
-
throw
|
|
3304
|
+
throw unsupportedCapability16({
|
|
2782
3305
|
dialect: dialect.name,
|
|
2783
3306
|
feature: "json array_contains with object/array/null elements",
|
|
2784
3307
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -2916,6 +3439,10 @@ function applyReturning(params) {
|
|
|
2916
3439
|
const columns = requested.map((column) => {
|
|
2917
3440
|
if (typeof column === "string")
|
|
2918
3441
|
return dialect.quoteIdent({ name: column });
|
|
3442
|
+
if ("transform" in column) {
|
|
3443
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
3444
|
+
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
3445
|
+
}
|
|
2919
3446
|
if (dialect.name !== "postgres") {
|
|
2920
3447
|
throw internalError2({ message: "a castText RETURNING column is a postgres construct" });
|
|
2921
3448
|
}
|
|
@@ -2929,7 +3456,7 @@ function renderKeyTableJoin(params) {
|
|
|
2929
3456
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
2930
3457
|
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
2931
3458
|
if (!admitted) {
|
|
2932
|
-
throw
|
|
3459
|
+
throw unsupportedCapability16({
|
|
2933
3460
|
dialect: dialect.name,
|
|
2934
3461
|
feature: "relation key table",
|
|
2935
3462
|
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
@@ -3006,7 +3533,7 @@ function renderSelect(params) {
|
|
|
3006
3533
|
let distinctPrefix = "";
|
|
3007
3534
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
3008
3535
|
if (!dialect.capabilities.distinctOn) {
|
|
3009
|
-
throw
|
|
3536
|
+
throw unsupportedCapability16({
|
|
3010
3537
|
dialect: dialect.name,
|
|
3011
3538
|
feature: "SELECT DISTINCT ON",
|
|
3012
3539
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -3040,7 +3567,7 @@ function renderSelect(params) {
|
|
|
3040
3567
|
}
|
|
3041
3568
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
3042
3569
|
if (!dialect.capabilities.lateralJoin) {
|
|
3043
|
-
throw
|
|
3570
|
+
throw unsupportedCapability16({
|
|
3044
3571
|
dialect: dialect.name,
|
|
3045
3572
|
feature: "LATERAL JOIN relation loading",
|
|
3046
3573
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -3125,7 +3652,7 @@ function renderInsert(params) {
|
|
|
3125
3652
|
let target = "";
|
|
3126
3653
|
if (onConflict.constraint !== undefined) {
|
|
3127
3654
|
if (dialect.name !== "postgres") {
|
|
3128
|
-
throw
|
|
3655
|
+
throw unsupportedCapability16({
|
|
3129
3656
|
dialect: dialect.name,
|
|
3130
3657
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
3131
3658
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -3140,7 +3667,7 @@ function renderInsert(params) {
|
|
|
3140
3667
|
}
|
|
3141
3668
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
3142
3669
|
} else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
3143
|
-
throw
|
|
3670
|
+
throw unsupportedCapability16({
|
|
3144
3671
|
dialect: dialect.name,
|
|
3145
3672
|
feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
|
|
3146
3673
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -3158,7 +3685,7 @@ function renderInsert(params) {
|
|
|
3158
3685
|
throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
3159
3686
|
}
|
|
3160
3687
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
3161
|
-
throw
|
|
3688
|
+
throw unsupportedCapability16({
|
|
3162
3689
|
dialect: dialect.name,
|
|
3163
3690
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
3164
3691
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
@@ -3174,14 +3701,14 @@ function renderInsert(params) {
|
|
|
3174
3701
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
3175
3702
|
} else {
|
|
3176
3703
|
if (onConflict.constraint !== undefined) {
|
|
3177
|
-
throw
|
|
3704
|
+
throw unsupportedCapability16({
|
|
3178
3705
|
dialect: dialect.name,
|
|
3179
3706
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
3180
3707
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
3181
3708
|
});
|
|
3182
3709
|
}
|
|
3183
3710
|
if (onConflict.conflictWhere !== undefined) {
|
|
3184
|
-
throw
|
|
3711
|
+
throw unsupportedCapability16({
|
|
3185
3712
|
dialect: dialect.name,
|
|
3186
3713
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
3187
3714
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -3268,7 +3795,7 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
|
3268
3795
|
function renderChain(params) {
|
|
3269
3796
|
const { dialect, statement, collector } = params;
|
|
3270
3797
|
if (dialect.statementChain === null) {
|
|
3271
|
-
throw
|
|
3798
|
+
throw unsupportedCapability16({
|
|
3272
3799
|
dialect: dialect.name,
|
|
3273
3800
|
feature: "a statement chain (data-modifying WITH steps)",
|
|
3274
3801
|
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
@@ -3430,7 +3957,7 @@ function renderInsertSelect(params) {
|
|
|
3430
3957
|
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
3431
3958
|
}
|
|
3432
3959
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
3433
|
-
import { unsupportedCapability as
|
|
3960
|
+
import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
3434
3961
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
3435
3962
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
3436
3963
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -3457,13 +3984,13 @@ function parsePostgresTypeName(params) {
|
|
|
3457
3984
|
}
|
|
3458
3985
|
function renderPostgresTypeName(params) {
|
|
3459
3986
|
if (params.dialect.name !== "postgres")
|
|
3460
|
-
throw
|
|
3987
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
3461
3988
|
const { dialect, type } = params;
|
|
3462
3989
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
3463
3990
|
}
|
|
3464
3991
|
function renderPostgresTypeIdentity(params) {
|
|
3465
3992
|
if (params.dialect.name !== "postgres")
|
|
3466
|
-
throw
|
|
3993
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
3467
3994
|
const collector = new ParamCollector(params.dialect);
|
|
3468
3995
|
const { source } = params;
|
|
3469
3996
|
let text;
|
|
@@ -3476,11 +4003,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
3476
4003
|
return { text, values: collector.values, returning: "none" };
|
|
3477
4004
|
}
|
|
3478
4005
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
3479
|
-
import { unsupportedCapability as
|
|
4006
|
+
import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
3480
4007
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
3481
4008
|
const { dialect, table, column } = params;
|
|
3482
4009
|
if (dialect.name !== "postgres")
|
|
3483
|
-
throw
|
|
4010
|
+
throw unsupportedCapability18({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
3484
4011
|
const collector = new ParamCollector(dialect);
|
|
3485
4012
|
const namespace = collector.add(table.namespace);
|
|
3486
4013
|
const name = collector.add(table.name);
|
|
@@ -3504,28 +4031,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
3504
4031
|
};
|
|
3505
4032
|
}
|
|
3506
4033
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
3507
|
-
import { unsupportedCapability as
|
|
4034
|
+
import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
3508
4035
|
function renderConstraintOwnedIndexDrop(params) {
|
|
3509
4036
|
if (params.dialect.name !== "postgres")
|
|
3510
|
-
throw
|
|
4037
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
3511
4038
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
3512
4039
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
3513
4040
|
}
|
|
3514
4041
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
3515
|
-
import { unsupportedCapability as
|
|
4042
|
+
import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
3516
4043
|
function renderMysqlCheckProbe(params) {
|
|
3517
4044
|
if (params.dialect.name !== "mysql")
|
|
3518
|
-
throw
|
|
4045
|
+
throw unsupportedCapability20({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
3519
4046
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
3520
4047
|
const probe = q(params.probe);
|
|
3521
4048
|
switch (params.operation) {
|
|
3522
4049
|
case "clone":
|
|
3523
4050
|
if (params.table === undefined)
|
|
3524
|
-
throw
|
|
4051
|
+
throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
3525
4052
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
3526
4053
|
case "add": {
|
|
3527
4054
|
if (params.expression === undefined || params.definition === undefined)
|
|
3528
|
-
throw
|
|
4055
|
+
throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
3529
4056
|
const parts = tableDefinitionParts({ sql: params.definition });
|
|
3530
4057
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
3531
4058
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -3571,31 +4098,31 @@ function sqliteTableConstraints(params) {
|
|
|
3571
4098
|
return result;
|
|
3572
4099
|
}
|
|
3573
4100
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
3574
|
-
import { unsupportedCapability as
|
|
4101
|
+
import { unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
3575
4102
|
function renderMigrationVisibility(params) {
|
|
3576
4103
|
if (params.dialect.name !== "mysql")
|
|
3577
|
-
throw
|
|
4104
|
+
throw unsupportedCapability21({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
3578
4105
|
const collector = new ParamCollector(params.dialect);
|
|
3579
4106
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
3580
4107
|
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" };
|
|
3581
4108
|
}
|
|
3582
4109
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
3583
|
-
import { unsupportedCapability as
|
|
4110
|
+
import { unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
3584
4111
|
function renderSqliteColumnInventory(params) {
|
|
3585
4112
|
if (params.dialect.name !== "sqlite")
|
|
3586
|
-
throw
|
|
4113
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
3587
4114
|
const collector = new ParamCollector(params.dialect);
|
|
3588
4115
|
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" };
|
|
3589
4116
|
}
|
|
3590
4117
|
function renderSqliteForeignKeyInventory(params) {
|
|
3591
4118
|
if (params.dialect.name !== "sqlite")
|
|
3592
|
-
throw
|
|
4119
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
3593
4120
|
const collector = new ParamCollector(params.dialect);
|
|
3594
4121
|
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" };
|
|
3595
4122
|
}
|
|
3596
4123
|
function renderSqliteRebuildObjects(params) {
|
|
3597
4124
|
if (params.dialect.name !== "sqlite")
|
|
3598
|
-
throw
|
|
4125
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
3599
4126
|
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" };
|
|
3600
4127
|
}
|
|
3601
4128
|
function renderMigrationInventory(params) {
|
|
@@ -3634,7 +4161,7 @@ ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
|
3634
4161
|
return { text: `SELECT type AS kind, name, tbl_name AS parent, sql AS definition FROM ${schema}.sqlite_schema WHERE name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND name <> '_vibe_migrations' ORDER BY type, tbl_name, name`, values: [], returning: "none" };
|
|
3635
4162
|
}
|
|
3636
4163
|
// packages/sql/src/dialects/migration-objects.ts
|
|
3637
|
-
import { internalError as internalError3, unsupportedCapability as
|
|
4164
|
+
import { internalError as internalError3, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
3638
4165
|
function renderMigrationObjectQuery(params) {
|
|
3639
4166
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
3640
4167
|
}
|
|
@@ -3642,7 +4169,7 @@ function objectQuery(params) {
|
|
|
3642
4169
|
const { dialect, identity: object, collector } = params;
|
|
3643
4170
|
if (object.kind === "namespace") {
|
|
3644
4171
|
if (dialect.name !== "postgres")
|
|
3645
|
-
throw
|
|
4172
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3646
4173
|
return { text: `SELECT n.nspname::text AS "name" FROM pg_catalog.pg_namespace n WHERE n.nspname = ${collector.add(object.name)}`, values: collector.values, returning: "none" };
|
|
3647
4174
|
}
|
|
3648
4175
|
const physical = "table" in object ? object.table : object.name;
|
|
@@ -3706,7 +4233,7 @@ function objectQuery(params) {
|
|
|
3706
4233
|
break;
|
|
3707
4234
|
case "constraint":
|
|
3708
4235
|
if (object.constraintType === "exclusion")
|
|
3709
|
-
throw
|
|
4236
|
+
throw unsupportedCapability23({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
3710
4237
|
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)}`;
|
|
3711
4238
|
break;
|
|
3712
4239
|
case "index":
|
|
@@ -3716,15 +4243,15 @@ function objectQuery(params) {
|
|
|
3716
4243
|
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}`;
|
|
3717
4244
|
break;
|
|
3718
4245
|
default:
|
|
3719
|
-
throw
|
|
4246
|
+
throw unsupportedCapability23({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
3720
4247
|
}
|
|
3721
4248
|
} else {
|
|
3722
4249
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
3723
|
-
throw
|
|
4250
|
+
throw unsupportedCapability23({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
3724
4251
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
3725
4252
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
3726
4253
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
3727
|
-
throw
|
|
4254
|
+
throw unsupportedCapability23({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
3728
4255
|
const sqliteParams = new ParamCollector(dialect);
|
|
3729
4256
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
3730
4257
|
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)}`;
|
|
@@ -3754,7 +4281,7 @@ function renderMigrationDependencies(params) {
|
|
|
3754
4281
|
}
|
|
3755
4282
|
function dependencyQuery(params) {
|
|
3756
4283
|
if (params.dialect.name !== "postgres")
|
|
3757
|
-
throw
|
|
4284
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
3758
4285
|
const object = params.identity;
|
|
3759
4286
|
const collector = params.collector;
|
|
3760
4287
|
if (object.kind === "namespace") {
|
|
@@ -3811,7 +4338,7 @@ function inspectionReferences(params) {
|
|
|
3811
4338
|
function renderMigrationObjectBatch(params) {
|
|
3812
4339
|
const { dialect } = params;
|
|
3813
4340
|
if (dialect.name !== "postgres")
|
|
3814
|
-
throw
|
|
4341
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
3815
4342
|
const groups = new Map;
|
|
3816
4343
|
for (const [position, identity] of params.identities.entries()) {
|
|
3817
4344
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -3844,16 +4371,16 @@ function renderMigrationNamespace(params) {
|
|
|
3844
4371
|
}
|
|
3845
4372
|
function renderCheckProbe(params) {
|
|
3846
4373
|
if (params.dialect.name !== "postgres")
|
|
3847
|
-
throw
|
|
4374
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
3848
4375
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
3849
4376
|
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" };
|
|
3850
4377
|
}
|
|
3851
4378
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
3852
|
-
import { VibeError as
|
|
4379
|
+
import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3853
4380
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
3854
4381
|
function requirePostgres2(params) {
|
|
3855
4382
|
if (params.dialect.name !== "postgres") {
|
|
3856
|
-
throw
|
|
4383
|
+
throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.feature });
|
|
3857
4384
|
}
|
|
3858
4385
|
}
|
|
3859
4386
|
function renderMigrationSessionRead(params) {
|
|
@@ -3868,7 +4395,7 @@ function renderMigrationSessionSet(params) {
|
|
|
3868
4395
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
3869
4396
|
const { milliseconds, setting } = params;
|
|
3870
4397
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
3871
|
-
throw new
|
|
4398
|
+
throw new VibeError15({
|
|
3872
4399
|
code: "VIBE_MIGRATION",
|
|
3873
4400
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
3874
4401
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -3981,14 +4508,26 @@ export {
|
|
|
3981
4508
|
SQLITE_SESSION_SQL,
|
|
3982
4509
|
SQL_DEFAULT_CELL,
|
|
3983
4510
|
SQL_DIALECTS,
|
|
4511
|
+
assertSqlParamBuilding,
|
|
3984
4512
|
chainVetoToken,
|
|
3985
4513
|
compileExclusion,
|
|
4514
|
+
compileReusableSqlCommand,
|
|
4515
|
+
compileReusableSqlQuery,
|
|
3986
4516
|
compileSql,
|
|
4517
|
+
compileSqlCommand,
|
|
4518
|
+
compileSqlQuery,
|
|
3987
4519
|
containsPattern,
|
|
4520
|
+
createReusableSqlCommand,
|
|
4521
|
+
createReusableSqlQuery,
|
|
4522
|
+
createSqlCommand,
|
|
4523
|
+
createSqlInput,
|
|
4524
|
+
createSqlInputScope,
|
|
4525
|
+
createSqlQuery,
|
|
3988
4526
|
dbNowMarker,
|
|
3989
4527
|
decode,
|
|
3990
4528
|
decodeIsNoop,
|
|
3991
4529
|
describeRowLock,
|
|
4530
|
+
encode,
|
|
3992
4531
|
endsWithPattern,
|
|
3993
4532
|
enumMembershipCodec,
|
|
3994
4533
|
escapeGlobPattern,
|
|
@@ -4003,9 +4542,15 @@ export {
|
|
|
4003
4542
|
isJsonParamCell,
|
|
4004
4543
|
isListSetOperation,
|
|
4005
4544
|
isNativeEnumArrayParameter,
|
|
4545
|
+
isReusableSqlCommand,
|
|
4546
|
+
isReusableSqlQuery,
|
|
4006
4547
|
isSetOperation,
|
|
4548
|
+
isSqlBindingCollector,
|
|
4549
|
+
isSqlCommand,
|
|
4007
4550
|
isSqlDefaultCell,
|
|
4008
4551
|
isSqlFragment,
|
|
4552
|
+
isSqlParam,
|
|
4553
|
+
isSqlQuery,
|
|
4009
4554
|
isStepColumnRef,
|
|
4010
4555
|
jsonListParameter,
|
|
4011
4556
|
jsonParamCell,
|
|
@@ -4013,12 +4558,15 @@ export {
|
|
|
4013
4558
|
mysqlDialect,
|
|
4014
4559
|
nativeEnumArrayParameter,
|
|
4015
4560
|
parseDateTimeText,
|
|
4561
|
+
parseJsonText,
|
|
4016
4562
|
parsePgArrayLiteral,
|
|
4017
4563
|
parsePgArrayLiteralNested,
|
|
4018
4564
|
parsePostgresTypeName,
|
|
4019
4565
|
postgresArrayElementFromText,
|
|
4020
4566
|
postgresDialect,
|
|
4021
4567
|
projectionOutputName,
|
|
4568
|
+
recordSqlBinding,
|
|
4569
|
+
recordSqlBindingCheck,
|
|
4022
4570
|
renderAdvisoryLock,
|
|
4023
4571
|
renderCheckProbe,
|
|
4024
4572
|
renderColumnStateQuery,
|
|
@@ -4082,9 +4630,14 @@ export {
|
|
|
4082
4630
|
renderSqliteFtsUpdateTrigger,
|
|
4083
4631
|
renderSqliteRebuildObjects,
|
|
4084
4632
|
renderStatement,
|
|
4633
|
+
resultScalarContract,
|
|
4085
4634
|
returningColumnName,
|
|
4635
|
+
sealSqlInputScope,
|
|
4086
4636
|
shortestFloat32,
|
|
4637
|
+
snapshotSqlValue,
|
|
4087
4638
|
sql,
|
|
4639
|
+
sqlInputContract,
|
|
4640
|
+
sqlParamContract,
|
|
4088
4641
|
sqlUsesIncoming,
|
|
4089
4642
|
sqliteColumnClauses,
|
|
4090
4643
|
sqliteDialect,
|
|
@@ -4098,4 +4651,4 @@ export {
|
|
|
4098
4651
|
toPgArrayLiteral
|
|
4099
4652
|
};
|
|
4100
4653
|
|
|
4101
|
-
//# debugId=
|
|
4654
|
+
//# debugId=B8D70B32D9F5E99064756E2164756E21
|