@vibeorm/sql 3.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.d.ts +25 -8
- package/dist/codecs.d.ts +9 -0
- package/dist/dialect.d.ts +10 -0
- package/dist/dialects/postgres-migration.d.ts +2 -0
- package/dist/dialects/sqlite-json-equality.d.ts +15 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +1710 -998
- package/dist/index.js.map +24 -17
- package/dist/parameter-snapshot.d.ts +7 -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 +1080 -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 +46 -10
- package/dist/sql-bindings.d.ts +84 -0
- package/dist/sql-handles.d.ts +7 -0
- package/dist/sql-input.d.ts +98 -0
- package/dist/timestamp-identity.d.ts +40 -0
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,76 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/sql/src/timestamp-identity.ts
|
|
3
|
+
var identities = new WeakMap;
|
|
4
|
+
function rememberTimestampIdentity(params) {
|
|
5
|
+
const { date, text } = params;
|
|
6
|
+
const fraction = /\.(\d+)/.exec(text)?.[1] ?? "";
|
|
7
|
+
const remainder = fraction.slice(3).replace(/0+$/, "");
|
|
8
|
+
const canonical = Date.prototype.toISOString.call(date).replace(/(\.\d{3})Z$/, `$1${remainder}Z`);
|
|
9
|
+
identities.set(date, {
|
|
10
|
+
milliseconds: Date.prototype.getTime.call(date),
|
|
11
|
+
text,
|
|
12
|
+
...params.numeric === undefined ? {} : { numeric: params.numeric },
|
|
13
|
+
key: params.key ?? `date:${canonical}`,
|
|
14
|
+
kind: params.kind ?? "timestamp"
|
|
15
|
+
});
|
|
16
|
+
return date;
|
|
17
|
+
}
|
|
18
|
+
function identityOf(params) {
|
|
19
|
+
const stored = identities.get(params.date);
|
|
20
|
+
if (stored !== undefined && stored.milliseconds !== Date.prototype.getTime.call(params.date)) {
|
|
21
|
+
identities.delete(params.date);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
return stored;
|
|
25
|
+
}
|
|
26
|
+
function timestampIdentityKey(params) {
|
|
27
|
+
return identityOf(params)?.key ?? `date:${Number.isFinite(Date.prototype.getTime.call(params.date)) ? Date.prototype.toISOString.call(params.date) : "invalid"}`;
|
|
28
|
+
}
|
|
29
|
+
function timestampIdentityText(params) {
|
|
30
|
+
const identity = identityOf(params);
|
|
31
|
+
return identity !== undefined && (params.kind === undefined || identity.kind === params.kind) ? identity.text : undefined;
|
|
32
|
+
}
|
|
33
|
+
function sqliteTimestampIdentityValue(params) {
|
|
34
|
+
const identity = identityOf(params);
|
|
35
|
+
return identity?.kind === "sqlite" ? identity.numeric ?? identity.text : undefined;
|
|
36
|
+
}
|
|
37
|
+
function compareTimestampDates(params) {
|
|
38
|
+
const left = identityOf({ date: params.left });
|
|
39
|
+
const right = identityOf({ date: params.right });
|
|
40
|
+
if (timestampIdentityKey({ date: params.left }) === timestampIdentityKey({ date: params.right }))
|
|
41
|
+
return 0;
|
|
42
|
+
const rank = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
43
|
+
if (left?.kind === "sqlite" || right?.kind === "sqlite") {
|
|
44
|
+
return left?.numeric !== undefined && right?.numeric !== undefined ? rank(left.numeric, right.numeric) : null;
|
|
45
|
+
}
|
|
46
|
+
const clock = (identity) => {
|
|
47
|
+
const [, hours, minutes, seconds, offset] = identity.key.split(":");
|
|
48
|
+
const [whole, fraction = ""] = seconds.split(".");
|
|
49
|
+
const local = BigInt(Number(hours) * 3600 + Number(minutes) * 60 + Number(whole)) * 1000000000n + BigInt(fraction.padEnd(9, "0"));
|
|
50
|
+
const zone = Number(offset);
|
|
51
|
+
return [local - BigInt(zone) * 1000000n, -zone];
|
|
52
|
+
};
|
|
53
|
+
if (left?.kind !== undefined && left.kind !== "timestamp" || right?.kind !== undefined && right.kind !== "timestamp") {
|
|
54
|
+
if (left === undefined || right === undefined || left.kind !== right.kind)
|
|
55
|
+
return null;
|
|
56
|
+
const a = clock(left);
|
|
57
|
+
const b = clock(right);
|
|
58
|
+
return rank(a[0], b[0]) || rank(a[1], b[1]);
|
|
59
|
+
}
|
|
60
|
+
const milliseconds = rank(Date.prototype.getTime.call(params.left), Date.prototype.getTime.call(params.right));
|
|
61
|
+
if (milliseconds !== 0)
|
|
62
|
+
return milliseconds;
|
|
63
|
+
const a = /\.(\d+)/.exec(left?.text ?? "")?.[1]?.slice(3) ?? "";
|
|
64
|
+
const b = /\.(\d+)/.exec(right?.text ?? "")?.[1]?.slice(3) ?? "";
|
|
65
|
+
return rank(a.padEnd(Math.max(a.length, b.length), "0"), b.padEnd(Math.max(a.length, b.length), "0"));
|
|
66
|
+
}
|
|
67
|
+
function cloneTimestampDate(params) {
|
|
68
|
+
const date = new Date(Date.prototype.getTime.call(params.date));
|
|
69
|
+
const identity = identityOf(params);
|
|
70
|
+
if (identity !== undefined)
|
|
71
|
+
identities.set(date, identity);
|
|
72
|
+
return date;
|
|
73
|
+
}
|
|
2
74
|
// packages/sql/src/native-enum-parameter.ts
|
|
3
75
|
var NATIVE_ENUM_ARRAY_PARAMETER = Symbol("vibeorm.nativeEnumArrayParameter");
|
|
4
76
|
function nativeEnumArrayParameter(params) {
|
|
@@ -39,158 +111,1048 @@ function renderExternalOwnershipCatalog(params) {
|
|
|
39
111
|
return { text, values: [], returning: "none" };
|
|
40
112
|
}
|
|
41
113
|
// packages/sql/src/dialects/external-dependents.ts
|
|
42
|
-
import { unsupportedCapability as
|
|
114
|
+
import { unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
43
115
|
|
|
44
116
|
// packages/sql/src/params.ts
|
|
45
|
-
import { unsupportedCapability as
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return
|
|
117
|
+
import { unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
118
|
+
|
|
119
|
+
// packages/sql/src/sql-bindings.ts
|
|
120
|
+
import { VibeError as VibeError5, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
121
|
+
|
|
122
|
+
// packages/sql/src/parameter-snapshot.ts
|
|
123
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
124
|
+
|
|
125
|
+
// packages/sql/src/sql-input.ts
|
|
126
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
127
|
+
|
|
128
|
+
// packages/sql/src/result-decoder.ts
|
|
129
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
130
|
+
|
|
131
|
+
// packages/sql/src/pg-array.ts
|
|
132
|
+
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
133
|
+
function toPgArrayLiteral(params) {
|
|
134
|
+
const { values } = params;
|
|
135
|
+
const parts = values.map((value) => {
|
|
136
|
+
if (value === null || value === undefined)
|
|
137
|
+
return "NULL";
|
|
138
|
+
const text = String(value);
|
|
139
|
+
return PG_ARRAY_QUOTE.test(text) ? `"${text.replace(/["\\]/g, (char) => `\\${char}`)}"` : text;
|
|
140
|
+
});
|
|
141
|
+
return `{${parts.join(",")}}`;
|
|
142
|
+
}
|
|
143
|
+
function parsePgArrayLiteral(params) {
|
|
144
|
+
const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
|
|
145
|
+
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
146
|
+
return null;
|
|
147
|
+
const inner = literal.slice(1, -1);
|
|
148
|
+
if (inner.length === 0)
|
|
149
|
+
return [];
|
|
150
|
+
const out = [];
|
|
151
|
+
let index = 0;
|
|
152
|
+
while (index < inner.length) {
|
|
153
|
+
if (inner[index] === '"') {
|
|
154
|
+
let text = "";
|
|
155
|
+
index++;
|
|
156
|
+
while (index < inner.length) {
|
|
157
|
+
const char = inner[index];
|
|
158
|
+
if (char === "\\") {
|
|
159
|
+
text += inner[index + 1] ?? "";
|
|
160
|
+
index += 2;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
index++;
|
|
164
|
+
if (char === '"')
|
|
165
|
+
break;
|
|
166
|
+
text += char;
|
|
167
|
+
}
|
|
168
|
+
out.push(text);
|
|
169
|
+
} else {
|
|
170
|
+
let text = "";
|
|
171
|
+
while (index < inner.length && inner[index] !== ",") {
|
|
172
|
+
text += inner[index];
|
|
173
|
+
index++;
|
|
174
|
+
}
|
|
175
|
+
out.push(text === "NULL" ? null : text);
|
|
66
176
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
addMany(values) {
|
|
71
|
-
return values.map((value) => this.add(value));
|
|
177
|
+
if (inner[index] === ",")
|
|
178
|
+
index++;
|
|
72
179
|
}
|
|
180
|
+
return out;
|
|
73
181
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const collector = new ParamCollector(params.dialect);
|
|
80
|
-
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
|
-
const request = collector.add(JSON.stringify(requests));
|
|
82
|
-
const text = `WITH request AS (
|
|
83
|
-
SELECT * FROM jsonb_to_recordset(${request}::text::jsonb) AS request(position integer, kind text, namespace text, name text, "column" text)
|
|
84
|
-
), target AS (
|
|
85
|
-
SELECT request.position, 'pg_type'::regclass AS classid, t.oid AS objid, NULL::integer AS objsubid FROM request JOIN pg_catalog.pg_namespace n ON n.nspname = request.namespace JOIN pg_catalog.pg_type t ON t.typnamespace = n.oid AND t.typname = request.name WHERE request.kind = 'type'
|
|
86
|
-
UNION ALL SELECT request.position, 'pg_class'::regclass, c.oid, a.attnum::integer FROM request JOIN pg_catalog.pg_namespace n ON n.nspname = request.namespace JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid AND c.relname = request.name LEFT JOIN pg_catalog.pg_attribute a ON request.kind = 'column' AND a.attrelid = c.oid AND a.attname = request."column" AND NOT a.attisdropped WHERE request.kind = 'relation' OR request.kind = 'column' AND a.attnum IS NOT NULL
|
|
87
|
-
), owned AS (
|
|
88
|
-
SELECT position, classid, objid, objsubid FROM target
|
|
89
|
-
UNION SELECT target.position, 'pg_type'::regclass, c.reltype, NULL FROM target JOIN pg_catalog.pg_class c ON c.oid = target.objid WHERE target.classid = 'pg_class'::regclass AND target.objsubid IS NULL AND c.reltype <> 0
|
|
90
|
-
UNION SELECT target.position, 'pg_class'::regclass, s.oid, NULL FROM target JOIN pg_catalog.pg_depend sd ON sd.refclassid = target.classid AND sd.refobjid = target.objid AND (target.objsubid IS NULL OR sd.refobjsubid = target.objsubid) AND sd.classid = 'pg_class'::regclass AND sd.deptype IN ('a', 'i') JOIN pg_catalog.pg_class s ON s.oid = sd.objid AND s.relkind = 'S'
|
|
91
|
-
), referenced AS (
|
|
92
|
-
SELECT position, classid, objid, objsubid FROM owned
|
|
93
|
-
UNION SELECT owned.position, 'pg_type'::regclass, t.typarray, NULL FROM owned JOIN pg_catalog.pg_type t ON t.oid = owned.objid WHERE owned.classid = 'pg_type'::regclass AND t.typarray <> 0
|
|
94
|
-
)
|
|
95
|
-
SELECT DISTINCT referenced.position, dependent.type AS "dependentType", dependent.identity AS "dependentIdentity", ref.identity AS referenced, d.classid::regclass::text AS "dependentClass",
|
|
96
|
-
rn.nspname::text AS "relationNamespace", rel.relname::text AS "relationName", rel.relkind::text AS "relationKind", col.attname::text AS "columnName",
|
|
97
|
-
COALESCE(pn.nspname, tn.nspname, dependent.schema)::text AS "objectNamespace", COALESCE(p.proname, ty.typname)::text AS "objectName"
|
|
98
|
-
FROM referenced
|
|
99
|
-
JOIN pg_catalog.pg_depend d ON d.refclassid = referenced.classid AND d.refobjid = referenced.objid AND (referenced.objsubid IS NULL OR d.refobjsubid = referenced.objsubid) AND d.deptype = 'n'
|
|
100
|
-
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) dependent
|
|
101
|
-
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.refclassid, d.refobjid, d.refobjsubid) ref
|
|
102
|
-
LEFT JOIN pg_catalog.pg_class self ON d.classid = 'pg_class'::regclass AND self.oid = d.objid
|
|
103
|
-
LEFT JOIN pg_catalog.pg_index ix ON ix.indexrelid = self.oid
|
|
104
|
-
LEFT JOIN pg_catalog.pg_attrdef ad ON d.classid = 'pg_attrdef'::regclass AND ad.oid = d.objid
|
|
105
|
-
LEFT JOIN pg_catalog.pg_rewrite rw ON d.classid = 'pg_rewrite'::regclass AND rw.oid = d.objid
|
|
106
|
-
LEFT JOIN pg_catalog.pg_trigger tg ON d.classid = 'pg_trigger'::regclass AND tg.oid = d.objid
|
|
107
|
-
LEFT JOIN pg_catalog.pg_constraint con ON d.classid = 'pg_constraint'::regclass AND con.oid = d.objid
|
|
108
|
-
LEFT JOIN pg_catalog.pg_policy pol ON d.classid = 'pg_policy'::regclass AND pol.oid = d.objid
|
|
109
|
-
LEFT JOIN pg_catalog.pg_type ty ON d.classid = 'pg_type'::regclass AND ty.oid = d.objid
|
|
110
|
-
LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = ty.typnamespace
|
|
111
|
-
LEFT JOIN pg_catalog.pg_proc p ON d.classid = 'pg_proc'::regclass AND p.oid = d.objid
|
|
112
|
-
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
113
|
-
LEFT JOIN pg_catalog.pg_class rel ON rel.oid = COALESCE(ix.indrelid, self.oid, ad.adrelid, rw.ev_class, tg.tgrelid, NULLIF(con.conrelid, 0), pol.polrelid, NULLIF(ty.typrelid, 0))
|
|
114
|
-
LEFT JOIN pg_catalog.pg_namespace rn ON rn.oid = rel.relnamespace
|
|
115
|
-
LEFT JOIN pg_catalog.pg_attribute col ON col.attrelid = rel.oid AND col.attnum = CASE WHEN self.oid IS NOT NULL AND d.objsubid > 0 THEN d.objsubid WHEN ad.oid IS NOT NULL THEN ad.adnum END
|
|
116
|
-
ORDER BY 1, 2, 3, 4`;
|
|
117
|
-
return { text, values: collector.values, returning: "none" };
|
|
182
|
+
function parsePgArrayLiteralNested(params) {
|
|
183
|
+
const text = params.literal.replace(/^(?:\[-?\d+:-?\d+\])+=/, "");
|
|
184
|
+
const cursor = { index: 0 };
|
|
185
|
+
const parsed = parseDimension({ text, cursor });
|
|
186
|
+
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
118
187
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
let text;
|
|
129
|
-
switch (params.query) {
|
|
130
|
-
case "tables":
|
|
131
|
-
text = `SELECT table_name FROM information_schema.tables WHERE table_schema = ${namespace} AND table_type = 'BASE TABLE' AND table_name <> '_vibe_migrations' AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
132
|
-
break;
|
|
133
|
-
case "views":
|
|
134
|
-
text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
135
|
-
break;
|
|
136
|
-
case "columns":
|
|
137
|
-
text = `SELECT table_name, column_name, data_type, udt_name, udt_schema, is_nullable, is_identity, is_generated, collation_name, column_default, datetime_precision, character_maximum_length, numeric_precision, numeric_scale, ordinal_position, (SELECT format_type(a.atttypid, a.atttypmod) FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND c.relname = columns.table_name AND a.attname = columns.column_name) AS full_type FROM information_schema.columns AS columns WHERE table_schema = ${namespace} ORDER BY table_name, ordinal_position`;
|
|
138
|
-
break;
|
|
139
|
-
case "constraints":
|
|
140
|
-
text = `SELECT c.conname AS constraint_name, CASE c.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type, cl.relname AS table_name, att.attname AS column_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord) ON true JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = k.attnum WHERE c.contype IN ('p', 'u') AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, k.ord`;
|
|
141
|
-
break;
|
|
142
|
-
case "checks":
|
|
143
|
-
text = `SELECT cl.relname AS table_name, c.conname AS constraint_name, pg_get_constraintdef(c.oid) AS definition FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace WHERE c.contype = 'c' AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname`;
|
|
144
|
-
break;
|
|
145
|
-
case "foreignKeys":
|
|
146
|
-
text = `SELECT c.conname AS constraint_name, cl.relname AS table_name, att.attname AS column_name, fn.nspname AS foreign_namespace, fcl.relname AS foreign_table_name, fatt.attname AS foreign_column_name, c.confdeltype AS on_delete_char, c.confupdtype AS on_update_char, c.convalidated AS validated, c.condeferrable AS deferrable, (SELECT bool_and(t.tgenabled IN ('O', 'A')) FROM pg_catalog.pg_trigger t WHERE t.tgconstraint = c.oid) AS triggers_enabled FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace JOIN pg_catalog.pg_class fcl ON fcl.oid = c.confrelid JOIN pg_catalog.pg_namespace fn ON fn.oid = fcl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true JOIN unnest(c.confkey) WITH ORDINALITY AS rk(attnum, ord) ON lk.ord = rk.ord JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = lk.attnum JOIN pg_catalog.pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = rk.attnum WHERE c.contype = 'f' AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, lk.ord`;
|
|
147
|
-
break;
|
|
148
|
-
case "enums":
|
|
149
|
-
text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_type'::regclass AND d.objid = t.oid AND d.deptype = 'e') ORDER BY t.typname, e.enumsortorder`;
|
|
150
|
-
break;
|
|
151
|
-
case "indexes":
|
|
152
|
-
text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
|
|
153
|
-
break;
|
|
188
|
+
function parseDimension(params) {
|
|
189
|
+
const { text, cursor } = params;
|
|
190
|
+
if (text[cursor.index] !== "{")
|
|
191
|
+
return null;
|
|
192
|
+
cursor.index++;
|
|
193
|
+
const out = [];
|
|
194
|
+
if (text[cursor.index] === "}") {
|
|
195
|
+
cursor.index++;
|
|
196
|
+
return out;
|
|
154
197
|
}
|
|
155
|
-
|
|
198
|
+
while (cursor.index < text.length) {
|
|
199
|
+
if (text[cursor.index] === "{") {
|
|
200
|
+
const nested = parseDimension({ text, cursor });
|
|
201
|
+
if (nested === null)
|
|
202
|
+
return null;
|
|
203
|
+
out.push(nested);
|
|
204
|
+
} else if (text[cursor.index] === '"') {
|
|
205
|
+
let element = "";
|
|
206
|
+
cursor.index++;
|
|
207
|
+
while (cursor.index < text.length && text[cursor.index] !== '"') {
|
|
208
|
+
if (text[cursor.index] === "\\")
|
|
209
|
+
cursor.index++;
|
|
210
|
+
element += text[cursor.index] ?? "";
|
|
211
|
+
cursor.index++;
|
|
212
|
+
}
|
|
213
|
+
if (text[cursor.index] !== '"')
|
|
214
|
+
return null;
|
|
215
|
+
cursor.index++;
|
|
216
|
+
out.push(element);
|
|
217
|
+
} else {
|
|
218
|
+
let element = "";
|
|
219
|
+
while (cursor.index < text.length && text[cursor.index] !== "," && text[cursor.index] !== "}") {
|
|
220
|
+
element += text[cursor.index];
|
|
221
|
+
cursor.index++;
|
|
222
|
+
}
|
|
223
|
+
out.push(element === "NULL" ? null : element);
|
|
224
|
+
}
|
|
225
|
+
if (text[cursor.index] === ",") {
|
|
226
|
+
cursor.index++;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (text[cursor.index] !== "}")
|
|
230
|
+
return null;
|
|
231
|
+
cursor.index++;
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
156
235
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const collector = new ParamCollector(dialect);
|
|
164
|
-
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
|
-
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`;
|
|
236
|
+
|
|
237
|
+
// packages/sql/src/codecs.ts
|
|
238
|
+
import { VibeError, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
|
|
239
|
+
function enumMembershipCodec(params) {
|
|
240
|
+
const { model, field, enum: enumName } = params;
|
|
241
|
+
const members = new Set(params.members);
|
|
166
242
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
throw unsupportedCapability6({ dialect: dialect.name, feature: "uniqueIndexOperatorClasses", hint: "custom unique operator classes can change client selector equality" });
|
|
181
|
-
return columns.map((column) => {
|
|
182
|
-
const operatorClass = column.operatorClass;
|
|
183
|
-
if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass })) {
|
|
184
|
-
throw new VibeError({ 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 } });
|
|
243
|
+
encode: (value) => {
|
|
244
|
+
if (typeof value === "string" && members.has(value))
|
|
245
|
+
return value;
|
|
246
|
+
throw new VibeError({
|
|
247
|
+
code: "VIBE_VALIDATION",
|
|
248
|
+
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
249
|
+
meta: { model, field, enum: enumName }
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
decode: (value) => {
|
|
253
|
+
if (typeof value === "string" && members.has(value))
|
|
254
|
+
return value;
|
|
255
|
+
throw invalidStoredData({ model, field, enum: enumName, value });
|
|
185
256
|
}
|
|
186
|
-
|
|
187
|
-
return `${dialect.quoteIdent({ name: column.name })}${ops}${column.desc ? " DESC" : ""}`;
|
|
188
|
-
});
|
|
257
|
+
};
|
|
189
258
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
259
|
+
function decodeIsNoop(params) {
|
|
260
|
+
const { scalarType, wire } = params;
|
|
261
|
+
if (wire === undefined)
|
|
262
|
+
return false;
|
|
263
|
+
if (scalarType === "DateTime")
|
|
264
|
+
return false;
|
|
265
|
+
if (scalarType === "Json")
|
|
266
|
+
return wire.json === "parsed";
|
|
267
|
+
if (scalarType === "Int" || scalarType === "Float")
|
|
268
|
+
return wire.numbers === "native";
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
var identity = (value) => value;
|
|
272
|
+
var passthroughCodec = { encode: identity, decode: identity };
|
|
273
|
+
var numberCodec = {
|
|
274
|
+
encode: identity,
|
|
275
|
+
decode: (value) => typeof value === "string" ? Number(value) : value
|
|
276
|
+
};
|
|
277
|
+
var bigIntCodec = {
|
|
278
|
+
encode: (value) => typeof value === "bigint" ? value.toString() : value,
|
|
279
|
+
decode: (value) => {
|
|
280
|
+
if (typeof value === "bigint")
|
|
281
|
+
return value;
|
|
282
|
+
if (typeof value === "string" && /^[+-]?\d+$/.test(value.trim()))
|
|
283
|
+
return BigInt(value.trim());
|
|
284
|
+
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
285
|
+
return BigInt(value);
|
|
286
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
var decimalCodec = {
|
|
290
|
+
encode: (value) => typeof value === "number" || typeof value === "bigint" ? String(value) : value,
|
|
291
|
+
decode: identity
|
|
292
|
+
};
|
|
293
|
+
var mysqlDecimalCodec = {
|
|
294
|
+
encode: decimalCodec.encode,
|
|
295
|
+
decode: (value) => {
|
|
296
|
+
if (typeof value !== "string" || !value.includes("."))
|
|
297
|
+
return value;
|
|
298
|
+
const trimmed = value.replace(/0+$/, "").replace(/\.$/, "");
|
|
299
|
+
return trimmed === "" || trimmed === "-" ? "0" : trimmed;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
303
|
+
var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
|
|
304
|
+
function parseDateTimeText(params) {
|
|
305
|
+
const trimmed = params.text.trim();
|
|
306
|
+
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
307
|
+
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
308
|
+
if (!Number.isNaN(asUtc.getTime()))
|
|
309
|
+
return rememberTimestampIdentity({ date: asUtc, text: trimmed });
|
|
310
|
+
}
|
|
311
|
+
const zoned = PG_ZONED_DATETIME.exec(trimmed);
|
|
312
|
+
if (zoned !== null) {
|
|
313
|
+
const [, date, time, fraction, hours, minutes] = zoned;
|
|
314
|
+
const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
|
|
315
|
+
if (!Number.isNaN(iso.getTime()))
|
|
316
|
+
return rememberTimestampIdentity({ date: iso, text: trimmed });
|
|
317
|
+
}
|
|
318
|
+
const parsed = new Date(trimmed);
|
|
319
|
+
return Number.isNaN(parsed.getTime()) ? undefined : rememberTimestampIdentity({ date: parsed, text: trimmed });
|
|
320
|
+
}
|
|
321
|
+
var dateNativeCodec = {
|
|
322
|
+
encode: (value) => {
|
|
323
|
+
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
324
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
325
|
+
}
|
|
326
|
+
return value instanceof Date ? timestampIdentityText({ date: value }) ?? value : value;
|
|
327
|
+
},
|
|
328
|
+
decode: (value) => {
|
|
329
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
330
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
331
|
+
return date;
|
|
332
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
var dateTextCodec = {
|
|
336
|
+
encode: (value) => {
|
|
337
|
+
dateNativeCodec.encode(value);
|
|
338
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
339
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
340
|
+
return value instanceof Date ? sqliteTimestampIdentityValue({ date: value }) ?? date.toISOString() : date.toISOString();
|
|
341
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
342
|
+
},
|
|
343
|
+
decode: (value) => {
|
|
344
|
+
const date = dateNativeCodec.decode(value);
|
|
345
|
+
if (typeof value === "number")
|
|
346
|
+
return rememberTimestampIdentity({ date, text: date.toISOString(), numeric: value, key: `epoch:${value}`, kind: "sqlite" });
|
|
347
|
+
return typeof value === "string" ? rememberTimestampIdentity({ date, text: value, key: `date:${value}`, kind: "sqlite" }) : date;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var mysqlDateCodec = {
|
|
351
|
+
encode: (value) => {
|
|
352
|
+
dateNativeCodec.encode(value);
|
|
353
|
+
if (value instanceof Date) {
|
|
354
|
+
const text = timestampIdentityText({ date: value, kind: "timestamp" });
|
|
355
|
+
if (text !== undefined) {
|
|
356
|
+
const fraction = /\.(\d+)/.exec(text)?.[1] ?? "";
|
|
357
|
+
return value.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, `.${fraction.padEnd(3, "0")}`);
|
|
358
|
+
}
|
|
359
|
+
return value;
|
|
360
|
+
}
|
|
361
|
+
if (typeof value === "number") {
|
|
362
|
+
const parsed = new Date(value);
|
|
363
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
364
|
+
return parsed;
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
if (typeof value === "string")
|
|
368
|
+
return parseDateTimeText({ text: value }) ?? value;
|
|
369
|
+
return value;
|
|
370
|
+
},
|
|
371
|
+
decode: dateNativeCodec.decode
|
|
372
|
+
};
|
|
373
|
+
var MILLISECONDS_PER_DAY = 86400000;
|
|
374
|
+
var TIME_OF_DAY_TEXT = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/;
|
|
375
|
+
var pad = (value, width) => String(value).padStart(width, "0");
|
|
376
|
+
var utcMillisecondOfDay = (date) => (date.getTime() % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
|
|
377
|
+
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)}`;
|
|
378
|
+
function makeTimeOfDayCodec(params) {
|
|
379
|
+
const suffix = params.zoned ? "+00" : "";
|
|
380
|
+
return Object.freeze({
|
|
381
|
+
encode: (value) => {
|
|
382
|
+
dateNativeCodec.encode(value);
|
|
383
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
384
|
+
if (date === undefined || !Number.isFinite(date.getTime())) {
|
|
385
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
|
|
386
|
+
}
|
|
387
|
+
return timestampIdentityText({ date, kind: params.zoned ? "timetz" : "time" }) ?? `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
388
|
+
},
|
|
389
|
+
decode: (value) => {
|
|
390
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) {
|
|
391
|
+
if (timestampIdentityText({ date: value, kind: params.zoned ? "timetz" : "time" }) !== undefined)
|
|
392
|
+
return value;
|
|
393
|
+
const milliseconds = utcMillisecondOfDay(value);
|
|
394
|
+
const text = timeOfDayText(milliseconds);
|
|
395
|
+
return rememberTimestampIdentity({ date: new Date(milliseconds), text: `${text}${suffix}`, key: `${params.zoned ? "timetz" : "time"}:${text}:0`, kind: params.zoned ? "timetz" : "time" });
|
|
396
|
+
}
|
|
397
|
+
const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
|
|
398
|
+
if (match !== null) {
|
|
399
|
+
const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
|
|
400
|
+
const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
|
|
401
|
+
const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
|
|
402
|
+
if (millisecond <= MILLISECONDS_PER_DAY) {
|
|
403
|
+
const date = new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
|
|
404
|
+
const exactFraction = (fraction ?? "").padEnd(3, "0").replace(/(?<=\d{3})0+$/, "");
|
|
405
|
+
const key = `${params.zoned ? "timetz" : "time"}:${hours}:${minutes}:${seconds ?? "00"}.${exactFraction}:${offset}`;
|
|
406
|
+
return rememberTimestampIdentity({ date, text: value.trim(), key, kind: params.zoned ? "timetz" : "time" });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
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 } });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
var timeCodec = makeTimeOfDayCodec({ zoned: false });
|
|
414
|
+
var timetzCodec = makeTimeOfDayCodec({ zoned: true });
|
|
415
|
+
function timeOfDayCodec(params) {
|
|
416
|
+
const type = timeOfDayType({ nativeType: params.nativeType });
|
|
417
|
+
return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
|
|
418
|
+
}
|
|
419
|
+
var booleanIntCodec = {
|
|
420
|
+
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
421
|
+
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
422
|
+
};
|
|
423
|
+
var booleanNativeInCodec = {
|
|
424
|
+
encode: identity,
|
|
425
|
+
decode: (value) => value === "0" ? false : value === "1" ? true : booleanIntCodec.decode(value)
|
|
426
|
+
};
|
|
427
|
+
function parseJsonText(params) {
|
|
428
|
+
const { text, context } = params;
|
|
429
|
+
try {
|
|
430
|
+
return JSON.parse(text);
|
|
431
|
+
} catch (cause) {
|
|
432
|
+
if (context !== undefined) {
|
|
433
|
+
throw new VibeError({
|
|
434
|
+
code: "VIBE_INTERNAL",
|
|
435
|
+
message: "a bound Json field returned text that is not valid JSON",
|
|
436
|
+
meta: { ...context }
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
throw new VibeError({
|
|
440
|
+
code: "VIBE_INTERNAL",
|
|
441
|
+
message: "a Json column returned text that is not valid JSON",
|
|
442
|
+
meta: { value: text },
|
|
443
|
+
cause
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
var jsonCodec = {
|
|
448
|
+
encode: (value) => JSON.stringify(value),
|
|
449
|
+
decode: (value) => {
|
|
450
|
+
if (typeof value !== "string")
|
|
451
|
+
return value;
|
|
452
|
+
return parseJsonText({ text: value });
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
function shortestFloat32(params) {
|
|
456
|
+
const { value } = params;
|
|
457
|
+
if (value === 0 || !Number.isFinite(value))
|
|
458
|
+
return value;
|
|
459
|
+
const target = Math.fround(value);
|
|
460
|
+
for (let precision = 1;precision <= 9; precision++) {
|
|
461
|
+
const candidate = Number(value.toPrecision(precision));
|
|
462
|
+
if (Math.fround(candidate) === target)
|
|
463
|
+
return candidate;
|
|
464
|
+
}
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
var float32Codec = {
|
|
468
|
+
encode: identity,
|
|
469
|
+
decode: (value) => {
|
|
470
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
471
|
+
return typeof number === "number" ? shortestFloat32({ value: number }) : number;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var booleanFromText = (text) => text === "t" ? true : text === "f" ? false : text;
|
|
475
|
+
var bytesFromText = (text) => /^\\x(?:[0-9a-fA-F]{2})*$/.test(text) ? new Uint8Array(Buffer.from(text.slice(2), "hex")) : text;
|
|
476
|
+
function postgresArrayElementFromText(params) {
|
|
477
|
+
switch (params.scalarType) {
|
|
478
|
+
case "Boolean":
|
|
479
|
+
return booleanFromText;
|
|
480
|
+
case "Bytes":
|
|
481
|
+
return bytesFromText;
|
|
482
|
+
case "Json":
|
|
483
|
+
return jsonCodec.decode;
|
|
484
|
+
default:
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
var POSTGRES_CODECS = {
|
|
489
|
+
String: passthroughCodec,
|
|
490
|
+
Boolean: passthroughCodec,
|
|
491
|
+
Int: numberCodec,
|
|
492
|
+
BigInt: bigIntCodec,
|
|
493
|
+
Float: numberCodec,
|
|
494
|
+
Decimal: decimalCodec,
|
|
495
|
+
DateTime: dateNativeCodec,
|
|
496
|
+
Json: jsonCodec,
|
|
497
|
+
Bytes: passthroughCodec
|
|
498
|
+
};
|
|
499
|
+
var SQLITE_CODECS = {
|
|
500
|
+
String: passthroughCodec,
|
|
501
|
+
Boolean: booleanIntCodec,
|
|
502
|
+
Int: numberCodec,
|
|
503
|
+
BigInt: bigIntCodec,
|
|
504
|
+
Float: numberCodec,
|
|
505
|
+
Decimal: decimalCodec,
|
|
506
|
+
DateTime: dateTextCodec,
|
|
507
|
+
Json: jsonCodec,
|
|
508
|
+
Bytes: passthroughCodec
|
|
509
|
+
};
|
|
510
|
+
var MYSQL_CODECS = {
|
|
511
|
+
String: passthroughCodec,
|
|
512
|
+
Boolean: booleanNativeInCodec,
|
|
513
|
+
Int: numberCodec,
|
|
514
|
+
BigInt: bigIntCodec,
|
|
515
|
+
Float: numberCodec,
|
|
516
|
+
Decimal: mysqlDecimalCodec,
|
|
517
|
+
DateTime: mysqlDateCodec,
|
|
518
|
+
Json: jsonCodec,
|
|
519
|
+
Bytes: passthroughCodec
|
|
520
|
+
};
|
|
521
|
+
var DIALECT_CODECS = {
|
|
522
|
+
postgres: POSTGRES_CODECS,
|
|
523
|
+
sqlite: SQLITE_CODECS,
|
|
524
|
+
mysql: MYSQL_CODECS
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// packages/sql/src/result-decoder.ts
|
|
528
|
+
var scalarContracts = new WeakMap;
|
|
529
|
+
var decoderIdentities = new WeakMap;
|
|
530
|
+
var nullableDecoders = new WeakMap;
|
|
531
|
+
var arrayDecoders = new WeakMap;
|
|
532
|
+
function sameResultDecoder(params) {
|
|
533
|
+
if (params.left === params.right)
|
|
534
|
+
return true;
|
|
535
|
+
const identity = decoderIdentities.get(params.left);
|
|
536
|
+
return identity !== undefined && identity === decoderIdentities.get(params.right);
|
|
537
|
+
}
|
|
538
|
+
function wrappedIdentity(params) {
|
|
539
|
+
const identity = decoderIdentities.get(params.decoder);
|
|
540
|
+
return identity === undefined ? undefined : JSON.stringify([params.kind, identity]);
|
|
541
|
+
}
|
|
542
|
+
function resultScalarContract(params) {
|
|
543
|
+
return scalarContracts.get(params.decoder);
|
|
544
|
+
}
|
|
545
|
+
function registerScalar(params) {
|
|
546
|
+
if (params.contract !== undefined)
|
|
547
|
+
scalarContracts.set(params.decoder, Object.freeze(params.contract));
|
|
548
|
+
if (params.identity !== undefined)
|
|
549
|
+
decoderIdentities.set(params.decoder, params.identity);
|
|
550
|
+
return params.decoder;
|
|
551
|
+
}
|
|
552
|
+
function wrappedContract(params) {
|
|
553
|
+
const contract = scalarContracts.get(params.decoder);
|
|
554
|
+
if (contract === undefined || params.list === true && contract.list)
|
|
555
|
+
return;
|
|
556
|
+
return { ...contract, ...params.list === true ? { list: true, nullable: false } : {}, ...params.nullable === true ? { nullable: true } : {} };
|
|
557
|
+
}
|
|
558
|
+
function refuse(encoding, message) {
|
|
559
|
+
throw new VibeError2({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
560
|
+
}
|
|
561
|
+
function scalar(type, valid) {
|
|
562
|
+
const convert = (params, encoding) => {
|
|
563
|
+
const { value, dialect } = params;
|
|
564
|
+
if (value === null || value === undefined)
|
|
565
|
+
refuse(encoding, `A non-null ${type} projection received null or a missing column`);
|
|
566
|
+
if (type === "BigInt" && !(typeof value === "bigint" || typeof value === "string" && /^[+-]?\d+$/.test(value) || typeof value === "number" && Number.isSafeInteger(value))) {
|
|
567
|
+
refuse(encoding, "BigInt requires exact integer text, bigint, or a safe integer");
|
|
568
|
+
}
|
|
569
|
+
if (type === "Boolean" && value !== true && value !== false && value !== 0 && value !== 1 && !(!encoding && dialect === "mysql" && (value === "0" || value === "1")))
|
|
570
|
+
refuse(encoding, "Boolean requires true, false, 0, or 1");
|
|
571
|
+
if (type === "Decimal" && typeof value === "number")
|
|
572
|
+
refuse(encoding, "Decimal requires exact decimal text, not a floating-point number");
|
|
573
|
+
if ((type === "Int" || type === "Float") && typeof value === "string" && !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) {
|
|
574
|
+
refuse(encoding, `Invalid ${type} text`);
|
|
575
|
+
}
|
|
576
|
+
const codec = DIALECT_CODECS[dialect][type];
|
|
577
|
+
const result = type === "Json" && params.wire?.json === "parsed" ? value : codec.decode(value);
|
|
578
|
+
if (!valid(result))
|
|
579
|
+
refuse(encoding, `Invalid ${type} projection value`);
|
|
580
|
+
return result;
|
|
581
|
+
};
|
|
582
|
+
return registerScalar({ identity: JSON.stringify(["scalar", type]), contract: { scalar: type, nullable: false, list: false }, decoder: Object.freeze({
|
|
583
|
+
decode: (params) => convert(params, false),
|
|
584
|
+
encode: (params) => {
|
|
585
|
+
const value = type === "Json" ? params.value : convert(params, true);
|
|
586
|
+
if (value === undefined || value === null)
|
|
587
|
+
refuse(true, `A non-null ${type} parameter cannot be null or undefined`);
|
|
588
|
+
return DIALECT_CODECS[params.dialect][type].encode(value);
|
|
589
|
+
}
|
|
590
|
+
}) });
|
|
591
|
+
}
|
|
592
|
+
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
593
|
+
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
594
|
+
var extendedDecoder = registerScalar({ identity: "extended-date-time", contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
|
|
595
|
+
decode: (params) => {
|
|
596
|
+
if (params.value === "infinity" || params.value === Infinity)
|
|
597
|
+
return "infinity";
|
|
598
|
+
if (params.value === "-infinity" || params.value === -Infinity)
|
|
599
|
+
return "-infinity";
|
|
600
|
+
return dateDecoder.decode(params);
|
|
601
|
+
},
|
|
602
|
+
encode: (params) => {
|
|
603
|
+
if (params.value === "infinity" || params.value === "-infinity") {
|
|
604
|
+
if (params.dialect !== "postgres")
|
|
605
|
+
throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
606
|
+
return params.value;
|
|
607
|
+
}
|
|
608
|
+
return dateDecoder.encode(params);
|
|
609
|
+
}
|
|
610
|
+
}) });
|
|
611
|
+
function hasEncoder(decoder) {
|
|
612
|
+
return "encode" in decoder && typeof decoder.encode === "function";
|
|
613
|
+
}
|
|
614
|
+
function nullable({ decoder }) {
|
|
615
|
+
const cached = nullableDecoders.get(decoder);
|
|
616
|
+
if (cached !== undefined)
|
|
617
|
+
return cached;
|
|
618
|
+
const read = (params) => params.value === null ? null : decoder.decode(params);
|
|
619
|
+
const wrapped = hasEncoder(decoder) ? registerScalar({ identity: wrappedIdentity({ decoder, kind: "nullable" }), contract: wrappedContract({ decoder, nullable: true }), decoder: Object.freeze({
|
|
620
|
+
decode: read,
|
|
621
|
+
encode: (params) => params.value === null ? null : decoder.encode(params)
|
|
622
|
+
}) }) : Object.freeze({ decode: read });
|
|
623
|
+
nullableDecoders.set(decoder, wrapped);
|
|
624
|
+
return wrapped;
|
|
625
|
+
}
|
|
626
|
+
function array({ decoder }) {
|
|
627
|
+
const cached = arrayDecoders.get(decoder);
|
|
628
|
+
if (cached !== undefined)
|
|
629
|
+
return cached;
|
|
630
|
+
const bidirectional = hasEncoder(decoder);
|
|
631
|
+
const read = (params) => {
|
|
632
|
+
const raw = params.value;
|
|
633
|
+
let value;
|
|
634
|
+
if (params.dialect === "postgres" && typeof raw === "string") {
|
|
635
|
+
if (bidirectional)
|
|
636
|
+
value = parsePgArrayLiteral({ literal: raw });
|
|
637
|
+
else {
|
|
638
|
+
const elements = parsePgArrayLiteralNested({ literal: raw });
|
|
639
|
+
if (elements !== null && elements.some((element) => Array.isArray(element)))
|
|
640
|
+
refuse(false, "Read-only array decoders support one-dimensional SQL arrays only");
|
|
641
|
+
value = elements;
|
|
642
|
+
}
|
|
643
|
+
} else
|
|
644
|
+
value = ArrayBuffer.isView(raw) && "length" in raw ? Array.from(raw) : raw;
|
|
645
|
+
if (!Array.isArray(value))
|
|
646
|
+
refuse(false, "Expected an array projection");
|
|
647
|
+
return value.map((element) => decoder.decode({ ...params, value: element }));
|
|
648
|
+
};
|
|
649
|
+
const wrapped = hasEncoder(decoder) ? registerScalar({ identity: wrappedIdentity({ decoder, kind: "array" }), contract: wrappedContract({ decoder, list: true }), decoder: Object.freeze({
|
|
650
|
+
decode: read,
|
|
651
|
+
encode: (params) => {
|
|
652
|
+
if (!Array.isArray(params.value))
|
|
653
|
+
refuse(true, "Expected an array parameter");
|
|
654
|
+
return params.value.map((value) => decoder.encode({ ...params, value }));
|
|
655
|
+
}
|
|
656
|
+
}) }) : Object.freeze({ decode: read });
|
|
657
|
+
arrayDecoders.set(decoder, wrapped);
|
|
658
|
+
return wrapped;
|
|
659
|
+
}
|
|
660
|
+
var encode = Object.freeze({
|
|
661
|
+
array: ({ encoder }) => Object.freeze({
|
|
662
|
+
encode: (params) => {
|
|
663
|
+
if (!Array.isArray(params.value))
|
|
664
|
+
refuse(true, "Expected an array parameter");
|
|
665
|
+
return Array.from(params.value, (value) => encoder.encode({ ...params, value }));
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
});
|
|
669
|
+
var decode = Object.freeze({
|
|
670
|
+
string: stringDecoder,
|
|
671
|
+
boolean: scalar("Boolean", (value) => typeof value === "boolean"),
|
|
672
|
+
int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
|
|
673
|
+
float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
|
|
674
|
+
bigInt: scalar("BigInt", (value) => typeof value === "bigint"),
|
|
675
|
+
decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
|
|
676
|
+
dateTime: dateDecoder,
|
|
677
|
+
extendedDateTime: extendedDecoder,
|
|
678
|
+
json: scalar("Json", (value) => value !== undefined),
|
|
679
|
+
bytes: scalar("Bytes", (value) => value instanceof Uint8Array),
|
|
680
|
+
nullable,
|
|
681
|
+
array,
|
|
682
|
+
enum: ({ values }) => {
|
|
683
|
+
const members = new Set(values);
|
|
684
|
+
return registerScalar({ identity: JSON.stringify(["enum", [...members].sort()]), contract: { scalar: "String", nullable: false, list: false }, decoder: Object.freeze({
|
|
685
|
+
decode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(false, "Unknown enum projection member"),
|
|
686
|
+
encode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(true, "Unknown enum parameter member")
|
|
687
|
+
}) });
|
|
688
|
+
},
|
|
689
|
+
object: ({ fields }) => {
|
|
690
|
+
const entries = Object.entries(fields);
|
|
691
|
+
const convert = (params, encoding) => {
|
|
692
|
+
if (typeof params.value !== "object" || params.value === null || Array.isArray(params.value))
|
|
693
|
+
refuse(encoding, "Expected a result row object");
|
|
694
|
+
const row = params.value;
|
|
695
|
+
return Object.fromEntries(entries.map(([name, decoder]) => {
|
|
696
|
+
if (!Object.hasOwn(row, name))
|
|
697
|
+
refuse(encoding, `Missing projection column "${name}"`);
|
|
698
|
+
return [name, encoding ? decoder.encode({ ...params, value: row[name] }) : decoder.decode({ ...params, value: row[name] })];
|
|
699
|
+
}));
|
|
700
|
+
};
|
|
701
|
+
return Object.freeze({ decode: (params) => convert(params, false), encode: (params) => convert(params, true) });
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// packages/sql/src/sql-handles.ts
|
|
706
|
+
var checks = new Set;
|
|
707
|
+
function registerSqlHandleCheck(params) {
|
|
708
|
+
checks.add(params.check);
|
|
709
|
+
}
|
|
710
|
+
function isSqlHandle(value) {
|
|
711
|
+
for (const check of checks)
|
|
712
|
+
if (check(value))
|
|
713
|
+
return true;
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// packages/sql/src/sql-input.ts
|
|
718
|
+
var inputs = new WeakMap;
|
|
719
|
+
var tokens = new WeakMap;
|
|
720
|
+
var scopes = new WeakMap;
|
|
721
|
+
registerSqlHandleCheck({ check: (value) => inputs.has(value) || tokens.has(value) || scopes.has(value) });
|
|
722
|
+
function invalid(message) {
|
|
723
|
+
throw new VibeError3({ code: "VIBE_VALIDATION", message });
|
|
724
|
+
}
|
|
725
|
+
function isSqlParam(value) {
|
|
726
|
+
return typeof value === "object" && value !== null && tokens.has(value);
|
|
727
|
+
}
|
|
728
|
+
function isSqlInput(value) {
|
|
729
|
+
return typeof value === "object" && value !== null && inputs.has(value);
|
|
730
|
+
}
|
|
731
|
+
function createSqlInput(params) {
|
|
732
|
+
if (typeof params.contract.encoder?.encode !== "function")
|
|
733
|
+
invalid("SQL input contracts require a parameter encoder");
|
|
734
|
+
const input = Object.freeze(Object.create(null));
|
|
735
|
+
inputs.set(input, Object.freeze({
|
|
736
|
+
...params.contract,
|
|
737
|
+
...params.contract.enumValues === undefined ? {} : { enumValues: Object.freeze([...params.contract.enumValues]) },
|
|
738
|
+
...params.contract.enumType === undefined ? {} : { enumType: Object.freeze({ ...params.contract.enumType }) }
|
|
739
|
+
}));
|
|
740
|
+
return input;
|
|
741
|
+
}
|
|
742
|
+
function sqlInputContract(params) {
|
|
743
|
+
const value = params.input;
|
|
744
|
+
if (typeof value !== "object" || value === null)
|
|
745
|
+
invalid("SQL input declarations require a decoder or qb.input descriptor");
|
|
746
|
+
const declared = inputs.get(value);
|
|
747
|
+
if (declared !== undefined)
|
|
748
|
+
return declared;
|
|
749
|
+
if (!("encode" in value) || !("decode" in value) || typeof value.encode !== "function" || typeof value.decode !== "function")
|
|
750
|
+
invalid("Expected an authentic SQL input descriptor or bidirectional decoder");
|
|
751
|
+
const decoder = value;
|
|
752
|
+
const physical = resultScalarContract({ decoder });
|
|
753
|
+
return Object.freeze({ encoder: decoder, scalar: physical?.scalar ?? "Unknown", nullable: physical?.nullable ?? true, list: physical?.list ?? false });
|
|
754
|
+
}
|
|
755
|
+
function createSqlInputScope(params) {
|
|
756
|
+
if (typeof params.isHandle !== "function")
|
|
757
|
+
invalid("SQL input scopes require the builder's handle check");
|
|
758
|
+
const declarations = params.inputs;
|
|
759
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations))
|
|
760
|
+
invalid("Reusable SQL requires an input declaration record");
|
|
761
|
+
const prototype = Object.getPrototypeOf(declarations);
|
|
762
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
763
|
+
invalid("SQL input declarations require a plain record");
|
|
764
|
+
const keys = Reflect.ownKeys(declarations);
|
|
765
|
+
const entries = keys.map((key) => {
|
|
766
|
+
if (typeof key !== "string" || key.length === 0 || key.includes("\x00"))
|
|
767
|
+
invalid("SQL input names must be nonempty strings without NUL");
|
|
768
|
+
const descriptor = Object.getOwnPropertyDescriptor(declarations, key);
|
|
769
|
+
if (!("value" in descriptor))
|
|
770
|
+
invalid("SQL input declarations cannot contain accessors");
|
|
771
|
+
return [key, sqlInputContract({ input: descriptor.value })];
|
|
772
|
+
});
|
|
773
|
+
const scope = Object.freeze(Object.create(null));
|
|
774
|
+
scopes.set(scope, { open: true, keys: Object.freeze(entries.map(([key]) => key)), isHandle: params.isHandle });
|
|
775
|
+
const record = Object.create(null);
|
|
776
|
+
for (const [key, contract] of entries) {
|
|
777
|
+
const param = Object.freeze(Object.create(null));
|
|
778
|
+
tokens.set(param, { scope, key, contract });
|
|
779
|
+
record[key] = param;
|
|
780
|
+
}
|
|
781
|
+
return { scope, params: Object.freeze(record) };
|
|
782
|
+
}
|
|
783
|
+
function sealSqlInputScope(params) {
|
|
784
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
785
|
+
state.open = false;
|
|
786
|
+
}
|
|
787
|
+
function assertSqlParamBuilding(params) {
|
|
788
|
+
const state = tokens.get(params.param) ?? invalid("Expected an authentic SQL input token");
|
|
789
|
+
if (scopes.get(state.scope)?.open !== true)
|
|
790
|
+
invalid("SQL input tokens cannot escape their reusable build callback");
|
|
791
|
+
}
|
|
792
|
+
function sqlParamContract(params) {
|
|
793
|
+
return (tokens.get(params.param) ?? invalid("Expected an authentic SQL input token")).contract;
|
|
794
|
+
}
|
|
795
|
+
function sqlParamKey(params) {
|
|
796
|
+
const state = tokens.get(params.param) ?? invalid("Expected an authentic SQL input token");
|
|
797
|
+
if (state.scope !== params.scope)
|
|
798
|
+
invalid("A reusable query cannot capture another template's input token");
|
|
799
|
+
return state.key;
|
|
800
|
+
}
|
|
801
|
+
function sqlInputScopeHandle(params) {
|
|
802
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
803
|
+
return state.isHandle(params.value);
|
|
804
|
+
}
|
|
805
|
+
function sqlInputScopeKeys(params) {
|
|
806
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
807
|
+
if (state.open)
|
|
808
|
+
invalid("A reusable SQL build must finish before compilation");
|
|
809
|
+
return state.keys;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// packages/sql/src/parameter-snapshot.ts
|
|
813
|
+
function snapshotSqlValue(value, ancestors = new Set, isHandle) {
|
|
814
|
+
const invalid = (message) => {
|
|
815
|
+
throw new VibeError4({ code: "VIBE_VALIDATION", message });
|
|
816
|
+
};
|
|
817
|
+
if (isSqlParam(value))
|
|
818
|
+
return invalid("SQL input tokens are valid only in admitted reusable builder positions");
|
|
819
|
+
if (typeof value === "object" && value !== null && (isSqlHandle(value) || isHandle?.(value)))
|
|
820
|
+
return invalid("SQL builder, plan, template, input and fragment handles are not execution values");
|
|
821
|
+
if (value === undefined)
|
|
822
|
+
return invalid("SQL values cannot be undefined; omit a defaulted column or use null");
|
|
823
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "bigint")
|
|
824
|
+
return value;
|
|
825
|
+
if (typeof value === "number")
|
|
826
|
+
return value;
|
|
827
|
+
if (value instanceof Date)
|
|
828
|
+
return Number.isFinite(value.getTime()) ? cloneTimestampDate({ date: value }) : invalid("SQL dates must be finite");
|
|
829
|
+
if (value instanceof Uint8Array)
|
|
830
|
+
return new Uint8Array(value);
|
|
831
|
+
if (typeof value !== "object")
|
|
832
|
+
return invalid("Unsupported SQL parameter value");
|
|
833
|
+
if (ancestors.has(value))
|
|
834
|
+
return invalid("Cyclic SQL parameter values are unsupported");
|
|
835
|
+
const array = Array.isArray(value);
|
|
836
|
+
const prototype = Object.getPrototypeOf(value);
|
|
837
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
|
|
838
|
+
return invalid("SQL parameters require standard arrays or plain objects");
|
|
839
|
+
const nested = new Set([...ancestors, value]);
|
|
840
|
+
const snapshot = array ? [] : Object.create(prototype === null ? null : Object.prototype);
|
|
841
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
842
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
843
|
+
if (!("value" in descriptor))
|
|
844
|
+
return invalid("SQL parameter objects and arrays cannot contain accessors");
|
|
845
|
+
Object.defineProperty(snapshot, key, { ...descriptor, value: snapshotSqlValue(descriptor.value, nested, isHandle) });
|
|
846
|
+
}
|
|
847
|
+
return snapshot;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// packages/sql/src/sql-bindings.ts
|
|
851
|
+
var recordings = new WeakMap;
|
|
852
|
+
var programs = new WeakMap;
|
|
853
|
+
function invalid2(message) {
|
|
854
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message });
|
|
855
|
+
}
|
|
856
|
+
function unsupported(message) {
|
|
857
|
+
throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message });
|
|
858
|
+
}
|
|
859
|
+
function sqlParameterTransport(params) {
|
|
860
|
+
if (isNativeEnumArrayParameter(params.value))
|
|
861
|
+
return { kind: "native-enum-array", type: params.value.type };
|
|
862
|
+
if (isJsonListParameter(params.value))
|
|
863
|
+
return { kind: "json-list", storage: params.value.storage, whole: typeof params.value.value !== "string" };
|
|
864
|
+
return { kind: "plain" };
|
|
865
|
+
}
|
|
866
|
+
function renderSqlParameterTransport(params) {
|
|
867
|
+
const { dialect, placeholder, transport } = params;
|
|
868
|
+
if (transport.kind === "plain")
|
|
869
|
+
return placeholder;
|
|
870
|
+
if (!dialect.capabilities.scalarArrays)
|
|
871
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "scalarArrays" });
|
|
872
|
+
if (transport.kind === "native-enum-array")
|
|
873
|
+
return `${placeholder}::${dialect.quoteIdent({ name: transport.type.namespace })}.${dialect.quoteIdent({ name: transport.type.name })}[]`;
|
|
874
|
+
return `${placeholder}::text::${transport.storage}${transport.whole ? "[]" : ""}`;
|
|
875
|
+
}
|
|
876
|
+
function sqlParameterWireValue(params) {
|
|
877
|
+
if (isNativeEnumArrayParameter(params.value))
|
|
878
|
+
return params.value.value;
|
|
879
|
+
if (isJsonListParameter(params.value))
|
|
880
|
+
return typeof params.value.value === "string" ? params.value.value : toPgArrayLiteral({ values: params.value.value });
|
|
881
|
+
return params.value;
|
|
882
|
+
}
|
|
883
|
+
function isSqlBindingCollector(params) {
|
|
884
|
+
return recordings.has(params.collector);
|
|
885
|
+
}
|
|
886
|
+
function startSqlBindingCollection(params) {
|
|
887
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
888
|
+
if (recordings.has(params.collector))
|
|
889
|
+
invalid2("SQL binding collection was already started");
|
|
890
|
+
recordings.set(params.collector, { dialect: params.dialect, scope: params.scope, steps: [], contracts: new Map, used: new Set });
|
|
891
|
+
}
|
|
892
|
+
function contractShape(contract) {
|
|
893
|
+
return JSON.stringify({
|
|
894
|
+
scalar: contract.scalar,
|
|
895
|
+
nullable: contract.nullable,
|
|
896
|
+
list: contract.list,
|
|
897
|
+
nativeType: contract.nativeType,
|
|
898
|
+
enumValues: contract.enumValues,
|
|
899
|
+
enumType: contract.enumType === undefined ? undefined : [contract.enumType.namespace, contract.enumType.name],
|
|
900
|
+
jsonStorage: contract.jsonStorage
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
function sameContract(left, right) {
|
|
904
|
+
return left.encoder === right.encoder && left.columnIdentity === right.columnIdentity && contractShape(left) === contractShape(right);
|
|
905
|
+
}
|
|
906
|
+
function recordSqlBinding(params) {
|
|
907
|
+
const state = recordings.get(params.collector) ?? invalid2("SQL input tokens require execution through their reusable template");
|
|
908
|
+
let key;
|
|
909
|
+
if (params.source.kind === "input") {
|
|
910
|
+
key = sqlParamKey({ param: params.source.param, scope: state.scope });
|
|
911
|
+
if (params.contract === undefined)
|
|
912
|
+
invalid2("Named SQL inputs require an effective encoding contract");
|
|
913
|
+
const previous = state.contracts.get(key);
|
|
914
|
+
if (previous !== undefined && !sameContract(previous, params.contract))
|
|
915
|
+
invalid2(`SQL input "${key}" has incompatible encoding contracts`);
|
|
916
|
+
state.contracts.set(key, params.contract);
|
|
917
|
+
state.used.add(key);
|
|
918
|
+
}
|
|
919
|
+
const transport = params.transport.kind === "native-enum-array" ? Object.freeze({ ...params.transport, type: Object.freeze({ ...params.transport.type }) }) : Object.freeze({ ...params.transport });
|
|
920
|
+
const source = params.source.kind === "literal" ? Object.freeze({ kind: "literal", value: snapshotSqlValue(params.source.value) }) : Object.freeze({ ...params.source });
|
|
921
|
+
const placeholder = params.collector.reserve();
|
|
922
|
+
const text = renderSqlParameterTransport({ dialect: state.dialect, placeholder, transport });
|
|
923
|
+
state.steps.push(Object.freeze({ kind: "slot", source, key, transport, encode: params.encode, rejectNull: params.rejectNull === true, ...params.validate === undefined ? {} : { validate: params.validate } }));
|
|
924
|
+
return text;
|
|
925
|
+
}
|
|
926
|
+
function recordSqlBindingCheck(params) {
|
|
927
|
+
const state = recordings.get(params.collector) ?? invalid2("Expected a structural SQL collector");
|
|
928
|
+
state.steps.push(Object.freeze({ kind: "check", check: params.check }));
|
|
929
|
+
}
|
|
930
|
+
function finishSqlBindingCollection(params) {
|
|
931
|
+
const state = recordings.get(params.collector) ?? invalid2("Expected a structural SQL collector");
|
|
932
|
+
const keys = sqlInputScopeKeys({ scope: state.scope });
|
|
933
|
+
for (const key of keys)
|
|
934
|
+
if (!state.used.has(key))
|
|
935
|
+
invalid2(`Declared SQL input "${key}" is unused by the finished query`);
|
|
936
|
+
const program = Object.freeze(Object.create(null));
|
|
937
|
+
programs.set(program, { dialect: state.dialect, keys, steps: Object.freeze([...state.steps]) });
|
|
938
|
+
return program;
|
|
939
|
+
}
|
|
940
|
+
function inputSnapshot(params) {
|
|
941
|
+
const value = params.value === undefined && params.keys.length === 0 ? Object.create(null) : params.value;
|
|
942
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
943
|
+
invalid2("Reusable SQL parameters require an exact plain record");
|
|
944
|
+
const prototype = Object.getPrototypeOf(value);
|
|
945
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
946
|
+
invalid2("Reusable SQL parameters require a plain record without inherited inputs");
|
|
947
|
+
const own = Reflect.ownKeys(value);
|
|
948
|
+
if (own.length !== params.keys.length || own.some((key) => typeof key !== "string" || !params.keys.includes(key)))
|
|
949
|
+
invalid2("Reusable SQL parameters must contain exactly the declared names");
|
|
950
|
+
const descriptors = params.keys.map((key) => {
|
|
951
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
952
|
+
if (descriptor === undefined || !("value" in descriptor) || descriptor.value === undefined)
|
|
953
|
+
invalid2(`SQL input "${key}" must be an own defined data property`);
|
|
954
|
+
return { key, value: descriptor.value };
|
|
955
|
+
});
|
|
956
|
+
const snapshot = Object.create(null);
|
|
957
|
+
for (const entry of descriptors)
|
|
958
|
+
snapshot[entry.key] = snapshotSqlValue(entry.value, new Set, params.isHandle);
|
|
959
|
+
return snapshot;
|
|
960
|
+
}
|
|
961
|
+
function sameTransport(left, right) {
|
|
962
|
+
if (left.kind !== right.kind)
|
|
963
|
+
return false;
|
|
964
|
+
if (left.kind === "native-enum-array" && right.kind === "native-enum-array")
|
|
965
|
+
return left.type.namespace === right.type.namespace && left.type.name === right.type.name;
|
|
966
|
+
if (left.kind === "json-list" && right.kind === "json-list")
|
|
967
|
+
return left.storage === right.storage && left.whole === right.whole;
|
|
968
|
+
return true;
|
|
969
|
+
}
|
|
970
|
+
function bindSqlBindingProgram(params) {
|
|
971
|
+
const state = programs.get(params.program) ?? invalid2("Expected an authentic SQL binding program");
|
|
972
|
+
const inputs = inputSnapshot({ keys: state.keys, value: params.params, isHandle: params.isHandle });
|
|
973
|
+
const encodedInputs = new Map;
|
|
974
|
+
const values = [];
|
|
975
|
+
for (const step of state.steps) {
|
|
976
|
+
if (step.kind === "check") {
|
|
977
|
+
step.check();
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
const value = step.source.kind === "literal" ? step.source.value : inputs[step.key];
|
|
981
|
+
step.validate?.(value);
|
|
982
|
+
if (step.rejectNull && value === null)
|
|
983
|
+
invalid2("SQL comparison and membership inputs cannot be SQL NULL; use isNull");
|
|
984
|
+
let encoded;
|
|
985
|
+
if (step.key !== undefined && encodedInputs.has(step.key))
|
|
986
|
+
encoded = encodedInputs.get(step.key);
|
|
987
|
+
else {
|
|
988
|
+
encoded = snapshotSqlValue(step.encode({ value: snapshotSqlValue(value), dialect: state.dialect }));
|
|
989
|
+
if (step.key !== undefined)
|
|
990
|
+
encodedInputs.set(step.key, encoded);
|
|
991
|
+
}
|
|
992
|
+
step.validate?.(encoded);
|
|
993
|
+
if (step.rejectNull && encoded === null)
|
|
994
|
+
invalid2("SQL comparison and membership encoders cannot produce SQL NULL; use isNull");
|
|
995
|
+
if (encoded !== null && !sameTransport(step.transport, sqlParameterTransport({ value: encoded })))
|
|
996
|
+
unsupported("A reusable SQL encoder produced an undeclared parameter transport shape");
|
|
997
|
+
values.push(snapshotSqlValue(sqlParameterWireValue({ value: encoded })));
|
|
998
|
+
}
|
|
999
|
+
return values;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// packages/sql/src/params.ts
|
|
1003
|
+
class ParamCollector {
|
|
1004
|
+
dialect;
|
|
1005
|
+
reserved = 0;
|
|
1006
|
+
values;
|
|
1007
|
+
constructor(dialect) {
|
|
1008
|
+
this.dialect = dialect;
|
|
1009
|
+
this.values = [];
|
|
1010
|
+
}
|
|
1011
|
+
reserve() {
|
|
1012
|
+
const index = isSqlBindingCollector({ collector: this }) ? ++this.reserved : this.values.length;
|
|
1013
|
+
return this.dialect.placeholder({ index });
|
|
1014
|
+
}
|
|
1015
|
+
count() {
|
|
1016
|
+
return isSqlBindingCollector({ collector: this }) ? this.reserved : this.values.length;
|
|
1017
|
+
}
|
|
1018
|
+
add(value) {
|
|
1019
|
+
const transport = sqlParameterTransport({ value });
|
|
1020
|
+
if (isSqlBindingCollector({ collector: this }))
|
|
1021
|
+
return recordSqlBinding({
|
|
1022
|
+
collector: this,
|
|
1023
|
+
source: { kind: "literal", value },
|
|
1024
|
+
transport,
|
|
1025
|
+
encode: (params) => params.value
|
|
1026
|
+
});
|
|
1027
|
+
if (transport.kind !== "plain" && !this.dialect.capabilities.scalarArrays)
|
|
1028
|
+
throw unsupportedCapability3({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
1029
|
+
this.values.push(sqlParameterWireValue({ value }));
|
|
1030
|
+
return renderSqlParameterTransport({ dialect: this.dialect, placeholder: this.reserve(), transport });
|
|
1031
|
+
}
|
|
1032
|
+
addMany(values) {
|
|
1033
|
+
return values.map((value) => this.add(value));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// packages/sql/src/dialects/external-dependents.ts
|
|
1038
|
+
function renderExternalDependents(params) {
|
|
1039
|
+
if (params.dialect.name !== "postgres")
|
|
1040
|
+
throw unsupportedCapability4({ dialect: params.dialect.name, feature: "externalObjects" });
|
|
1041
|
+
const collector = new ParamCollector(params.dialect);
|
|
1042
|
+
const requests = params.targets.map((target, position) => ({ position, kind: target.kind, namespace: target.namespace, name: target.name, column: target.kind === "column" ? target.column : null }));
|
|
1043
|
+
const request = collector.add(JSON.stringify(requests));
|
|
1044
|
+
const text = `WITH request AS (
|
|
1045
|
+
SELECT * FROM jsonb_to_recordset(${request}::text::jsonb) AS request(position integer, kind text, namespace text, name text, "column" text)
|
|
1046
|
+
), target AS (
|
|
1047
|
+
SELECT request.position, 'pg_type'::regclass AS classid, t.oid AS objid, NULL::integer AS objsubid FROM request JOIN pg_catalog.pg_namespace n ON n.nspname = request.namespace JOIN pg_catalog.pg_type t ON t.typnamespace = n.oid AND t.typname = request.name WHERE request.kind = 'type'
|
|
1048
|
+
UNION ALL SELECT request.position, 'pg_class'::regclass, c.oid, a.attnum::integer FROM request JOIN pg_catalog.pg_namespace n ON n.nspname = request.namespace JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid AND c.relname = request.name LEFT JOIN pg_catalog.pg_attribute a ON request.kind = 'column' AND a.attrelid = c.oid AND a.attname = request."column" AND NOT a.attisdropped WHERE request.kind = 'relation' OR request.kind = 'column' AND a.attnum IS NOT NULL
|
|
1049
|
+
), owned AS (
|
|
1050
|
+
SELECT position, classid, objid, objsubid FROM target
|
|
1051
|
+
UNION SELECT target.position, 'pg_type'::regclass, c.reltype, NULL FROM target JOIN pg_catalog.pg_class c ON c.oid = target.objid WHERE target.classid = 'pg_class'::regclass AND target.objsubid IS NULL AND c.reltype <> 0
|
|
1052
|
+
UNION SELECT target.position, 'pg_class'::regclass, s.oid, NULL FROM target JOIN pg_catalog.pg_depend sd ON sd.refclassid = target.classid AND sd.refobjid = target.objid AND (target.objsubid IS NULL OR sd.refobjsubid = target.objsubid) AND sd.classid = 'pg_class'::regclass AND sd.deptype IN ('a', 'i') JOIN pg_catalog.pg_class s ON s.oid = sd.objid AND s.relkind = 'S'
|
|
1053
|
+
), referenced AS (
|
|
1054
|
+
SELECT position, classid, objid, objsubid FROM owned
|
|
1055
|
+
UNION SELECT owned.position, 'pg_type'::regclass, t.typarray, NULL FROM owned JOIN pg_catalog.pg_type t ON t.oid = owned.objid WHERE owned.classid = 'pg_type'::regclass AND t.typarray <> 0
|
|
1056
|
+
)
|
|
1057
|
+
SELECT DISTINCT referenced.position, dependent.type AS "dependentType", dependent.identity AS "dependentIdentity", ref.identity AS referenced, d.classid::regclass::text AS "dependentClass",
|
|
1058
|
+
rn.nspname::text AS "relationNamespace", rel.relname::text AS "relationName", rel.relkind::text AS "relationKind", col.attname::text AS "columnName",
|
|
1059
|
+
COALESCE(pn.nspname, tn.nspname, dependent.schema)::text AS "objectNamespace", COALESCE(p.proname, ty.typname)::text AS "objectName"
|
|
1060
|
+
FROM referenced
|
|
1061
|
+
JOIN pg_catalog.pg_depend d ON d.refclassid = referenced.classid AND d.refobjid = referenced.objid AND (referenced.objsubid IS NULL OR d.refobjsubid = referenced.objsubid) AND d.deptype = 'n'
|
|
1062
|
+
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) dependent
|
|
1063
|
+
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.refclassid, d.refobjid, d.refobjsubid) ref
|
|
1064
|
+
LEFT JOIN pg_catalog.pg_class self ON d.classid = 'pg_class'::regclass AND self.oid = d.objid
|
|
1065
|
+
LEFT JOIN pg_catalog.pg_index ix ON ix.indexrelid = self.oid
|
|
1066
|
+
LEFT JOIN pg_catalog.pg_attrdef ad ON d.classid = 'pg_attrdef'::regclass AND ad.oid = d.objid
|
|
1067
|
+
LEFT JOIN pg_catalog.pg_rewrite rw ON d.classid = 'pg_rewrite'::regclass AND rw.oid = d.objid
|
|
1068
|
+
LEFT JOIN pg_catalog.pg_trigger tg ON d.classid = 'pg_trigger'::regclass AND tg.oid = d.objid
|
|
1069
|
+
LEFT JOIN pg_catalog.pg_constraint con ON d.classid = 'pg_constraint'::regclass AND con.oid = d.objid
|
|
1070
|
+
LEFT JOIN pg_catalog.pg_policy pol ON d.classid = 'pg_policy'::regclass AND pol.oid = d.objid
|
|
1071
|
+
LEFT JOIN pg_catalog.pg_type ty ON d.classid = 'pg_type'::regclass AND ty.oid = d.objid
|
|
1072
|
+
LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = ty.typnamespace
|
|
1073
|
+
LEFT JOIN pg_catalog.pg_proc p ON d.classid = 'pg_proc'::regclass AND p.oid = d.objid
|
|
1074
|
+
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
1075
|
+
LEFT JOIN pg_catalog.pg_class rel ON rel.oid = COALESCE(ix.indrelid, self.oid, ad.adrelid, rw.ev_class, tg.tgrelid, NULLIF(con.conrelid, 0), pol.polrelid, NULLIF(ty.typrelid, 0))
|
|
1076
|
+
LEFT JOIN pg_catalog.pg_namespace rn ON rn.oid = rel.relnamespace
|
|
1077
|
+
LEFT JOIN pg_catalog.pg_attribute col ON col.attrelid = rel.oid AND col.attnum = CASE WHEN self.oid IS NOT NULL AND d.objsubid > 0 THEN d.objsubid WHEN ad.oid IS NOT NULL THEN ad.adnum END
|
|
1078
|
+
ORDER BY 1, 2, 3, 4`;
|
|
1079
|
+
return { text, values: collector.values, returning: "none" };
|
|
1080
|
+
}
|
|
1081
|
+
// packages/sql/src/dialects/postgres-physical-catalog.ts
|
|
1082
|
+
import { unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
1083
|
+
function renderPostgresPhysicalCatalog(params) {
|
|
1084
|
+
const { dialect } = params;
|
|
1085
|
+
if (dialect.name !== "postgres")
|
|
1086
|
+
throw unsupportedCapability5({ dialect: dialect.name, feature: "postgresPhysicalCatalog" });
|
|
1087
|
+
const collector = new ParamCollector(dialect);
|
|
1088
|
+
const namespace = collector.add(params.namespace);
|
|
1089
|
+
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)`;
|
|
1090
|
+
let text;
|
|
1091
|
+
switch (params.query) {
|
|
1092
|
+
case "tables":
|
|
1093
|
+
text = `SELECT table_name FROM information_schema.tables WHERE table_schema = ${namespace} AND table_type = 'BASE TABLE' AND table_name <> '_vibe_migrations' AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
1094
|
+
break;
|
|
1095
|
+
case "views":
|
|
1096
|
+
text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
1097
|
+
break;
|
|
1098
|
+
case "columns":
|
|
1099
|
+
text = `SELECT table_name, column_name, data_type, udt_name, udt_schema, is_nullable, is_identity, is_generated, collation_name, column_default, datetime_precision, character_maximum_length, numeric_precision, numeric_scale, ordinal_position, (SELECT format_type(a.atttypid, a.atttypmod) FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND c.relname = columns.table_name AND a.attname = columns.column_name) AS full_type FROM information_schema.columns AS columns WHERE table_schema = ${namespace} ORDER BY table_name, ordinal_position`;
|
|
1100
|
+
break;
|
|
1101
|
+
case "constraints":
|
|
1102
|
+
text = `SELECT c.conname AS constraint_name, CASE c.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type, cl.relname AS table_name, att.attname AS column_name FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord) ON true JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = k.attnum WHERE c.contype IN ('p', 'u') AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, k.ord`;
|
|
1103
|
+
break;
|
|
1104
|
+
case "checks":
|
|
1105
|
+
text = `SELECT cl.relname AS table_name, c.conname AS constraint_name, pg_get_constraintdef(c.oid) AS definition FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace WHERE c.contype = 'c' AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname`;
|
|
1106
|
+
break;
|
|
1107
|
+
case "foreignKeys":
|
|
1108
|
+
text = `SELECT c.conname AS constraint_name, cl.relname AS table_name, att.attname AS column_name, fn.nspname AS foreign_namespace, fcl.relname AS foreign_table_name, fatt.attname AS foreign_column_name, c.confdeltype AS on_delete_char, c.confupdtype AS on_update_char, c.convalidated AS validated, c.condeferrable AS deferrable, (SELECT bool_and(t.tgenabled IN ('O', 'A')) FROM pg_catalog.pg_trigger t WHERE t.tgconstraint = c.oid) AS triggers_enabled FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace JOIN pg_catalog.pg_class fcl ON fcl.oid = c.confrelid JOIN pg_catalog.pg_namespace fn ON fn.oid = fcl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true JOIN unnest(c.confkey) WITH ORDINALITY AS rk(attnum, ord) ON lk.ord = rk.ord JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = lk.attnum JOIN pg_catalog.pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = rk.attnum WHERE c.contype = 'f' AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, lk.ord`;
|
|
1109
|
+
break;
|
|
1110
|
+
case "enums":
|
|
1111
|
+
text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_type'::regclass AND d.objid = t.oid AND d.deptype = 'e') ORDER BY t.typname, e.enumsortorder`;
|
|
1112
|
+
break;
|
|
1113
|
+
case "indexes":
|
|
1114
|
+
text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
return { text, values: collector.values, returning: "none" };
|
|
1118
|
+
}
|
|
1119
|
+
// packages/sql/src/dialects/postgres-index-keys.ts
|
|
1120
|
+
import { unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
1121
|
+
function renderPostgresIndexKeys(params) {
|
|
1122
|
+
const { dialect } = params;
|
|
1123
|
+
if (dialect.name !== "postgres")
|
|
1124
|
+
throw unsupportedCapability6({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
1125
|
+
const collector = new ParamCollector(dialect);
|
|
1126
|
+
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(", ")})`;
|
|
1127
|
+
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`;
|
|
1128
|
+
return {
|
|
1129
|
+
text: `SELECT n.nspname AS table_namespace, c.relname AS table_name, idx.relname AS index_name, am.amname AS method, i.indisunique AS unique, pg_catalog.pg_get_expr(i.indpred, i.indrelid, false) AS predicate, i.indnullsnotdistinct AS nulls_not_distinct, k.ordinality AS ordinal, a.attname AS column_name, (i.indoption[k.ordinality - 1] & 1) <> 0 AS descending, opn.nspname AS operator_namespace, op.opcname AS operator_name, COALESCE(op.oid = resolved_default.oid, false) AS exact_default FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON c.oid = i.indrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN pg_catalog.pg_class idx ON idx.oid = i.indexrelid JOIN pg_catalog.pg_am am ON am.oid = idx.relam CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, ordinality) LEFT JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = k.attnum JOIN pg_catalog.pg_opclass op ON op.oid = i.indclass[k.ordinality - 1] JOIN pg_catalog.pg_namespace opn ON opn.oid = op.opcnamespace ${defaultResolution} WHERE ${scope} AND k.ordinality <= i.indnkeyatts ORDER BY n.nspname, c.relname, idx.relname, k.ordinality`,
|
|
1130
|
+
values: collector.values,
|
|
1131
|
+
returning: "none"
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
// packages/sql/src/index-columns.ts
|
|
1135
|
+
import { VibeError as VibeError6, isSqlObjectIdentity, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
1136
|
+
function renderIndexColumns(params) {
|
|
1137
|
+
const { dialect, columns, unique } = params;
|
|
1138
|
+
const hasOperatorClass = columns.some((column) => column.operatorClass !== undefined);
|
|
1139
|
+
if (hasOperatorClass && !dialect.capabilities.indexOperatorClasses)
|
|
1140
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
1141
|
+
if (hasOperatorClass && unique)
|
|
1142
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "uniqueIndexOperatorClasses", hint: "custom unique operator classes can change client selector equality" });
|
|
1143
|
+
return columns.map((column) => {
|
|
1144
|
+
const operatorClass = column.operatorClass;
|
|
1145
|
+
if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass })) {
|
|
1146
|
+
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 } });
|
|
1147
|
+
}
|
|
1148
|
+
const ops = operatorClass === undefined ? "" : ` ${dialect.quoteIdent({ name: operatorClass.namespace })}.${dialect.quoteIdent({ name: operatorClass.name })}`;
|
|
1149
|
+
return `${dialect.quoteIdent({ name: column.name })}${ops}${column.desc ? " DESC" : ""}`;
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
// packages/sql/src/dialects/module-dependency.ts
|
|
1153
|
+
function renderModuleDependencyQuery(params) {
|
|
1154
|
+
const { dialect, operation } = params;
|
|
1155
|
+
if (operation.kind === "visibility")
|
|
194
1156
|
return dialect.name === "mysql" ? { text: "SHOW GRANTS FOR CURRENT_USER", values: [], returning: "none" } : undefined;
|
|
195
1157
|
if (dialect.name === "sqlite" || operation.tables.length === 0)
|
|
196
1158
|
return;
|
|
@@ -233,7 +1195,7 @@ UNION ALL SELECT DISTINCT 'partition', TABLE_NAME, TABLE_SCHEMA, NULL FROM infor
|
|
|
233
1195
|
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
1196
|
}
|
|
235
1197
|
// packages/sql/src/dialects/migration-shadow.ts
|
|
236
|
-
import { assertSqlNamespace, unsupportedCapability as
|
|
1198
|
+
import { assertSqlNamespace, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
237
1199
|
var SHADOW_DATABASE = Object.freeze({ schema: "_vibeorm_shadow", table: "_vibe_shadow", lock: 1447645267, timeoutSeconds: 60 });
|
|
238
1200
|
var SHADOW_CAPABILITIES = Object.freeze({
|
|
239
1201
|
postgres: Object.freeze({ serverProvisioning: true, dedicated: true }),
|
|
@@ -243,7 +1205,7 @@ var SHADOW_CAPABILITIES = Object.freeze({
|
|
|
243
1205
|
function renderShadowOperation(params) {
|
|
244
1206
|
const { dialect, operation } = params;
|
|
245
1207
|
if (!SHADOW_CAPABILITIES[dialect.name].serverProvisioning)
|
|
246
|
-
throw
|
|
1208
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "shadowDatabaseProvisioning" });
|
|
247
1209
|
const pg = dialect.name === "postgres";
|
|
248
1210
|
const collector = new ParamCollector(dialect);
|
|
249
1211
|
const marker = pg ? `${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}.${dialect.quoteIdent({ name: SHADOW_DATABASE.table })}` : dialect.quoteIdent({ name: SHADOW_DATABASE.table });
|
|
@@ -268,7 +1230,7 @@ function renderShadowOperation(params) {
|
|
|
268
1230
|
ORDER BY ordinal_position`);
|
|
269
1231
|
case "grants": {
|
|
270
1232
|
if (pg)
|
|
271
|
-
throw
|
|
1233
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
|
|
272
1234
|
return result("SHOW GRANTS FOR CURRENT_USER");
|
|
273
1235
|
}
|
|
274
1236
|
case "markerCreate":
|
|
@@ -299,7 +1261,7 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
|
|
|
299
1261
|
case "resetPostgres":
|
|
300
1262
|
case "resetDedicatedPostgres": {
|
|
301
1263
|
if (!pg)
|
|
302
|
-
throw
|
|
1264
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "postgresShadowReset" });
|
|
303
1265
|
const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
|
|
304
1266
|
for (const namespace of owned)
|
|
305
1267
|
assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
|
|
@@ -311,12 +1273,12 @@ UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME
|
|
|
311
1273
|
}
|
|
312
1274
|
case "mysqlForeignKeys": {
|
|
313
1275
|
if (pg)
|
|
314
|
-
throw
|
|
1276
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
315
1277
|
return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
|
|
316
1278
|
}
|
|
317
1279
|
case "dropMysqlObject": {
|
|
318
1280
|
if (pg)
|
|
319
|
-
throw
|
|
1281
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
320
1282
|
return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
|
|
321
1283
|
}
|
|
322
1284
|
}
|
|
@@ -358,10 +1320,10 @@ import {
|
|
|
358
1320
|
} from "@vibeorm/schema";
|
|
359
1321
|
|
|
360
1322
|
// packages/sql/src/identifiers.ts
|
|
361
|
-
import { VibeError as
|
|
1323
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
362
1324
|
function assertNoNul(params) {
|
|
363
1325
|
if (params.name.includes("\x00")) {
|
|
364
|
-
throw new
|
|
1326
|
+
throw new VibeError7({
|
|
365
1327
|
code: "VIBE_VALIDATION",
|
|
366
1328
|
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
367
1329
|
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
@@ -402,6 +1364,7 @@ var sqliteDialect = {
|
|
|
402
1364
|
"array_ends_with"
|
|
403
1365
|
],
|
|
404
1366
|
jsonParamCast: "",
|
|
1367
|
+
valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "INTEGER", Int: "INTEGER", BigInt: "INTEGER", Float: "REAL", Decimal: "TEXT", DateTime: "TEXT", Json: "TEXT", Bytes: "BLOB" }),
|
|
405
1368
|
lastInsertIdSql: null,
|
|
406
1369
|
rowLockSyntax: null,
|
|
407
1370
|
advisoryLockFunctions: null,
|
|
@@ -410,792 +1373,442 @@ var sqliteDialect = {
|
|
|
410
1373
|
paramBudget: 900,
|
|
411
1374
|
booleanLiteral: { true: "1", false: "0" },
|
|
412
1375
|
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]) })]);
|
|
1376
|
+
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
620
1377
|
},
|
|
621
|
-
|
|
622
|
-
|
|
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);
|
|
1378
|
+
quoteStringLiteral({ value }) {
|
|
1379
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
633
1380
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
return construct([Object.freeze({ row: "existing", column })]);
|
|
1381
|
+
placeholder() {
|
|
1382
|
+
return "?";
|
|
637
1383
|
},
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
return construct([Object.freeze({ row: "incoming", column })]);
|
|
1384
|
+
jsonTextProjection({ expression }) {
|
|
1385
|
+
return expression;
|
|
641
1386
|
},
|
|
642
|
-
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
return
|
|
1387
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
1388
|
+
if (offsetSeconds === 0)
|
|
1389
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1390
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
646
1391
|
}
|
|
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 })}`;
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
1395
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
1396
|
+
function tableDefinitionParts(params) {
|
|
1397
|
+
const sql = params.sql;
|
|
1398
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
1399
|
+
throw unavailable();
|
|
1400
|
+
const parts = [];
|
|
1401
|
+
let start = -1;
|
|
1402
|
+
let depth = 0;
|
|
1403
|
+
let quote;
|
|
1404
|
+
for (let index = 0;index < sql.length; index++) {
|
|
1405
|
+
const char = sql[index];
|
|
1406
|
+
if (quote !== undefined) {
|
|
1407
|
+
if (char === quote) {
|
|
1408
|
+
if (quote !== "]" && sql[index + 1] === quote)
|
|
1409
|
+
index++;
|
|
1410
|
+
else
|
|
1411
|
+
quote = undefined;
|
|
1412
|
+
}
|
|
1413
|
+
continue;
|
|
681
1414
|
}
|
|
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)`));
|
|
1415
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
1416
|
+
quote = char;
|
|
1417
|
+
continue;
|
|
686
1418
|
}
|
|
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 });
|
|
1419
|
+
if (char === "[") {
|
|
1420
|
+
quote = "]";
|
|
1421
|
+
continue;
|
|
701
1422
|
}
|
|
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++;
|
|
1423
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
1424
|
+
const end = sql.indexOf(`
|
|
1425
|
+
`, index + 2);
|
|
1426
|
+
if (end < 0)
|
|
1427
|
+
break;
|
|
1428
|
+
index = end;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
1432
|
+
const end = sql.indexOf("*/", index + 2);
|
|
1433
|
+
if (end < 0)
|
|
1434
|
+
break;
|
|
1435
|
+
index = end + 1;
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (char === "(") {
|
|
1439
|
+
if (depth === 0)
|
|
1440
|
+
start = index + 1;
|
|
1441
|
+
depth++;
|
|
1442
|
+
} else if (char === ")") {
|
|
1443
|
+
depth--;
|
|
1444
|
+
if (depth === 0 && start >= 0) {
|
|
1445
|
+
parts.push(sql.slice(start, index).trim());
|
|
1446
|
+
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
761
1447
|
}
|
|
762
|
-
|
|
1448
|
+
} else if (char === "," && depth === 1) {
|
|
1449
|
+
parts.push(sql.slice(start, index).trim());
|
|
1450
|
+
start = index + 1;
|
|
763
1451
|
}
|
|
764
|
-
if (inner[index] === ",")
|
|
765
|
-
index++;
|
|
766
1452
|
}
|
|
767
|
-
|
|
1453
|
+
throw unavailable();
|
|
768
1454
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
771
|
-
const cursor = { index: 0 };
|
|
772
|
-
const parsed = parseDimension({ text, cursor });
|
|
773
|
-
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
1455
|
+
function unavailable() {
|
|
1456
|
+
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
774
1457
|
}
|
|
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++;
|
|
1458
|
+
function sqliteColumnClauses(params) {
|
|
1459
|
+
const columns = [];
|
|
1460
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
1461
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
1462
|
+
if (token === null)
|
|
1463
|
+
throw unavailable();
|
|
1464
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
814
1465
|
continue;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
return null;
|
|
818
|
-
cursor.index++;
|
|
819
|
-
return out;
|
|
1466
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
1467
|
+
columns.push({ name, sql: part });
|
|
820
1468
|
}
|
|
821
|
-
return
|
|
1469
|
+
return columns;
|
|
822
1470
|
}
|
|
823
1471
|
|
|
824
|
-
// packages/sql/src/
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
const
|
|
828
|
-
const
|
|
1472
|
+
// packages/sql/src/dialects/sqlite-fts.ts
|
|
1473
|
+
function updateStatements(params) {
|
|
1474
|
+
const quote = (name) => sqliteDialect.quoteIdent({ name });
|
|
1475
|
+
const columns = params.columns.map((column) => quote(column)).join(", ");
|
|
1476
|
+
const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
1477
|
+
const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
1478
|
+
const changes = [
|
|
1479
|
+
"old.rowid IS NOT new.rowid",
|
|
1480
|
+
...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
|
|
1481
|
+
].join(" OR ");
|
|
1482
|
+
const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
|
|
1483
|
+
const body = `BEGIN
|
|
1484
|
+
INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
|
|
1485
|
+
INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
|
|
1486
|
+
END`;
|
|
1487
|
+
const tail = `${target} WHEN (${changes}) ${body}`;
|
|
829
1488
|
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
|
-
}
|
|
1489
|
+
create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
|
|
1490
|
+
definition: `CREATE TRIGGER ${tail}`,
|
|
1491
|
+
legacyDefinition: `CREATE TRIGGER ${target} ${body}`
|
|
844
1492
|
};
|
|
845
1493
|
}
|
|
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;
|
|
1494
|
+
function renderSqliteFtsUpdateTrigger(params) {
|
|
1495
|
+
const { create, definition } = updateStatements(params);
|
|
1496
|
+
return { create, definition };
|
|
857
1497
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
};
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1498
|
+
function sqliteFtsUpdateUpgrade(params) {
|
|
1499
|
+
const quoted = '"(?:[^"]|"")+"';
|
|
1500
|
+
const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
|
|
1501
|
+
for (const match of params.create.matchAll(body)) {
|
|
1502
|
+
const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
|
|
1503
|
+
const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
|
|
1504
|
+
if (params.name !== `${ftsTable}_au`)
|
|
1505
|
+
continue;
|
|
1506
|
+
const statements = updateStatements({ table: params.table, ftsTable, columns });
|
|
1507
|
+
if (params.create !== statements.create)
|
|
1508
|
+
continue;
|
|
1509
|
+
const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
|
|
1510
|
+
if (installed === undefined)
|
|
1511
|
+
continue;
|
|
1512
|
+
if (params.tableDefinition === undefined)
|
|
1513
|
+
return { kind: "tableRequired", ftsTable };
|
|
1514
|
+
const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
|
|
1515
|
+
if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
|
|
1516
|
+
continue;
|
|
1517
|
+
return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
|
|
874
1518
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
// packages/sql/src/dialects/sqlite-session.ts
|
|
1522
|
+
var SQLITE_SESSION_SQL = Object.freeze({
|
|
1523
|
+
lockingMode: "PRAGMA main.locking_mode",
|
|
1524
|
+
exclusive: "PRAGMA main.locking_mode = EXCLUSIVE",
|
|
1525
|
+
normal: "PRAGMA main.locking_mode = NORMAL",
|
|
1526
|
+
acquire: "BEGIN EXCLUSIVE",
|
|
1527
|
+
commit: "COMMIT",
|
|
1528
|
+
rollback: "ROLLBACK",
|
|
1529
|
+
touch: "SELECT name FROM main.sqlite_schema LIMIT 1",
|
|
1530
|
+
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
1531
|
+
});
|
|
1532
|
+
// packages/sql/src/composable.ts
|
|
1533
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1534
|
+
var fragments = new WeakMap;
|
|
1535
|
+
registerSqlHandleCheck({ check: (value) => fragments.has(value) });
|
|
1536
|
+
function invalid3(message) {
|
|
1537
|
+
throw new VibeError9({ code: "VIBE_VALIDATION", message });
|
|
1538
|
+
}
|
|
1539
|
+
function construct(parts) {
|
|
1540
|
+
const snapshot = Object.freeze([...parts]);
|
|
1541
|
+
const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
|
|
1542
|
+
value: () => ({ parts: snapshot })
|
|
1543
|
+
}));
|
|
1544
|
+
fragments.set(fragment, snapshot);
|
|
1545
|
+
return fragment;
|
|
1546
|
+
}
|
|
1547
|
+
function isSqlFragment(value) {
|
|
1548
|
+
return typeof value === "object" && value !== null && fragments.has(value);
|
|
1549
|
+
}
|
|
1550
|
+
function partsOf(value) {
|
|
1551
|
+
return fragments.get(value) ?? invalid3("Expected a constructed SQL fragment");
|
|
1552
|
+
}
|
|
1553
|
+
function containsGreatest(fragment) {
|
|
1554
|
+
return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
|
|
1555
|
+
}
|
|
1556
|
+
function identifier(parts) {
|
|
1557
|
+
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
1558
|
+
invalid3("SQL identifiers require non-empty, NUL-free name components");
|
|
887
1559
|
}
|
|
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;
|
|
1560
|
+
}
|
|
1561
|
+
function template(strings, ...values) {
|
|
1562
|
+
if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
|
|
1563
|
+
invalid3("Use sql as a template tag");
|
|
897
1564
|
}
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1565
|
+
const parts = [];
|
|
1566
|
+
for (let index = 0;index < strings.length; index++) {
|
|
1567
|
+
parts.push(strings[index]);
|
|
1568
|
+
if (index < values.length) {
|
|
1569
|
+
const value = values[index];
|
|
1570
|
+
if (isSqlFragment(value))
|
|
1571
|
+
parts.push(...partsOf(value));
|
|
1572
|
+
else
|
|
1573
|
+
parts.push(Object.freeze({ value: snapshotSqlValue(value) }));
|
|
1574
|
+
}
|
|
904
1575
|
}
|
|
905
|
-
|
|
906
|
-
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
1576
|
+
return construct(parts);
|
|
907
1577
|
}
|
|
908
|
-
var
|
|
909
|
-
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
return value;
|
|
1578
|
+
var sql = Object.freeze(Object.assign(template, {
|
|
1579
|
+
text: ({ text }) => {
|
|
1580
|
+
if (typeof text !== "string" || text.includes("\x00"))
|
|
1581
|
+
invalid3("SQL text must be a NUL-free string");
|
|
1582
|
+
return construct([text]);
|
|
914
1583
|
},
|
|
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" });
|
|
1584
|
+
ident: ({ parts }) => {
|
|
1585
|
+
identifier(parts);
|
|
1586
|
+
return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
|
|
929
1587
|
},
|
|
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;
|
|
1588
|
+
value: ({ value, decoder }) => construct([Object.freeze({ value: snapshotSqlValue(value), ...decoder === undefined ? {} : { decoder } })]),
|
|
1589
|
+
join: ({ items, separator }) => {
|
|
1590
|
+
if (!Array.isArray(items))
|
|
1591
|
+
invalid3("sql.join requires an array");
|
|
1592
|
+
const between = separator === undefined ? [", "] : partsOf(separator);
|
|
1593
|
+
const parts = [];
|
|
1594
|
+
items.forEach((value, index) => {
|
|
1595
|
+
if (index > 0)
|
|
1596
|
+
parts.push(...between);
|
|
1597
|
+
parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value: snapshotSqlValue(value) })]);
|
|
1598
|
+
});
|
|
1599
|
+
return construct(parts);
|
|
946
1600
|
},
|
|
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
|
-
|
|
1601
|
+
existing: ({ column }) => {
|
|
1602
|
+
identifier([column]);
|
|
1603
|
+
return construct([Object.freeze({ row: "existing", column })]);
|
|
1604
|
+
},
|
|
1605
|
+
incoming: ({ column }) => {
|
|
1606
|
+
identifier([column]);
|
|
1607
|
+
return construct([Object.freeze({ row: "incoming", column })]);
|
|
1608
|
+
},
|
|
1609
|
+
greatest: ({ values }) => {
|
|
1610
|
+
if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
|
|
1611
|
+
invalid3("sql.greatest requires two SQL fragments");
|
|
1612
|
+
return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
|
|
1613
|
+
}
|
|
1614
|
+
}));
|
|
1615
|
+
function renderSqlFragment(params) {
|
|
1616
|
+
const { dialect, collector, conflict } = params;
|
|
1617
|
+
const render = (fragment) => renderSqlFragment({ ...params, fragment });
|
|
1618
|
+
const encode = (part) => {
|
|
1619
|
+
if (part.value === undefined)
|
|
1620
|
+
invalid3("SQL parameters cannot be undefined; use null explicitly");
|
|
1621
|
+
return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: snapshotSqlValue(part.value), dialect: dialect.name }));
|
|
1622
|
+
};
|
|
1623
|
+
const recording = isSqlBindingCollector({ collector });
|
|
1624
|
+
const recordValue = (part) => recordSqlBinding({
|
|
1625
|
+
collector,
|
|
1626
|
+
source: { kind: "literal", value: part.value },
|
|
1627
|
+
transport: part.decoder === undefined ? sqlParameterTransport({ value: part.value }) : { kind: "plain" },
|
|
1628
|
+
encode: ({ value, dialect: target }) => part.decoder === undefined ? value : part.decoder.encode({ value, dialect: target.name })
|
|
1629
|
+
});
|
|
1630
|
+
const frameOperand = (fragment) => {
|
|
1631
|
+
const parts = partsOf(fragment).filter((part) => part !== "");
|
|
1632
|
+
const only = parts.length === 1 ? parts[0] : undefined;
|
|
1633
|
+
if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
|
|
1634
|
+
if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
|
|
1635
|
+
throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
|
|
1636
|
+
if (only.value === null) {
|
|
1637
|
+
if (only.decoder !== undefined)
|
|
1638
|
+
recordSqlBindingCheck({ collector, check: () => {
|
|
1639
|
+
if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
|
|
1640
|
+
throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
|
|
1641
|
+
} });
|
|
1642
|
+
return "NULL";
|
|
976
1643
|
}
|
|
977
|
-
|
|
1644
|
+
const text = recordValue(only);
|
|
1645
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
1646
|
+
}
|
|
1647
|
+
const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
|
|
1648
|
+
if (value !== undefined && value.encoded === null)
|
|
1649
|
+
return "NULL";
|
|
1650
|
+
const text = value === undefined ? render(fragment) : collector.add(value.encoded);
|
|
1651
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
1652
|
+
};
|
|
1653
|
+
return partsOf(params.fragment).map((part) => {
|
|
1654
|
+
if (typeof part === "string")
|
|
1655
|
+
return part;
|
|
1656
|
+
if ("value" in part)
|
|
1657
|
+
return recording ? recordValue(part) : collector.add(encode(part));
|
|
1658
|
+
if ("ident" in part)
|
|
1659
|
+
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
1660
|
+
if ("row" in part) {
|
|
1661
|
+
if (conflict === undefined)
|
|
1662
|
+
invalid3("Existing/incoming row references require a conflict update");
|
|
1663
|
+
const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
|
|
1664
|
+
if (name === undefined)
|
|
1665
|
+
invalid3(`Unknown conflict-expression field "${part.column}"`);
|
|
1666
|
+
const ref = dialect.excludedRowRef;
|
|
1667
|
+
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
1668
|
+
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
978
1669
|
}
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1670
|
+
if (dialect.greatestForm === "native")
|
|
1671
|
+
return `GREATEST(${part.greatest.map(render).join(", ")})`;
|
|
1672
|
+
if (!part.greatest.some(containsGreatest)) {
|
|
1673
|
+
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)`));
|
|
1674
|
+
}
|
|
1675
|
+
const [left, right] = part.greatest.map(frameOperand);
|
|
1676
|
+
const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
|
|
1677
|
+
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})`;
|
|
1678
|
+
}).join("");
|
|
986
1679
|
}
|
|
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
|
-
});
|
|
1680
|
+
function mapConflictColumns(params) {
|
|
1681
|
+
return construct(partsOf(params.fragment).map((part) => {
|
|
1682
|
+
if (typeof part === "string")
|
|
1683
|
+
return part;
|
|
1684
|
+
if ("row" in part) {
|
|
1685
|
+
const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
|
|
1686
|
+
if (column === undefined)
|
|
1687
|
+
invalid3(`Unknown conflict-expression field "${part.column}"`);
|
|
1688
|
+
return Object.freeze({ ...part, column });
|
|
1009
1689
|
}
|
|
1010
|
-
|
|
1011
|
-
};
|
|
1012
|
-
|
|
1690
|
+
if ("greatest" in part)
|
|
1691
|
+
return Object.freeze({ greatest: Object.freeze(part.greatest.map((fragment) => mapConflictColumns({ ...params, fragment }))) });
|
|
1692
|
+
return part;
|
|
1693
|
+
}));
|
|
1694
|
+
}
|
|
1695
|
+
function compileSql(params) {
|
|
1696
|
+
const collector = new ParamCollector(params.dialect);
|
|
1697
|
+
const text = renderSqlFragment({ dialect: params.dialect, fragment: params.query, collector });
|
|
1698
|
+
return { text, values: collector.values };
|
|
1699
|
+
}
|
|
1700
|
+
function sqlUsesIncoming(params) {
|
|
1701
|
+
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
1702
|
+
}
|
|
1703
|
+
// packages/sql/src/query-plan.ts
|
|
1704
|
+
import { VibeError as VibeError10 } from "@vibeorm/schema";
|
|
1705
|
+
var queries = new WeakMap;
|
|
1706
|
+
var commands = new WeakMap;
|
|
1707
|
+
var reusableQueries = new WeakMap;
|
|
1708
|
+
var reusableCommands = new WeakMap;
|
|
1709
|
+
registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
|
|
1710
|
+
function invalid4(message) {
|
|
1711
|
+
throw new VibeError10({ code: "VIBE_VALIDATION", message });
|
|
1712
|
+
}
|
|
1713
|
+
function createSqlQuery(params) {
|
|
1714
|
+
if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
|
|
1715
|
+
invalid4("SQL queries require a renderer and result decoder");
|
|
1716
|
+
const query = Object.freeze(Object.create(null));
|
|
1717
|
+
queries.set(query, Object.freeze({ render: params.render, decoder: params.decoder }));
|
|
1718
|
+
return query;
|
|
1719
|
+
}
|
|
1720
|
+
function createSqlCommand(params) {
|
|
1721
|
+
if (typeof params.render !== "function")
|
|
1722
|
+
invalid4("SQL commands require a renderer");
|
|
1723
|
+
const command = Object.freeze(Object.create(null));
|
|
1724
|
+
commands.set(command, params.render);
|
|
1725
|
+
return command;
|
|
1726
|
+
}
|
|
1727
|
+
function isSqlQuery(value) {
|
|
1728
|
+
return typeof value === "object" && value !== null && queries.has(value);
|
|
1729
|
+
}
|
|
1730
|
+
function isSqlCommand(value) {
|
|
1731
|
+
return typeof value === "object" && value !== null && commands.has(value);
|
|
1732
|
+
}
|
|
1733
|
+
function isReusableSqlQuery(value) {
|
|
1734
|
+
return typeof value === "object" && value !== null && reusableQueries.has(value);
|
|
1735
|
+
}
|
|
1736
|
+
function isReusableSqlCommand(value) {
|
|
1737
|
+
return typeof value === "object" && value !== null && reusableCommands.has(value);
|
|
1738
|
+
}
|
|
1739
|
+
function createReusableSqlQuery(params) {
|
|
1740
|
+
const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
|
|
1741
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
1742
|
+
const query = Object.freeze(Object.create(null));
|
|
1743
|
+
reusableQueries.set(query, { ...state, scope: params.scope, cache: new WeakMap });
|
|
1744
|
+
return query;
|
|
1745
|
+
}
|
|
1746
|
+
function createReusableSqlCommand(params) {
|
|
1747
|
+
const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
|
|
1748
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
1749
|
+
const query = Object.freeze(Object.create(null));
|
|
1750
|
+
reusableCommands.set(query, { render, scope: params.scope, cache: new WeakMap });
|
|
1751
|
+
return query;
|
|
1752
|
+
}
|
|
1753
|
+
function compiledTemplate(params) {
|
|
1754
|
+
const cached = params.state.cache.get(params.dialect);
|
|
1755
|
+
if (cached !== undefined)
|
|
1756
|
+
return cached;
|
|
1757
|
+
const collector = new ParamCollector(params.dialect);
|
|
1758
|
+
startSqlBindingCollection({ collector, dialect: params.dialect, scope: params.state.scope });
|
|
1759
|
+
const text = params.state.render({ dialect: params.dialect, collector });
|
|
1760
|
+
if (collector.count() > params.dialect.paramBudget)
|
|
1761
|
+
invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1762
|
+
const program = finishSqlBindingCollection({ collector });
|
|
1763
|
+
const compiled = Object.freeze({ text, program });
|
|
1764
|
+
params.state.cache.set(params.dialect, compiled);
|
|
1765
|
+
return compiled;
|
|
1766
|
+
}
|
|
1767
|
+
function executionHandle(params) {
|
|
1013
1768
|
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;
|
|
1769
|
+
return queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) || isSqlFragment(value) || isSqlInput(value) || sqlInputScopeHandle(params);
|
|
1023
1770
|
}
|
|
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
|
-
}
|
|
1771
|
+
function bindTemplate(params) {
|
|
1772
|
+
const { scope } = params.state;
|
|
1773
|
+
return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
|
|
1044
1774
|
}
|
|
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 });
|
|
1775
|
+
function compileReusableSqlQuery(params) {
|
|
1776
|
+
const state = reusableQueries.get(params.query) ?? invalid4("Expected an authentic reusable SQL row query");
|
|
1777
|
+
const compiled = compiledTemplate({ dialect: params.dialect, state });
|
|
1778
|
+
return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }), decoder: state.decoder };
|
|
1087
1779
|
}
|
|
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
|
-
});
|
|
1780
|
+
function compileReusableSqlCommand(params) {
|
|
1781
|
+
const state = reusableCommands.get(params.query) ?? invalid4("Expected an authentic reusable SQL command");
|
|
1782
|
+
const compiled = compiledTemplate({ dialect: params.dialect, state });
|
|
1783
|
+
return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }) };
|
|
1784
|
+
}
|
|
1785
|
+
function compileSqlQuery(params) {
|
|
1786
|
+
const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
|
|
1787
|
+
const collector = new ParamCollector(params.dialect);
|
|
1788
|
+
const text = state.render({ dialect: params.dialect, collector });
|
|
1789
|
+
if (collector.values.length > params.dialect.paramBudget)
|
|
1790
|
+
invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1791
|
+
return { text, values: collector.values, decoder: state.decoder };
|
|
1792
|
+
}
|
|
1793
|
+
function compileSqlCommand(params) {
|
|
1794
|
+
const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
|
|
1795
|
+
const collector = new ParamCollector(params.dialect);
|
|
1796
|
+
const text = render({ dialect: params.dialect, collector });
|
|
1797
|
+
if (collector.values.length > params.dialect.paramBudget)
|
|
1798
|
+
invalid4("SQL command exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1799
|
+
return { text, values: collector.values };
|
|
1118
1800
|
}
|
|
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
1801
|
// packages/sql/src/exclusion.ts
|
|
1190
|
-
import { VibeError as
|
|
1802
|
+
import { VibeError as VibeError12, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
1191
1803
|
|
|
1192
1804
|
// packages/sql/src/predicate.ts
|
|
1193
|
-
import { VibeError as
|
|
1805
|
+
import { VibeError as VibeError11, assertPredicateCapabilities, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
1194
1806
|
function renderPredicate(params) {
|
|
1807
|
+
assertPredicateCapabilities({ predicate: params.predicate, dialect: params.dialect.name, usage: params.usage });
|
|
1195
1808
|
if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
|
|
1196
|
-
throw
|
|
1809
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
1197
1810
|
if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
|
|
1198
|
-
throw
|
|
1811
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "partialIndexes" });
|
|
1199
1812
|
}
|
|
1200
1813
|
const context = {
|
|
1201
1814
|
model: params.model,
|
|
@@ -1212,7 +1825,8 @@ var PRECEDENCE = {
|
|
|
1212
1825
|
not: 3,
|
|
1213
1826
|
compare: 4,
|
|
1214
1827
|
inList: 4,
|
|
1215
|
-
isNull: 4
|
|
1828
|
+
isNull: 4,
|
|
1829
|
+
isTrue: 4
|
|
1216
1830
|
};
|
|
1217
1831
|
var COMPARISON_SQL = {
|
|
1218
1832
|
eq: "=",
|
|
@@ -1230,7 +1844,7 @@ var INT64_MIN = -(2n ** 63n);
|
|
|
1230
1844
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
1231
1845
|
function refuse2(params) {
|
|
1232
1846
|
const { context, rule, message } = params;
|
|
1233
|
-
throw new
|
|
1847
|
+
throw new VibeError11({
|
|
1234
1848
|
code: "VIBE_SCHEMA_INVALID",
|
|
1235
1849
|
message: `${context.site} on "${context.model.name}": ${message}`,
|
|
1236
1850
|
meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
|
|
@@ -1263,14 +1877,16 @@ function renderNode(params) {
|
|
|
1263
1877
|
}
|
|
1264
1878
|
case "not":
|
|
1265
1879
|
return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
|
|
1880
|
+
case "isTrue":
|
|
1881
|
+
return `COALESCE(${renderNode({ node: node.operand, context })}, FALSE)`;
|
|
1266
1882
|
case "isNull": {
|
|
1267
|
-
const field = resolveField({ name: node.field.name, context });
|
|
1883
|
+
const field = resolveField({ name: node.field.name, context, nullTest: true });
|
|
1268
1884
|
return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
|
|
1269
1885
|
}
|
|
1270
1886
|
case "compare": {
|
|
1271
|
-
const left =
|
|
1272
|
-
const right = renderOperand({ operand: node.right, field: left, context });
|
|
1273
|
-
return `${
|
|
1887
|
+
const left = renderExpression({ expression: node.left, context });
|
|
1888
|
+
const right = renderOperand({ operand: node.right, field: left.field, context });
|
|
1889
|
+
return `${left.text} ${COMPARISON_SQL[node.op]} ${right}`;
|
|
1274
1890
|
}
|
|
1275
1891
|
case "inList": {
|
|
1276
1892
|
if (node.values.length === 0) {
|
|
@@ -1314,7 +1930,7 @@ function resolveField(params) {
|
|
|
1314
1930
|
meta: { field: name }
|
|
1315
1931
|
});
|
|
1316
1932
|
}
|
|
1317
|
-
if (field.type === "Json" || field.type === "Bytes") {
|
|
1933
|
+
if (params.nullTest !== true && (field.type === "Json" || field.type === "Bytes")) {
|
|
1318
1934
|
refuse2({
|
|
1319
1935
|
context,
|
|
1320
1936
|
rule: "predicate-unsupported-type",
|
|
@@ -1323,7 +1939,7 @@ function resolveField(params) {
|
|
|
1323
1939
|
});
|
|
1324
1940
|
}
|
|
1325
1941
|
if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
|
|
1326
|
-
throw
|
|
1942
|
+
throw unsupportedCapability9({
|
|
1327
1943
|
dialect: "mysql",
|
|
1328
1944
|
feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
|
|
1329
1945
|
hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
|
|
@@ -1340,22 +1956,43 @@ function compareColumn(params) {
|
|
|
1340
1956
|
const quoted = quoteColumn({ field, context });
|
|
1341
1957
|
return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
|
|
1342
1958
|
}
|
|
1959
|
+
function renderExpression(params) {
|
|
1960
|
+
const { expression, context } = params;
|
|
1961
|
+
if (expression.kind === "field") {
|
|
1962
|
+
const field = resolveField({ name: expression.name, context });
|
|
1963
|
+
return { text: compareColumn({ field, context }), field };
|
|
1964
|
+
}
|
|
1965
|
+
if (expression.kind === "jsonText") {
|
|
1966
|
+
const field = resolveField({ name: expression.field.name, context, nullTest: true });
|
|
1967
|
+
if (field.type !== "Json")
|
|
1968
|
+
refuse2({ context, rule: "predicate-type-mismatch", message: `jsonText requires a scalar Json field, not "${field.name}"` });
|
|
1969
|
+
if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00"))) {
|
|
1970
|
+
refuse2({ context, rule: "predicate-json-path", message: "jsonText requires a string-only path without NUL bytes" });
|
|
1971
|
+
}
|
|
1972
|
+
const path = expression.path.map((value) => context.dialect.quoteStringLiteral({ value })).join(", ");
|
|
1973
|
+
return { text: `(${quoteColumn({ field, context })} #>> ARRAY[${path}]::text[])`, field: { ...field, type: "String" } };
|
|
1974
|
+
}
|
|
1975
|
+
const inner = renderExpression({ expression: expression.expression, context });
|
|
1976
|
+
if (inner.field.type !== "String")
|
|
1977
|
+
refuse2({ context, rule: "predicate-type-mismatch", message: "length requires a text expression" });
|
|
1978
|
+
return { text: `length(${inner.text})`, field: { ...inner.field, type: "Int" } };
|
|
1979
|
+
}
|
|
1343
1980
|
function renderOperand(params) {
|
|
1344
1981
|
const { operand, field, context } = params;
|
|
1345
1982
|
if (operand.kind === "value")
|
|
1346
1983
|
return renderLiteral({ value: operand, field, context });
|
|
1347
|
-
const other =
|
|
1984
|
+
const other = renderExpression({ expression: operand, context });
|
|
1348
1985
|
const leftType = fieldTypeIdentity({ type: field.type });
|
|
1349
|
-
const rightType = fieldTypeIdentity({ type: other.type });
|
|
1986
|
+
const rightType = fieldTypeIdentity({ type: other.field.type });
|
|
1350
1987
|
if (leftType !== rightType) {
|
|
1351
1988
|
refuse2({
|
|
1352
1989
|
context,
|
|
1353
1990
|
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 }
|
|
1991
|
+
message: `compares "${field.name}" (${leftType}) with "${other.field.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
|
|
1992
|
+
meta: { field: field.name, other: other.field.name, fieldType: leftType, otherType: rightType }
|
|
1356
1993
|
});
|
|
1357
1994
|
}
|
|
1358
|
-
return
|
|
1995
|
+
return other.text;
|
|
1359
1996
|
}
|
|
1360
1997
|
function renderLiteral(params) {
|
|
1361
1998
|
const { field, context } = params;
|
|
@@ -1493,23 +2130,23 @@ function describe(params) {
|
|
|
1493
2130
|
}
|
|
1494
2131
|
|
|
1495
2132
|
// packages/sql/src/exclusion.ts
|
|
1496
|
-
function
|
|
1497
|
-
throw new
|
|
2133
|
+
function invalid5(rule) {
|
|
2134
|
+
throw new VibeError12({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
1498
2135
|
}
|
|
1499
2136
|
function compileExclusion(params) {
|
|
1500
2137
|
const { exclusion: spec, model, dialect } = params;
|
|
1501
2138
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1502
|
-
throw
|
|
2139
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1503
2140
|
if (!spec.name || spec.name.includes("\x00"))
|
|
1504
|
-
|
|
2141
|
+
invalid5("name");
|
|
1505
2142
|
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
1506
|
-
|
|
2143
|
+
invalid5("range-type");
|
|
1507
2144
|
if (!["[)", "[]", "()", "(]"].includes(spec.range.bounds))
|
|
1508
|
-
|
|
2145
|
+
invalid5("bounds");
|
|
1509
2146
|
const field = (name) => {
|
|
1510
2147
|
const value = model.fields.find((entry) => entry.name === name);
|
|
1511
2148
|
if (value === undefined || value.isList || value.isUnsupported)
|
|
1512
|
-
|
|
2149
|
+
invalid5("field");
|
|
1513
2150
|
return value;
|
|
1514
2151
|
};
|
|
1515
2152
|
const endpoint = (value) => {
|
|
@@ -1517,17 +2154,17 @@ function compileExclusion(params) {
|
|
|
1517
2154
|
return "NULL";
|
|
1518
2155
|
const infinity = "infinity" in value ? value.infinity : value.nullAs;
|
|
1519
2156
|
if (infinity !== undefined && (spec.range.type === "int8range" || !["infinity", "-infinity"].includes(infinity)))
|
|
1520
|
-
|
|
2157
|
+
invalid5("infinity");
|
|
1521
2158
|
const literal = infinity === undefined ? undefined : `${dialect.quoteStringLiteral({ value: infinity })}::${spec.range.type === "tsrange" ? "timestamp" : "timestamptz"}`;
|
|
1522
2159
|
if ("infinity" in value)
|
|
1523
2160
|
return literal;
|
|
1524
2161
|
const column = field(value.field);
|
|
1525
2162
|
if (fieldTypeIdentity2({ type: column.type }) !== (spec.range.type === "int8range" ? "BigInt" : "DateTime"))
|
|
1526
|
-
|
|
2163
|
+
invalid5("range-field-type");
|
|
1527
2164
|
if (column.type === "DateTime") {
|
|
1528
2165
|
const timezone = /timestamptz|timestamp.*with time zone/i.test(column.nativeType ?? "timestamptz");
|
|
1529
2166
|
if (timezone !== (spec.range.type === "tstzrange"))
|
|
1530
|
-
|
|
2167
|
+
invalid5("range-timezone");
|
|
1531
2168
|
}
|
|
1532
2169
|
const sql = dialect.quoteIdent({ name: column.dbName ?? column.name });
|
|
1533
2170
|
return literal === undefined ? sql : `COALESCE(${sql}, ${literal})`;
|
|
@@ -1535,15 +2172,15 @@ function compileExclusion(params) {
|
|
|
1535
2172
|
const keys = spec.keys.map((key) => {
|
|
1536
2173
|
if ("expression" in key) {
|
|
1537
2174
|
if (!key.expression.trim() || key.expression.includes("\x00") || key.fields.length === 0)
|
|
1538
|
-
|
|
2175
|
+
invalid5("expression");
|
|
1539
2176
|
key.fields.forEach(field);
|
|
1540
2177
|
return `(${key.expression}) WITH =`;
|
|
1541
2178
|
}
|
|
1542
2179
|
const column = field(key.field);
|
|
1543
2180
|
if (column.isOptional)
|
|
1544
|
-
|
|
2181
|
+
invalid5("nullable-key-requires-expression");
|
|
1545
2182
|
if (!isEnumType2(column.type) && !["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
|
|
1546
|
-
|
|
2183
|
+
invalid5("key-type");
|
|
1547
2184
|
return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
|
|
1548
2185
|
});
|
|
1549
2186
|
keys.push(`${spec.range.type}(${endpoint(spec.range.lower)}, ${endpoint(spec.range.upper)}, ${dialect.quoteStringLiteral({ value: spec.range.bounds })}) WITH &&`);
|
|
@@ -1552,13 +2189,13 @@ function compileExclusion(params) {
|
|
|
1552
2189
|
}
|
|
1553
2190
|
function renderExclusionSetup(params) {
|
|
1554
2191
|
if (!params.dialect.capabilities.exclusionConstraints)
|
|
1555
|
-
throw
|
|
2192
|
+
throw unsupportedCapability10({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
1556
2193
|
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
1557
2194
|
}
|
|
1558
2195
|
function renderExclusionDdl(params) {
|
|
1559
2196
|
const { dialect, table, exclusion, operation } = params;
|
|
1560
2197
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1561
|
-
throw
|
|
2198
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1562
2199
|
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
1563
2200
|
if (operation === "drop")
|
|
1564
2201
|
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
@@ -1567,14 +2204,14 @@ function renderExclusionDdl(params) {
|
|
|
1567
2204
|
function renderExclusionCatalog(params) {
|
|
1568
2205
|
const { dialect, probe } = params;
|
|
1569
2206
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1570
|
-
throw
|
|
2207
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1571
2208
|
const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
1572
2209
|
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
2210
|
}
|
|
1574
2211
|
function renderExclusionProbe(params) {
|
|
1575
2212
|
const { dialect, table, probe, definition } = params;
|
|
1576
2213
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1577
|
-
throw
|
|
2214
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1578
2215
|
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
2216
|
}
|
|
1580
2217
|
// packages/sql/src/ast.ts
|
|
@@ -1653,7 +2290,7 @@ function chainVetoToken(params) {
|
|
|
1653
2290
|
return;
|
|
1654
2291
|
}
|
|
1655
2292
|
// packages/sql/src/locks.ts
|
|
1656
|
-
import { internalError, unsupportedCapability as
|
|
2293
|
+
import { internalError, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
1657
2294
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
1658
2295
|
strengths: Object.freeze({
|
|
1659
2296
|
update: "FOR UPDATE",
|
|
@@ -1673,7 +2310,7 @@ function renderRowLockClause(params) {
|
|
|
1673
2310
|
if (syntax === null) {
|
|
1674
2311
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
1675
2312
|
return null;
|
|
1676
|
-
throw
|
|
2313
|
+
throw unsupportedCapability11({
|
|
1677
2314
|
dialect: dialect.name,
|
|
1678
2315
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
1679
2316
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -1681,7 +2318,7 @@ function renderRowLockClause(params) {
|
|
|
1681
2318
|
}
|
|
1682
2319
|
const strengthText = syntax.strengths[lock.strength];
|
|
1683
2320
|
if (strengthText === undefined) {
|
|
1684
|
-
throw
|
|
2321
|
+
throw unsupportedCapability11({
|
|
1685
2322
|
dialect: dialect.name,
|
|
1686
2323
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
1687
2324
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -1689,7 +2326,7 @@ function renderRowLockClause(params) {
|
|
|
1689
2326
|
}
|
|
1690
2327
|
const waitText = syntax.wait[lock.wait];
|
|
1691
2328
|
if (waitText === undefined) {
|
|
1692
|
-
throw
|
|
2329
|
+
throw unsupportedCapability11({
|
|
1693
2330
|
dialect: dialect.name,
|
|
1694
2331
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
1695
2332
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -1708,7 +2345,7 @@ function renderAdvisoryLock(params) {
|
|
|
1708
2345
|
const { dialect, statement, collector } = params;
|
|
1709
2346
|
const functions = dialect.advisoryLockFunctions;
|
|
1710
2347
|
if (functions === null) {
|
|
1711
|
-
throw
|
|
2348
|
+
throw unsupportedCapability11({
|
|
1712
2349
|
dialect: dialect.name,
|
|
1713
2350
|
feature: "advisory locks",
|
|
1714
2351
|
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 +2376,7 @@ function assertInt32(params) {
|
|
|
1739
2376
|
var JSON_TRANSPORT = Object.freeze({ chunkSize: 100 });
|
|
1740
2377
|
function projectionOutputName(params) {
|
|
1741
2378
|
const { column } = params;
|
|
1742
|
-
return column.
|
|
2379
|
+
return column.alias ?? column.name;
|
|
1743
2380
|
}
|
|
1744
2381
|
// packages/sql/src/dialects/postgres.ts
|
|
1745
2382
|
var postgresDialect = {
|
|
@@ -1773,6 +2410,7 @@ var postgresDialect = {
|
|
|
1773
2410
|
"array_ends_with"
|
|
1774
2411
|
],
|
|
1775
2412
|
jsonParamCast: "::text::jsonb",
|
|
2413
|
+
valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "BOOLEAN", Int: "INTEGER", BigInt: "BIGINT", Float: "DOUBLE PRECISION", Decimal: "NUMERIC", DateTime: "TIMESTAMPTZ", Json: "JSONB", Bytes: "BYTEA" }),
|
|
1776
2414
|
lastInsertIdSql: null,
|
|
1777
2415
|
rowLockSyntax: {
|
|
1778
2416
|
strengths: Object.freeze({
|
|
@@ -1807,6 +2445,9 @@ var postgresDialect = {
|
|
|
1807
2445
|
placeholder({ index }) {
|
|
1808
2446
|
return `$${index}`;
|
|
1809
2447
|
},
|
|
2448
|
+
jsonTextProjection({ expression }) {
|
|
2449
|
+
return `${expression}::text`;
|
|
2450
|
+
},
|
|
1810
2451
|
renderDbNow({ offsetSeconds, collector }) {
|
|
1811
2452
|
if (offsetSeconds === 0)
|
|
1812
2453
|
return "now()";
|
|
@@ -1846,6 +2487,7 @@ var mysqlDialect = {
|
|
|
1846
2487
|
"array_ends_with"
|
|
1847
2488
|
],
|
|
1848
2489
|
jsonParamCast: "",
|
|
2490
|
+
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
2491
|
lastInsertIdSql: "SELECT LAST_INSERT_ID() AS `id`",
|
|
1850
2492
|
rowLockSyntax: {
|
|
1851
2493
|
strengths: Object.freeze({ update: "FOR UPDATE", share: "FOR SHARE" }),
|
|
@@ -1865,6 +2507,9 @@ var mysqlDialect = {
|
|
|
1865
2507
|
placeholder() {
|
|
1866
2508
|
return "?";
|
|
1867
2509
|
},
|
|
2510
|
+
jsonTextProjection({ expression }) {
|
|
2511
|
+
return `CAST(${expression} AS CHAR)`;
|
|
2512
|
+
},
|
|
1868
2513
|
renderDbNow({ offsetSeconds, collector }) {
|
|
1869
2514
|
if (offsetSeconds === 0)
|
|
1870
2515
|
return "NOW(6)";
|
|
@@ -1880,8 +2525,22 @@ var SQL_DIALECTS = {
|
|
|
1880
2525
|
function getSqlDialect(params) {
|
|
1881
2526
|
return SQL_DIALECTS[params.dialect];
|
|
1882
2527
|
}
|
|
2528
|
+
// packages/sql/src/dialects/sqlite-json-equality.ts
|
|
2529
|
+
function renderSqliteJsonEquality(params) {
|
|
2530
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2531
|
+
const values = q("__vibe_json_values");
|
|
2532
|
+
const tree = q("__vibe_json_tree");
|
|
2533
|
+
const paths = q("__vibe_json_paths");
|
|
2534
|
+
const child = q("__vibe_json_child");
|
|
2535
|
+
const parent = q("__vibe_json_parent");
|
|
2536
|
+
const [side, id, parentId, key, type, atom, path] = ["side", "id", "parent", "key", "type", "atom", "path"].map(q);
|
|
2537
|
+
const left = `${values}.${q("left")}`;
|
|
2538
|
+
const right = `${values}.${q("right")}`;
|
|
2539
|
+
return `(SELECT CASE WHEN ${left} IS NULL THEN NULL ELSE NOT EXISTS (` + `WITH RECURSIVE ${tree} AS (` + `SELECT 1 AS ${side}, ${id}, ${parentId}, ${key}, ${type}, ${atom} FROM json_tree(CASE WHEN json_valid(${left}) THEN ${left} ELSE 'null' END) ` + `UNION ALL SELECT -1 AS ${side}, ${id}, ${parentId}, ${key}, ${type}, ${atom} FROM json_tree(${right})` + `), ${paths} (${side}, ${id}, ${path}, ${type}, ${atom}) AS (` + `SELECT ${side}, ${id}, json_array(), ${type}, ${atom} FROM ${tree} WHERE ${parentId} IS NULL ` + `UNION ALL SELECT ${child}.${side}, ${child}.${id}, json_insert(${parent}.${path}, '$[#]', ${child}.${key}), ${child}.${type}, ${child}.${atom} ` + `FROM ${tree} AS ${child} JOIN ${paths} AS ${parent} ON ${child}.${side} = ${parent}.${side} AND ${child}.${parentId} = ${parent}.${id}` + `) SELECT 1 FROM ${paths} GROUP BY ${path}, CASE WHEN ${type} IN ('integer', 'real') THEN 'number' ELSE ${type} END, ${atom} HAVING SUM(${side}) <> 0` + `) END FROM (SELECT ${params.left} AS ${q("left")}, ${params.right} AS ${q("right")}) AS ${values})`;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
1883
2542
|
// packages/sql/src/dialects/diagnostics.ts
|
|
1884
|
-
import { unsupportedCapability as
|
|
2543
|
+
import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
1885
2544
|
var QUERIES = Object.freeze({
|
|
1886
2545
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
1887
2546
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -1889,7 +2548,7 @@ var QUERIES = Object.freeze({
|
|
|
1889
2548
|
function renderDiagnostic(params) {
|
|
1890
2549
|
const query = QUERIES[params.query];
|
|
1891
2550
|
if (params.dialect.name !== query.dialect) {
|
|
1892
|
-
throw
|
|
2551
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
1893
2552
|
}
|
|
1894
2553
|
return { text: query.text, values: [], returning: "none" };
|
|
1895
2554
|
}
|
|
@@ -1898,7 +2557,7 @@ function isFixedDiagnostic(params) {
|
|
|
1898
2557
|
}
|
|
1899
2558
|
|
|
1900
2559
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
1901
|
-
import { VibeError as
|
|
2560
|
+
import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
|
|
1902
2561
|
function renderPostgresExplain(params) {
|
|
1903
2562
|
const { dialect, query, options } = params;
|
|
1904
2563
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -1933,7 +2592,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
1933
2592
|
});
|
|
1934
2593
|
function requirePostgres(params) {
|
|
1935
2594
|
if (params.dialect.name !== "postgres") {
|
|
1936
|
-
throw
|
|
2595
|
+
throw unsupportedCapability13({
|
|
1937
2596
|
dialect: params.dialect.name,
|
|
1938
2597
|
feature: params.feature,
|
|
1939
2598
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -1951,7 +2610,7 @@ function renderPostgresWorkload(params) {
|
|
|
1951
2610
|
if (query === "statementSnapshot") {
|
|
1952
2611
|
const bounded = limit ?? 20;
|
|
1953
2612
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
1954
|
-
throw new
|
|
2613
|
+
throw new VibeError13({
|
|
1955
2614
|
code: "VIBE_CONFIG",
|
|
1956
2615
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
1957
2616
|
meta: { limit }
|
|
@@ -1970,7 +2629,7 @@ function renderPostgresWorkload(params) {
|
|
|
1970
2629
|
function requireArg(params) {
|
|
1971
2630
|
const { value, name, query } = params;
|
|
1972
2631
|
if (value === undefined || value.length === 0) {
|
|
1973
|
-
throw new
|
|
2632
|
+
throw new VibeError13({
|
|
1974
2633
|
code: "VIBE_CONFIG",
|
|
1975
2634
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
1976
2635
|
meta: { query, argument: name }
|
|
@@ -1980,10 +2639,10 @@ function requireArg(params) {
|
|
|
1980
2639
|
}
|
|
1981
2640
|
|
|
1982
2641
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
1983
|
-
import { unsupportedCapability as
|
|
2642
|
+
import { unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
1984
2643
|
|
|
1985
2644
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
1986
|
-
import { RlsContextError, VibeError as
|
|
2645
|
+
import { RlsContextError, VibeError as VibeError14, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability14, validateRlsContextIR } from "@vibeorm/schema";
|
|
1987
2646
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
1988
2647
|
var COLUMN_TYPES = {
|
|
1989
2648
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -1996,18 +2655,18 @@ var COLUMN_TYPES = {
|
|
|
1996
2655
|
};
|
|
1997
2656
|
function requireRls(params) {
|
|
1998
2657
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
1999
|
-
throw
|
|
2658
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
2000
2659
|
}
|
|
2001
2660
|
}
|
|
2002
2661
|
function identifier2(params) {
|
|
2003
2662
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
2004
|
-
throw new
|
|
2663
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
2005
2664
|
}
|
|
2006
2665
|
return params.dialect.quoteIdent({ name: params.name });
|
|
2007
2666
|
}
|
|
2008
2667
|
function tableRef(params) {
|
|
2009
2668
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
2010
|
-
throw
|
|
2669
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
2011
2670
|
}
|
|
2012
2671
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
2013
2672
|
}
|
|
@@ -2048,14 +2707,15 @@ function renderRlsColumnType(params) {
|
|
|
2048
2707
|
if (native !== undefined)
|
|
2049
2708
|
return native;
|
|
2050
2709
|
}
|
|
2051
|
-
throw
|
|
2710
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
2052
2711
|
}
|
|
2053
2712
|
function protectedModel(params) {
|
|
2054
2713
|
requireRls(params);
|
|
2714
|
+
assertNativeRlsAdmission({ schema: params.schema, dialect: params.dialect.name });
|
|
2055
2715
|
assertValidSchemaIR({ schema: params.schema });
|
|
2056
2716
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
2057
2717
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
2058
|
-
throw new
|
|
2718
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
2059
2719
|
}
|
|
2060
2720
|
return model;
|
|
2061
2721
|
}
|
|
@@ -2088,7 +2748,7 @@ function predicate(params) {
|
|
|
2088
2748
|
}).join(" AND ");
|
|
2089
2749
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
2090
2750
|
}
|
|
2091
|
-
throw new
|
|
2751
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
2092
2752
|
}
|
|
2093
2753
|
function renderRlsPolicy(params) {
|
|
2094
2754
|
const model = protectedModel(params);
|
|
@@ -2122,7 +2782,7 @@ function renderRlsContext(params) {
|
|
|
2122
2782
|
function renderRlsRole(params) {
|
|
2123
2783
|
requireRls(params);
|
|
2124
2784
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
2125
|
-
throw new
|
|
2785
|
+
throw new VibeError14({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
2126
2786
|
}
|
|
2127
2787
|
const collector = new ParamCollector(params.dialect);
|
|
2128
2788
|
return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
|
|
@@ -2274,7 +2934,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
2274
2934
|
});
|
|
2275
2935
|
function requireRls2(params) {
|
|
2276
2936
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
2277
|
-
throw
|
|
2937
|
+
throw unsupportedCapability15({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
2278
2938
|
}
|
|
2279
2939
|
}
|
|
2280
2940
|
function renderRlsReadinessQuery(params) {
|
|
@@ -2306,7 +2966,7 @@ function renderRlsPolicyPath(params) {
|
|
|
2306
2966
|
}
|
|
2307
2967
|
|
|
2308
2968
|
// packages/sql/src/render.ts
|
|
2309
|
-
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as
|
|
2969
|
+
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2310
2970
|
|
|
2311
2971
|
// packages/sql/src/like.ts
|
|
2312
2972
|
function escapeLikePattern(params) {
|
|
@@ -2425,15 +3085,23 @@ function quoteProjection(dialect, column) {
|
|
|
2425
3085
|
return base;
|
|
2426
3086
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
2427
3087
|
}
|
|
2428
|
-
if (
|
|
2429
|
-
|
|
3088
|
+
if (column.transform === "jsonText") {
|
|
3089
|
+
const ref = quoteCol(dialect, column);
|
|
3090
|
+
const alias = dialect.quoteIdent({ name: column.alias ?? column.name });
|
|
3091
|
+
return `${dialect.jsonTextProjection({ expression: ref })} AS ${alias}`;
|
|
2430
3092
|
}
|
|
2431
3093
|
const ref = quoteCol(dialect, column);
|
|
2432
|
-
const alias = dialect.quoteIdent({ name: column.name });
|
|
3094
|
+
const alias = dialect.quoteIdent({ name: column.alias ?? column.name });
|
|
2433
3095
|
if (column.transform === "castText")
|
|
2434
|
-
return `${ref}
|
|
3096
|
+
return `${textProjection({ dialect, expression: ref })} AS ${alias}`;
|
|
3097
|
+
if (dialect.name !== "postgres") {
|
|
3098
|
+
throw internalError2({ message: "encodeBase64 is a postgres construct" });
|
|
3099
|
+
}
|
|
2435
3100
|
return `encode(${ref}, 'base64') AS ${alias}`;
|
|
2436
3101
|
}
|
|
3102
|
+
function textProjection(params) {
|
|
3103
|
+
return params.dialect.name === "postgres" ? `${params.expression}::text` : `CAST(${params.expression} AS ${params.dialect.name === "mysql" ? "CHAR" : "TEXT"})`;
|
|
3104
|
+
}
|
|
2437
3105
|
function renderProjection(params) {
|
|
2438
3106
|
const { dialect, columns } = params;
|
|
2439
3107
|
return columns.map((column) => quoteProjection(dialect, column)).join(", ");
|
|
@@ -2481,7 +3149,7 @@ function renderAggRef(dialect, agg) {
|
|
|
2481
3149
|
function assertScalarArrays(dialect, feature) {
|
|
2482
3150
|
if (dialect.capabilities.scalarArrays)
|
|
2483
3151
|
return;
|
|
2484
|
-
throw
|
|
3152
|
+
throw unsupportedCapability16({
|
|
2485
3153
|
dialect: dialect.name,
|
|
2486
3154
|
feature,
|
|
2487
3155
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -2711,7 +3379,7 @@ function renderJsonLike(params) {
|
|
|
2711
3379
|
function renderJsonFilter(params) {
|
|
2712
3380
|
const { dialect, expr, collector } = params;
|
|
2713
3381
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
2714
|
-
throw
|
|
3382
|
+
throw unsupportedCapability16({
|
|
2715
3383
|
dialect: dialect.name,
|
|
2716
3384
|
feature: `the json filter "${expr.op}"`,
|
|
2717
3385
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -2754,8 +3422,8 @@ function renderJsonFilter(params) {
|
|
|
2754
3422
|
if (value !== null) {
|
|
2755
3423
|
const target = `json_extract(${column}, ${collector.add(path)})`;
|
|
2756
3424
|
const bound = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
2757
|
-
const
|
|
2758
|
-
base = `(${guard} AND ${
|
|
3425
|
+
const comparison = typeof value === "object" ? renderSqliteJsonEquality({ dialect, left: target, right: collector.add(JSON.stringify(value)) }) : `${target} = ${collector.add(bound)}`;
|
|
3426
|
+
base = `(${guard} AND ${comparison})`;
|
|
2759
3427
|
}
|
|
2760
3428
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
2761
3429
|
};
|
|
@@ -2778,7 +3446,7 @@ function renderJsonFilter(params) {
|
|
|
2778
3446
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
2779
3447
|
const tests = elements.map((element) => {
|
|
2780
3448
|
if (element === null || typeof element === "object" && element !== null) {
|
|
2781
|
-
throw
|
|
3449
|
+
throw unsupportedCapability16({
|
|
2782
3450
|
dialect: dialect.name,
|
|
2783
3451
|
feature: "json array_contains with object/array/null elements",
|
|
2784
3452
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -2916,11 +3584,12 @@ function applyReturning(params) {
|
|
|
2916
3584
|
const columns = requested.map((column) => {
|
|
2917
3585
|
if (typeof column === "string")
|
|
2918
3586
|
return dialect.quoteIdent({ name: column });
|
|
2919
|
-
if (
|
|
2920
|
-
|
|
3587
|
+
if ("transform" in column) {
|
|
3588
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
3589
|
+
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
2921
3590
|
}
|
|
2922
3591
|
const name = dialect.quoteIdent({ name: column.name });
|
|
2923
|
-
return `${name}
|
|
3592
|
+
return `${textProjection({ dialect, expression: name })} AS ${name}`;
|
|
2924
3593
|
}).join(", ");
|
|
2925
3594
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
2926
3595
|
}
|
|
@@ -2929,7 +3598,7 @@ function renderKeyTableJoin(params) {
|
|
|
2929
3598
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
2930
3599
|
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
2931
3600
|
if (!admitted) {
|
|
2932
|
-
throw
|
|
3601
|
+
throw unsupportedCapability16({
|
|
2933
3602
|
dialect: dialect.name,
|
|
2934
3603
|
feature: "relation key table",
|
|
2935
3604
|
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
@@ -2957,6 +3626,10 @@ function renderKeyTableJoin(params) {
|
|
|
2957
3626
|
switch (member.kind) {
|
|
2958
3627
|
case "integer":
|
|
2959
3628
|
return dialect.name === "mysql" ? `CAST(${placeholder} AS SIGNED)` : `CAST(${placeholder} AS bigint)`;
|
|
3629
|
+
case "timestamp":
|
|
3630
|
+
return `CAST(${placeholder} AS DATETIME(6))`;
|
|
3631
|
+
case "time":
|
|
3632
|
+
return `CAST(${placeholder} AS TIME(6))`;
|
|
2960
3633
|
case "decimal":
|
|
2961
3634
|
return `CAST(${placeholder} AS DECIMAL(65,${member.scale}))`;
|
|
2962
3635
|
case "citext":
|
|
@@ -2986,7 +3659,9 @@ function renderSelect(params) {
|
|
|
2986
3659
|
}
|
|
2987
3660
|
if (statement.aggregates !== undefined) {
|
|
2988
3661
|
for (const selection of statement.aggregates) {
|
|
2989
|
-
|
|
3662
|
+
const expression = renderAggRef(dialect, selection.agg);
|
|
3663
|
+
const projected = selection.castText === true ? textProjection({ dialect, expression }) : expression;
|
|
3664
|
+
columnList.push(`${projected} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
2990
3665
|
}
|
|
2991
3666
|
}
|
|
2992
3667
|
if (statement.countSubqueries !== undefined) {
|
|
@@ -3006,7 +3681,7 @@ function renderSelect(params) {
|
|
|
3006
3681
|
let distinctPrefix = "";
|
|
3007
3682
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
3008
3683
|
if (!dialect.capabilities.distinctOn) {
|
|
3009
|
-
throw
|
|
3684
|
+
throw unsupportedCapability16({
|
|
3010
3685
|
dialect: dialect.name,
|
|
3011
3686
|
feature: "SELECT DISTINCT ON",
|
|
3012
3687
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -3040,7 +3715,7 @@ function renderSelect(params) {
|
|
|
3040
3715
|
}
|
|
3041
3716
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
3042
3717
|
if (!dialect.capabilities.lateralJoin) {
|
|
3043
|
-
throw
|
|
3718
|
+
throw unsupportedCapability16({
|
|
3044
3719
|
dialect: dialect.name,
|
|
3045
3720
|
feature: "LATERAL JOIN relation loading",
|
|
3046
3721
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -3125,7 +3800,7 @@ function renderInsert(params) {
|
|
|
3125
3800
|
let target = "";
|
|
3126
3801
|
if (onConflict.constraint !== undefined) {
|
|
3127
3802
|
if (dialect.name !== "postgres") {
|
|
3128
|
-
throw
|
|
3803
|
+
throw unsupportedCapability16({
|
|
3129
3804
|
dialect: dialect.name,
|
|
3130
3805
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
3131
3806
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -3140,7 +3815,7 @@ function renderInsert(params) {
|
|
|
3140
3815
|
}
|
|
3141
3816
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
3142
3817
|
} else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
3143
|
-
throw
|
|
3818
|
+
throw unsupportedCapability16({
|
|
3144
3819
|
dialect: dialect.name,
|
|
3145
3820
|
feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
|
|
3146
3821
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -3158,7 +3833,7 @@ function renderInsert(params) {
|
|
|
3158
3833
|
throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
3159
3834
|
}
|
|
3160
3835
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
3161
|
-
throw
|
|
3836
|
+
throw unsupportedCapability16({
|
|
3162
3837
|
dialect: dialect.name,
|
|
3163
3838
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
3164
3839
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
@@ -3174,14 +3849,14 @@ function renderInsert(params) {
|
|
|
3174
3849
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
3175
3850
|
} else {
|
|
3176
3851
|
if (onConflict.constraint !== undefined) {
|
|
3177
|
-
throw
|
|
3852
|
+
throw unsupportedCapability16({
|
|
3178
3853
|
dialect: dialect.name,
|
|
3179
3854
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
3180
3855
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
3181
3856
|
});
|
|
3182
3857
|
}
|
|
3183
3858
|
if (onConflict.conflictWhere !== undefined) {
|
|
3184
|
-
throw
|
|
3859
|
+
throw unsupportedCapability16({
|
|
3185
3860
|
dialect: dialect.name,
|
|
3186
3861
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
3187
3862
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -3268,7 +3943,7 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
|
3268
3943
|
function renderChain(params) {
|
|
3269
3944
|
const { dialect, statement, collector } = params;
|
|
3270
3945
|
if (dialect.statementChain === null) {
|
|
3271
|
-
throw
|
|
3946
|
+
throw unsupportedCapability16({
|
|
3272
3947
|
dialect: dialect.name,
|
|
3273
3948
|
feature: "a statement chain (data-modifying WITH steps)",
|
|
3274
3949
|
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
@@ -3430,7 +4105,7 @@ function renderInsertSelect(params) {
|
|
|
3430
4105
|
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
3431
4106
|
}
|
|
3432
4107
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
3433
|
-
import { unsupportedCapability as
|
|
4108
|
+
import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
3434
4109
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
3435
4110
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
3436
4111
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -3457,13 +4132,13 @@ function parsePostgresTypeName(params) {
|
|
|
3457
4132
|
}
|
|
3458
4133
|
function renderPostgresTypeName(params) {
|
|
3459
4134
|
if (params.dialect.name !== "postgres")
|
|
3460
|
-
throw
|
|
4135
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
3461
4136
|
const { dialect, type } = params;
|
|
3462
4137
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
3463
4138
|
}
|
|
3464
4139
|
function renderPostgresTypeIdentity(params) {
|
|
3465
4140
|
if (params.dialect.name !== "postgres")
|
|
3466
|
-
throw
|
|
4141
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
3467
4142
|
const collector = new ParamCollector(params.dialect);
|
|
3468
4143
|
const { source } = params;
|
|
3469
4144
|
let text;
|
|
@@ -3476,11 +4151,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
3476
4151
|
return { text, values: collector.values, returning: "none" };
|
|
3477
4152
|
}
|
|
3478
4153
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
3479
|
-
import { unsupportedCapability as
|
|
4154
|
+
import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
3480
4155
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
3481
4156
|
const { dialect, table, column } = params;
|
|
3482
4157
|
if (dialect.name !== "postgres")
|
|
3483
|
-
throw
|
|
4158
|
+
throw unsupportedCapability18({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
3484
4159
|
const collector = new ParamCollector(dialect);
|
|
3485
4160
|
const namespace = collector.add(table.namespace);
|
|
3486
4161
|
const name = collector.add(table.name);
|
|
@@ -3504,28 +4179,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
3504
4179
|
};
|
|
3505
4180
|
}
|
|
3506
4181
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
3507
|
-
import { unsupportedCapability as
|
|
4182
|
+
import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
3508
4183
|
function renderConstraintOwnedIndexDrop(params) {
|
|
3509
4184
|
if (params.dialect.name !== "postgres")
|
|
3510
|
-
throw
|
|
4185
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
3511
4186
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
3512
4187
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
3513
4188
|
}
|
|
3514
4189
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
3515
|
-
import { unsupportedCapability as
|
|
4190
|
+
import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
3516
4191
|
function renderMysqlCheckProbe(params) {
|
|
3517
4192
|
if (params.dialect.name !== "mysql")
|
|
3518
|
-
throw
|
|
4193
|
+
throw unsupportedCapability20({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
3519
4194
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
3520
4195
|
const probe = q(params.probe);
|
|
3521
4196
|
switch (params.operation) {
|
|
3522
4197
|
case "clone":
|
|
3523
4198
|
if (params.table === undefined)
|
|
3524
|
-
throw
|
|
4199
|
+
throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
3525
4200
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
3526
4201
|
case "add": {
|
|
3527
4202
|
if (params.expression === undefined || params.definition === undefined)
|
|
3528
|
-
throw
|
|
4203
|
+
throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
3529
4204
|
const parts = tableDefinitionParts({ sql: params.definition });
|
|
3530
4205
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
3531
4206
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -3571,31 +4246,31 @@ function sqliteTableConstraints(params) {
|
|
|
3571
4246
|
return result;
|
|
3572
4247
|
}
|
|
3573
4248
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
3574
|
-
import { unsupportedCapability as
|
|
4249
|
+
import { unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
3575
4250
|
function renderMigrationVisibility(params) {
|
|
3576
4251
|
if (params.dialect.name !== "mysql")
|
|
3577
|
-
throw
|
|
4252
|
+
throw unsupportedCapability21({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
3578
4253
|
const collector = new ParamCollector(params.dialect);
|
|
3579
4254
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
3580
4255
|
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
4256
|
}
|
|
3582
4257
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
3583
|
-
import { unsupportedCapability as
|
|
4258
|
+
import { unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
3584
4259
|
function renderSqliteColumnInventory(params) {
|
|
3585
4260
|
if (params.dialect.name !== "sqlite")
|
|
3586
|
-
throw
|
|
4261
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
3587
4262
|
const collector = new ParamCollector(params.dialect);
|
|
3588
4263
|
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
4264
|
}
|
|
3590
4265
|
function renderSqliteForeignKeyInventory(params) {
|
|
3591
4266
|
if (params.dialect.name !== "sqlite")
|
|
3592
|
-
throw
|
|
4267
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
3593
4268
|
const collector = new ParamCollector(params.dialect);
|
|
3594
4269
|
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
4270
|
}
|
|
3596
4271
|
function renderSqliteRebuildObjects(params) {
|
|
3597
4272
|
if (params.dialect.name !== "sqlite")
|
|
3598
|
-
throw
|
|
4273
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
3599
4274
|
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
4275
|
}
|
|
3601
4276
|
function renderMigrationInventory(params) {
|
|
@@ -3634,7 +4309,7 @@ ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
|
3634
4309
|
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
4310
|
}
|
|
3636
4311
|
// packages/sql/src/dialects/migration-objects.ts
|
|
3637
|
-
import { internalError as internalError3, unsupportedCapability as
|
|
4312
|
+
import { internalError as internalError3, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
3638
4313
|
function renderMigrationObjectQuery(params) {
|
|
3639
4314
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
3640
4315
|
}
|
|
@@ -3642,7 +4317,7 @@ function objectQuery(params) {
|
|
|
3642
4317
|
const { dialect, identity: object, collector } = params;
|
|
3643
4318
|
if (object.kind === "namespace") {
|
|
3644
4319
|
if (dialect.name !== "postgres")
|
|
3645
|
-
throw
|
|
4320
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3646
4321
|
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
4322
|
}
|
|
3648
4323
|
const physical = "table" in object ? object.table : object.name;
|
|
@@ -3706,7 +4381,7 @@ function objectQuery(params) {
|
|
|
3706
4381
|
break;
|
|
3707
4382
|
case "constraint":
|
|
3708
4383
|
if (object.constraintType === "exclusion")
|
|
3709
|
-
throw
|
|
4384
|
+
throw unsupportedCapability23({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
3710
4385
|
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
4386
|
break;
|
|
3712
4387
|
case "index":
|
|
@@ -3716,15 +4391,15 @@ function objectQuery(params) {
|
|
|
3716
4391
|
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
4392
|
break;
|
|
3718
4393
|
default:
|
|
3719
|
-
throw
|
|
4394
|
+
throw unsupportedCapability23({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
3720
4395
|
}
|
|
3721
4396
|
} else {
|
|
3722
4397
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
3723
|
-
throw
|
|
4398
|
+
throw unsupportedCapability23({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
3724
4399
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
3725
4400
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
3726
4401
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
3727
|
-
throw
|
|
4402
|
+
throw unsupportedCapability23({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
3728
4403
|
const sqliteParams = new ParamCollector(dialect);
|
|
3729
4404
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
3730
4405
|
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 +4429,7 @@ function renderMigrationDependencies(params) {
|
|
|
3754
4429
|
}
|
|
3755
4430
|
function dependencyQuery(params) {
|
|
3756
4431
|
if (params.dialect.name !== "postgres")
|
|
3757
|
-
throw
|
|
4432
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
3758
4433
|
const object = params.identity;
|
|
3759
4434
|
const collector = params.collector;
|
|
3760
4435
|
if (object.kind === "namespace") {
|
|
@@ -3811,7 +4486,7 @@ function inspectionReferences(params) {
|
|
|
3811
4486
|
function renderMigrationObjectBatch(params) {
|
|
3812
4487
|
const { dialect } = params;
|
|
3813
4488
|
if (dialect.name !== "postgres")
|
|
3814
|
-
throw
|
|
4489
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
3815
4490
|
const groups = new Map;
|
|
3816
4491
|
for (const [position, identity] of params.identities.entries()) {
|
|
3817
4492
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -3844,16 +4519,16 @@ function renderMigrationNamespace(params) {
|
|
|
3844
4519
|
}
|
|
3845
4520
|
function renderCheckProbe(params) {
|
|
3846
4521
|
if (params.dialect.name !== "postgres")
|
|
3847
|
-
throw
|
|
4522
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
3848
4523
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
3849
4524
|
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
4525
|
}
|
|
3851
4526
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
3852
|
-
import { VibeError as
|
|
4527
|
+
import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3853
4528
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
3854
4529
|
function requirePostgres2(params) {
|
|
3855
4530
|
if (params.dialect.name !== "postgres") {
|
|
3856
|
-
throw
|
|
4531
|
+
throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.feature });
|
|
3857
4532
|
}
|
|
3858
4533
|
}
|
|
3859
4534
|
function renderMigrationSessionRead(params) {
|
|
@@ -3868,7 +4543,7 @@ function renderMigrationSessionSet(params) {
|
|
|
3868
4543
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
3869
4544
|
const { milliseconds, setting } = params;
|
|
3870
4545
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
3871
|
-
throw new
|
|
4546
|
+
throw new VibeError15({
|
|
3872
4547
|
code: "VIBE_MIGRATION",
|
|
3873
4548
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
3874
4549
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -3890,8 +4565,13 @@ function renderIndexStateQuery(params) {
|
|
|
3890
4565
|
text: `SELECT c.relname::text AS "index",
|
|
3891
4566
|
t.relname::text AS "table",
|
|
3892
4567
|
am.amname::text AS "method",
|
|
3893
|
-
|
|
3894
|
-
|
|
4568
|
+
i.indisunique AS "unique",
|
|
4569
|
+
i.indnullsnotdistinct AS "nullsNotDistinct",
|
|
4570
|
+
pg_catalog.array_to_json(ARRAY(SELECT pg_catalog.pg_get_indexdef(i.indexrelid, k.k, true)
|
|
4571
|
+
FROM pg_catalog.generate_series(1, i.indnkeyatts) AS k(k) ORDER BY k.k))::text AS "keyColumns",
|
|
4572
|
+
pg_catalog.array_to_json(ARRAY(SELECT pg_catalog.pg_get_indexdef(i.indexrelid, k.k, true)
|
|
4573
|
+
FROM pg_catalog.generate_series(i.indnkeyatts + 1, i.indnatts) AS k(k) ORDER BY k.k))::text AS "includedColumns",
|
|
4574
|
+
i.indisvalid AS "valid",
|
|
3895
4575
|
i.indisready AS "ready",
|
|
3896
4576
|
pg_catalog.pg_get_expr(i.indpred, i.indrelid) AS "predicate",
|
|
3897
4577
|
pg_catalog.pg_get_indexdef(i.indexrelid) AS "definition",
|
|
@@ -3981,14 +4661,28 @@ export {
|
|
|
3981
4661
|
SQLITE_SESSION_SQL,
|
|
3982
4662
|
SQL_DEFAULT_CELL,
|
|
3983
4663
|
SQL_DIALECTS,
|
|
4664
|
+
assertSqlParamBuilding,
|
|
3984
4665
|
chainVetoToken,
|
|
4666
|
+
cloneTimestampDate,
|
|
4667
|
+
compareTimestampDates,
|
|
3985
4668
|
compileExclusion,
|
|
4669
|
+
compileReusableSqlCommand,
|
|
4670
|
+
compileReusableSqlQuery,
|
|
3986
4671
|
compileSql,
|
|
4672
|
+
compileSqlCommand,
|
|
4673
|
+
compileSqlQuery,
|
|
3987
4674
|
containsPattern,
|
|
4675
|
+
createReusableSqlCommand,
|
|
4676
|
+
createReusableSqlQuery,
|
|
4677
|
+
createSqlCommand,
|
|
4678
|
+
createSqlInput,
|
|
4679
|
+
createSqlInputScope,
|
|
4680
|
+
createSqlQuery,
|
|
3988
4681
|
dbNowMarker,
|
|
3989
4682
|
decode,
|
|
3990
4683
|
decodeIsNoop,
|
|
3991
4684
|
describeRowLock,
|
|
4685
|
+
encode,
|
|
3992
4686
|
endsWithPattern,
|
|
3993
4687
|
enumMembershipCodec,
|
|
3994
4688
|
escapeGlobPattern,
|
|
@@ -4003,9 +4697,15 @@ export {
|
|
|
4003
4697
|
isJsonParamCell,
|
|
4004
4698
|
isListSetOperation,
|
|
4005
4699
|
isNativeEnumArrayParameter,
|
|
4700
|
+
isReusableSqlCommand,
|
|
4701
|
+
isReusableSqlQuery,
|
|
4006
4702
|
isSetOperation,
|
|
4703
|
+
isSqlBindingCollector,
|
|
4704
|
+
isSqlCommand,
|
|
4007
4705
|
isSqlDefaultCell,
|
|
4008
4706
|
isSqlFragment,
|
|
4707
|
+
isSqlParam,
|
|
4708
|
+
isSqlQuery,
|
|
4009
4709
|
isStepColumnRef,
|
|
4010
4710
|
jsonListParameter,
|
|
4011
4711
|
jsonParamCell,
|
|
@@ -4013,12 +4713,16 @@ export {
|
|
|
4013
4713
|
mysqlDialect,
|
|
4014
4714
|
nativeEnumArrayParameter,
|
|
4015
4715
|
parseDateTimeText,
|
|
4716
|
+
parseJsonText,
|
|
4016
4717
|
parsePgArrayLiteral,
|
|
4017
4718
|
parsePgArrayLiteralNested,
|
|
4018
4719
|
parsePostgresTypeName,
|
|
4019
4720
|
postgresArrayElementFromText,
|
|
4020
4721
|
postgresDialect,
|
|
4021
4722
|
projectionOutputName,
|
|
4723
|
+
recordSqlBinding,
|
|
4724
|
+
recordSqlBindingCheck,
|
|
4725
|
+
registerSqlHandleCheck,
|
|
4022
4726
|
renderAdvisoryLock,
|
|
4023
4727
|
renderCheckProbe,
|
|
4024
4728
|
renderColumnStateQuery,
|
|
@@ -4082,9 +4786,15 @@ export {
|
|
|
4082
4786
|
renderSqliteFtsUpdateTrigger,
|
|
4083
4787
|
renderSqliteRebuildObjects,
|
|
4084
4788
|
renderStatement,
|
|
4789
|
+
resultScalarContract,
|
|
4085
4790
|
returningColumnName,
|
|
4791
|
+
sameResultDecoder,
|
|
4792
|
+
sealSqlInputScope,
|
|
4086
4793
|
shortestFloat32,
|
|
4794
|
+
snapshotSqlValue,
|
|
4087
4795
|
sql,
|
|
4796
|
+
sqlInputContract,
|
|
4797
|
+
sqlParamContract,
|
|
4088
4798
|
sqlUsesIncoming,
|
|
4089
4799
|
sqliteColumnClauses,
|
|
4090
4800
|
sqliteDialect,
|
|
@@ -4095,7 +4805,9 @@ export {
|
|
|
4095
4805
|
strictMatchPattern,
|
|
4096
4806
|
tableDefinitionParts,
|
|
4097
4807
|
timeOfDayCodec,
|
|
4808
|
+
timestampIdentityKey,
|
|
4809
|
+
timestampIdentityText,
|
|
4098
4810
|
toPgArrayLiteral
|
|
4099
4811
|
};
|
|
4100
4812
|
|
|
4101
|
-
//# debugId=
|
|
4813
|
+
//# debugId=02AD9A2144138F0F64756E2164756E21
|