@vibeorm/sql 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ast.d.ts +334 -11
- package/dist/codecs.d.ts +48 -0
- package/dist/dialect.d.ts +44 -0
- package/dist/dialects/external-dependents.d.ts +28 -0
- package/dist/dialects/external-ownership.d.ts +8 -0
- package/dist/dialects/migration-shadow.d.ts +5 -0
- package/dist/dialects/postgres-index-keys.d.ts +10 -0
- package/dist/dialects/postgres-physical-catalog.d.ts +17 -0
- package/dist/dialects/sqlite-fts.d.ts +28 -0
- package/dist/exclusion.d.ts +2 -0
- package/dist/index-columns.d.ts +15 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +1023 -273
- package/dist/index.js.map +28 -20
- package/dist/json-list-parameter.d.ts +37 -0
- package/dist/native-enum-parameter.d.ts +17 -0
- package/dist/pg-array.d.ts +13 -0
- package/dist/render.d.ts +11 -1
- package/package.json +5 -3
- package/dist/ast.d.ts.map +0 -1
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/codecs.d.ts.map +0 -1
- package/dist/composable.d.ts.map +0 -1
- package/dist/dialect.d.ts.map +0 -1
- package/dist/dialects/constraint-owned-index.d.ts.map +0 -1
- package/dist/dialects/diagnostics.d.ts.map +0 -1
- package/dist/dialects/migration-history.d.ts.map +0 -1
- package/dist/dialects/migration-inventory.d.ts.map +0 -1
- package/dist/dialects/migration-objects.d.ts.map +0 -1
- package/dist/dialects/migration-replay-coverage.d.ts.map +0 -1
- package/dist/dialects/migration-shadow.d.ts.map +0 -1
- package/dist/dialects/migration-visibility.d.ts.map +0 -1
- package/dist/dialects/module-dependency.d.ts.map +0 -1
- package/dist/dialects/mysql-check-probe.d.ts.map +0 -1
- package/dist/dialects/mysql.d.ts.map +0 -1
- package/dist/dialects/postgres-default-identity.d.ts.map +0 -1
- package/dist/dialects/postgres-diagnostics.d.ts.map +0 -1
- package/dist/dialects/postgres-migration.d.ts.map +0 -1
- package/dist/dialects/postgres-rls-readiness.d.ts.map +0 -1
- package/dist/dialects/postgres-rls.d.ts.map +0 -1
- package/dist/dialects/postgres-type-identity.d.ts.map +0 -1
- package/dist/dialects/postgres.d.ts.map +0 -1
- package/dist/dialects/registry.d.ts.map +0 -1
- package/dist/dialects/sqlite-constraints.d.ts.map +0 -1
- package/dist/dialects/sqlite-session.d.ts.map +0 -1
- package/dist/dialects/sqlite-table-definition.d.ts.map +0 -1
- package/dist/dialects/sqlite.d.ts.map +0 -1
- package/dist/exclusion.d.ts.map +0 -1
- package/dist/identifiers.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/json-transport.d.ts.map +0 -1
- package/dist/like.d.ts.map +0 -1
- package/dist/locks.d.ts.map +0 -1
- package/dist/module-journal.d.ts.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/pg-array.d.ts.map +0 -1
- package/dist/predicate.d.ts.map +0 -1
- package/dist/render.d.ts.map +0 -1
- package/dist/result-decoder.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/sql/src/native-enum-parameter.ts
|
|
3
|
+
var NATIVE_ENUM_ARRAY_PARAMETER = Symbol("vibeorm.nativeEnumArrayParameter");
|
|
4
|
+
function nativeEnumArrayParameter(params) {
|
|
5
|
+
return { [NATIVE_ENUM_ARRAY_PARAMETER]: true, value: params.value, type: params.type };
|
|
6
|
+
}
|
|
7
|
+
function isNativeEnumArrayParameter(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && NATIVE_ENUM_ARRAY_PARAMETER in value;
|
|
9
|
+
}
|
|
10
|
+
// packages/sql/src/json-list-parameter.ts
|
|
11
|
+
var JSON_LIST_PARAMETER = Symbol("vibeorm.jsonListParameter");
|
|
12
|
+
function jsonListParameter(params) {
|
|
13
|
+
return { [JSON_LIST_PARAMETER]: true, value: params.value, storage: params.storage };
|
|
14
|
+
}
|
|
15
|
+
function isJsonListParameter(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && JSON_LIST_PARAMETER in value;
|
|
17
|
+
}
|
|
18
|
+
// packages/sql/src/dialects/external-ownership.ts
|
|
19
|
+
import { unsupportedCapability } from "@vibeorm/schema";
|
|
20
|
+
function renderExternalOwnershipCatalog(params) {
|
|
21
|
+
if (params.dialect.name !== "postgres")
|
|
22
|
+
throw unsupportedCapability({ dialect: params.dialect.name, feature: "externalObjects" });
|
|
23
|
+
const scope = "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND n.nspname NOT IN ('_vibeorm_shadow', '_vibe_shadow')";
|
|
24
|
+
let text;
|
|
25
|
+
switch (params.operation) {
|
|
26
|
+
case "namespaces":
|
|
27
|
+
text = `SELECT n.nspname::text AS namespace, has_schema_privilege(n.oid, 'USAGE') AS visible FROM pg_catalog.pg_namespace n WHERE ${scope} ORDER BY n.nspname`;
|
|
28
|
+
break;
|
|
29
|
+
case "inheritance":
|
|
30
|
+
text = `SELECT n.nspname::text AS namespace, c.relname::text AS name, pn.nspname::text AS "parentNamespace", p.relname::text AS "parentName" FROM pg_catalog.pg_inherits i JOIN pg_catalog.pg_class c ON c.oid = i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN pg_catalog.pg_class p ON p.oid = i.inhparent JOIN pg_catalog.pg_namespace pn ON pn.oid = p.relnamespace WHERE ${scope} ORDER BY n.nspname, c.relname, pn.nspname, p.relname`;
|
|
31
|
+
break;
|
|
32
|
+
case "ownedSequences":
|
|
33
|
+
text = `SELECT n.nspname::text AS namespace, s.relname::text AS name, tn.nspname::text AS "tableNamespace", t.relname::text AS "tableName" FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class s ON s.oid = d.objid AND s.relkind = 'S' JOIN pg_catalog.pg_namespace n ON n.oid = s.relnamespace JOIN pg_catalog.pg_class t ON t.oid = d.refobjid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.relnamespace WHERE d.classid = 'pg_class'::regclass AND d.refclassid = 'pg_class'::regclass AND d.deptype IN ('a', 'i') AND d.refobjsubid > 0 AND ${scope} ORDER BY n.nspname, s.relname`;
|
|
34
|
+
break;
|
|
35
|
+
case "foreignKeys":
|
|
36
|
+
text = `SELECT n.nspname::text AS namespace, t.relname::text AS table, rn.nspname::text AS "refNamespace", r.relname::text AS "refTable", c.conname::text AS name 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_class r ON r.oid = c.confrelid JOIN pg_catalog.pg_namespace rn ON rn.oid = r.relnamespace WHERE c.contype = 'f' AND ${scope} ORDER BY n.nspname, t.relname, c.conname`;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
return { text, values: [], returning: "none" };
|
|
40
|
+
}
|
|
41
|
+
// packages/sql/src/dialects/external-dependents.ts
|
|
42
|
+
import { unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
43
|
+
|
|
2
44
|
// packages/sql/src/params.ts
|
|
45
|
+
import { unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
3
46
|
class ParamCollector {
|
|
4
47
|
dialect;
|
|
5
48
|
values;
|
|
@@ -8,6 +51,19 @@ class ParamCollector {
|
|
|
8
51
|
this.values = [];
|
|
9
52
|
}
|
|
10
53
|
add(value) {
|
|
54
|
+
if (isNativeEnumArrayParameter(value)) {
|
|
55
|
+
if (!this.dialect.capabilities.scalarArrays)
|
|
56
|
+
throw unsupportedCapability2({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
57
|
+
this.values.push(value.value);
|
|
58
|
+
return `${this.dialect.placeholder({ index: this.values.length })}::${this.dialect.quoteIdent({ name: value.type.namespace })}.${this.dialect.quoteIdent({ name: value.type.name })}[]`;
|
|
59
|
+
}
|
|
60
|
+
if (isJsonListParameter(value)) {
|
|
61
|
+
if (!this.dialect.capabilities.scalarArrays)
|
|
62
|
+
throw unsupportedCapability2({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
63
|
+
const whole = typeof value.value !== "string";
|
|
64
|
+
this.values.push(whole ? [...value.value] : value.value);
|
|
65
|
+
return `${this.dialect.placeholder({ index: this.values.length })}::text::${value.storage}${whole ? "[]" : ""}`;
|
|
66
|
+
}
|
|
11
67
|
this.values.push(value);
|
|
12
68
|
return this.dialect.placeholder({ index: this.values.length });
|
|
13
69
|
}
|
|
@@ -16,6 +72,121 @@ class ParamCollector {
|
|
|
16
72
|
}
|
|
17
73
|
}
|
|
18
74
|
|
|
75
|
+
// packages/sql/src/dialects/external-dependents.ts
|
|
76
|
+
function renderExternalDependents(params) {
|
|
77
|
+
if (params.dialect.name !== "postgres")
|
|
78
|
+
throw unsupportedCapability3({ dialect: params.dialect.name, feature: "externalObjects" });
|
|
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" };
|
|
118
|
+
}
|
|
119
|
+
// packages/sql/src/dialects/postgres-physical-catalog.ts
|
|
120
|
+
import { unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
121
|
+
function renderPostgresPhysicalCatalog(params) {
|
|
122
|
+
const { dialect } = params;
|
|
123
|
+
if (dialect.name !== "postgres")
|
|
124
|
+
throw unsupportedCapability4({ dialect: dialect.name, feature: "postgresPhysicalCatalog" });
|
|
125
|
+
const collector = new ParamCollector(dialect);
|
|
126
|
+
const namespace = collector.add(params.namespace);
|
|
127
|
+
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)`;
|
|
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;
|
|
154
|
+
}
|
|
155
|
+
return { text, values: collector.values, returning: "none" };
|
|
156
|
+
}
|
|
157
|
+
// packages/sql/src/dialects/postgres-index-keys.ts
|
|
158
|
+
import { unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
159
|
+
function renderPostgresIndexKeys(params) {
|
|
160
|
+
const { dialect } = params;
|
|
161
|
+
if (dialect.name !== "postgres")
|
|
162
|
+
throw unsupportedCapability5({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
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`;
|
|
166
|
+
return {
|
|
167
|
+
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`,
|
|
168
|
+
values: collector.values,
|
|
169
|
+
returning: "none"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// packages/sql/src/index-columns.ts
|
|
173
|
+
import { VibeError, isSqlObjectIdentity, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
174
|
+
function renderIndexColumns(params) {
|
|
175
|
+
const { dialect, columns, unique } = params;
|
|
176
|
+
const hasOperatorClass = columns.some((column) => column.operatorClass !== undefined);
|
|
177
|
+
if (hasOperatorClass && !dialect.capabilities.indexOperatorClasses)
|
|
178
|
+
throw unsupportedCapability6({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
179
|
+
if (hasOperatorClass && unique)
|
|
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 } });
|
|
185
|
+
}
|
|
186
|
+
const ops = operatorClass === undefined ? "" : ` ${dialect.quoteIdent({ name: operatorClass.namespace })}.${dialect.quoteIdent({ name: operatorClass.name })}`;
|
|
187
|
+
return `${dialect.quoteIdent({ name: column.name })}${ops}${column.desc ? " DESC" : ""}`;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
19
190
|
// packages/sql/src/dialects/module-dependency.ts
|
|
20
191
|
function renderModuleDependencyQuery(params) {
|
|
21
192
|
const { dialect, operation } = params;
|
|
@@ -50,19 +221,19 @@ function renderReplayCoverageGaps(params) {
|
|
|
50
221
|
const collector = new ParamCollector(dialect);
|
|
51
222
|
const namespace = collector.add(params.namespace);
|
|
52
223
|
if (dialect.name === "postgres")
|
|
53
|
-
return { text: `SELECT 'type' AS kind, t.typname::text AS name FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND (t.typtype IN ('b', 'd', 'r', 'm') AND t.typelem = 0 OR t.typtype = 'c' AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c')) 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')
|
|
54
|
-
UNION ALL SELECT 'routine', p.proname::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.prokind <> 'f' AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e')
|
|
55
|
-
UNION ALL SELECT 'relation-layout', c.relname::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND (c.relkind IN ('p', 'f') OR c.relispartition OR EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = c.oid)) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = c.oid AND d.deptype = 'e')
|
|
56
|
-
UNION ALL SELECT 'rule', r.rulename::text FROM pg_catalog.pg_rewrite r JOIN pg_catalog.pg_class c ON c.oid = r.ev_class JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND r.rulename <> '_RETURN' AND NOT ${extensionMember("pg_class", "c.oid")}
|
|
57
|
-
UNION ALL SELECT 'namespace', n.nspname::text FROM pg_catalog.pg_namespace n WHERE n.nspname <> ${namespace} AND n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND NOT (${extensionOwnedNamespace})${params.privateNamespace === undefined ? "" : ` AND n.nspname <> ${collector.add(params.privateNamespace)}`}`, values: collector.values, returning: "none" };
|
|
224
|
+
return { text: `SELECT 'type' AS kind, t.typname::text AS name, n.nspname::text AS namespace, NULL::text AS parent FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND (t.typtype IN ('b', 'd', 'r', 'm') AND t.typelem = 0 OR t.typtype = 'c' AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c')) 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')
|
|
225
|
+
UNION ALL SELECT 'routine', p.proname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.prokind <> 'f' AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e')
|
|
226
|
+
UNION ALL SELECT 'relation-layout', c.relname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND (c.relkind IN ('p', 'f') OR c.relispartition OR EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = c.oid)) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = c.oid AND d.deptype = 'e')
|
|
227
|
+
UNION ALL SELECT 'rule', r.rulename::text, n.nspname::text, c.relname::text FROM pg_catalog.pg_rewrite r JOIN pg_catalog.pg_class c ON c.oid = r.ev_class JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND r.rulename <> '_RETURN' AND NOT ${extensionMember("pg_class", "c.oid")}
|
|
228
|
+
UNION ALL SELECT 'namespace', n.nspname::text, n.nspname::text, NULL::text FROM pg_catalog.pg_namespace n WHERE n.nspname <> ${namespace} AND n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND NOT (${extensionOwnedNamespace})${params.privateNamespace === undefined ? "" : ` AND n.nspname <> ${collector.add(params.privateNamespace)}`}`, values: collector.values, returning: "none" };
|
|
58
229
|
if (dialect.name === "mysql")
|
|
59
|
-
return { text: `SELECT 'procedure' AS kind, ROUTINE_NAME AS name FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${namespace} AND ROUTINE_TYPE = 'PROCEDURE'
|
|
60
|
-
UNION ALL SELECT 'event', EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ${collector.add(params.namespace)}
|
|
61
|
-
UNION ALL SELECT DISTINCT 'partition', TABLE_NAME FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = ${collector.add(params.namespace)} AND PARTITION_NAME IS NOT NULL`, values: collector.values, returning: "none" };
|
|
62
|
-
return { text: `SELECT 'table-options' AS kind, name 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" };
|
|
230
|
+
return { text: `SELECT 'procedure' AS kind, ROUTINE_NAME AS name, ROUTINE_SCHEMA AS namespace, NULL AS parent FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ${namespace} AND ROUTINE_TYPE = 'PROCEDURE'
|
|
231
|
+
UNION ALL SELECT 'event', EVENT_NAME, EVENT_SCHEMA, NULL FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ${collector.add(params.namespace)}
|
|
232
|
+
UNION ALL SELECT DISTINCT 'partition', TABLE_NAME, TABLE_SCHEMA, NULL FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = ${collector.add(params.namespace)} AND PARTITION_NAME IS NOT NULL`, values: collector.values, returning: "none" };
|
|
233
|
+
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" };
|
|
63
234
|
}
|
|
64
235
|
// packages/sql/src/dialects/migration-shadow.ts
|
|
65
|
-
import { unsupportedCapability } from "@vibeorm/schema";
|
|
236
|
+
import { assertSqlNamespace, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
66
237
|
var SHADOW_DATABASE = Object.freeze({ schema: "_vibeorm_shadow", table: "_vibe_shadow", lock: 1447645267, timeoutSeconds: 60 });
|
|
67
238
|
var SHADOW_CAPABILITIES = Object.freeze({
|
|
68
239
|
postgres: Object.freeze({ serverProvisioning: true, dedicated: true }),
|
|
@@ -72,7 +243,7 @@ var SHADOW_CAPABILITIES = Object.freeze({
|
|
|
72
243
|
function renderShadowOperation(params) {
|
|
73
244
|
const { dialect, operation } = params;
|
|
74
245
|
if (!SHADOW_CAPABILITIES[dialect.name].serverProvisioning)
|
|
75
|
-
throw
|
|
246
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "shadowDatabaseProvisioning" });
|
|
76
247
|
const pg = dialect.name === "postgres";
|
|
77
248
|
const collector = new ParamCollector(dialect);
|
|
78
249
|
const marker = pg ? `${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}.${dialect.quoteIdent({ name: SHADOW_DATABASE.table })}` : dialect.quoteIdent({ name: SHADOW_DATABASE.table });
|
|
@@ -97,7 +268,7 @@ function renderShadowOperation(params) {
|
|
|
97
268
|
ORDER BY ordinal_position`);
|
|
98
269
|
case "grants": {
|
|
99
270
|
if (pg)
|
|
100
|
-
throw
|
|
271
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
|
|
101
272
|
return result("SHOW GRANTS FOR CURRENT_USER");
|
|
102
273
|
}
|
|
103
274
|
case "markerCreate":
|
|
@@ -125,19 +296,27 @@ UNION ALL SELECT ROUTINE_TYPE, ROUTINE_NAME, ROUTINE_SCHEMA FROM information_sch
|
|
|
125
296
|
UNION ALL SELECT 'EVENT', EVENT_NAME, EVENT_SCHEMA FROM information_schema.EVENTS WHERE EVENT_SCHEMA = DATABASE()
|
|
126
297
|
UNION ALL SELECT 'TRIGGER', TRIGGER_NAME, TRIGGER_SCHEMA FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = DATABASE()
|
|
127
298
|
UNION ALL SELECT 'EXTERNAL FOREIGN KEY', CONCAT(TABLE_NAME, '.', CONSTRAINT_NAME), CONSTRAINT_SCHEMA FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE UNIQUE_CONSTRAINT_SCHEMA = DATABASE() AND CAST(CONSTRAINT_SCHEMA AS BINARY) <> CAST(DATABASE() AS BINARY)`);
|
|
128
|
-
case "resetPostgres":
|
|
299
|
+
case "resetPostgres":
|
|
300
|
+
case "resetDedicatedPostgres": {
|
|
129
301
|
if (!pg)
|
|
130
|
-
throw
|
|
131
|
-
|
|
302
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "postgresShadowReset" });
|
|
303
|
+
const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
|
|
304
|
+
for (const namespace of owned)
|
|
305
|
+
assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
|
|
306
|
+
return [
|
|
307
|
+
...owned.map((namespace) => ({ text: `DROP SCHEMA IF EXISTS ${dialect.quoteIdent({ name: namespace })} CASCADE`, values: [], returning: "none" })),
|
|
308
|
+
{ text: 'DROP SCHEMA "public" CASCADE', values: [], returning: "none" },
|
|
309
|
+
{ text: 'CREATE SCHEMA "public"', values: [], returning: "none" }
|
|
310
|
+
];
|
|
132
311
|
}
|
|
133
312
|
case "mysqlForeignKeys": {
|
|
134
313
|
if (pg)
|
|
135
|
-
throw
|
|
314
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
136
315
|
return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
|
|
137
316
|
}
|
|
138
317
|
case "dropMysqlObject": {
|
|
139
318
|
if (pg)
|
|
140
|
-
throw
|
|
319
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
141
320
|
return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
|
|
142
321
|
}
|
|
143
322
|
}
|
|
@@ -170,6 +349,209 @@ function renderModuleJournal(params) {
|
|
|
170
349
|
}
|
|
171
350
|
return { text, values: collector.values };
|
|
172
351
|
}
|
|
352
|
+
// packages/sql/src/capabilities.ts
|
|
353
|
+
import {
|
|
354
|
+
DIALECT_CAPABILITIES,
|
|
355
|
+
POSTGRES_CAPABILITIES,
|
|
356
|
+
SQLITE_CAPABILITIES,
|
|
357
|
+
MYSQL_CAPABILITIES
|
|
358
|
+
} from "@vibeorm/schema";
|
|
359
|
+
|
|
360
|
+
// packages/sql/src/identifiers.ts
|
|
361
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
362
|
+
function assertNoNul(params) {
|
|
363
|
+
if (params.name.includes("\x00")) {
|
|
364
|
+
throw new VibeError2({
|
|
365
|
+
code: "VIBE_VALIDATION",
|
|
366
|
+
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
367
|
+
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return params.name;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// packages/sql/src/dialects/sqlite.ts
|
|
374
|
+
var sqliteDialect = {
|
|
375
|
+
name: "sqlite",
|
|
376
|
+
capabilities: SQLITE_CAPABILITIES,
|
|
377
|
+
inArrayStrategy: "expand",
|
|
378
|
+
tupleInStrategy: "rowValue",
|
|
379
|
+
timeOfDayInStrategy: "in",
|
|
380
|
+
upsertForm: "onConflict",
|
|
381
|
+
conflictKeyEquality: {
|
|
382
|
+
foldingScalars: [],
|
|
383
|
+
textIsBinaryByDefault: true,
|
|
384
|
+
duplicatesInOneStatement: "lastWriteWins"
|
|
385
|
+
},
|
|
386
|
+
excludedRowRef: { form: "keyword", name: "excluded" },
|
|
387
|
+
doNothingForm: "onConflictDoNothing",
|
|
388
|
+
insensitiveLike: "lower",
|
|
389
|
+
strictMatchStrategy: "glob",
|
|
390
|
+
greatestForm: "frame",
|
|
391
|
+
orderByNullsMode: "native",
|
|
392
|
+
offsetRequiresLimit: { limitText: "-1" },
|
|
393
|
+
likeEscapeClause: " ESCAPE '\\'",
|
|
394
|
+
jsonPathMode: "sqliteExtract",
|
|
395
|
+
supportedJsonOps: [
|
|
396
|
+
"equals",
|
|
397
|
+
"string_contains",
|
|
398
|
+
"string_starts_with",
|
|
399
|
+
"string_ends_with",
|
|
400
|
+
"array_contains",
|
|
401
|
+
"array_starts_with",
|
|
402
|
+
"array_ends_with"
|
|
403
|
+
],
|
|
404
|
+
jsonParamCast: "",
|
|
405
|
+
lastInsertIdSql: null,
|
|
406
|
+
rowLockSyntax: null,
|
|
407
|
+
advisoryLockFunctions: null,
|
|
408
|
+
rowDefaultKeyword: false,
|
|
409
|
+
statementChain: null,
|
|
410
|
+
paramBudget: 900,
|
|
411
|
+
booleanLiteral: { true: "1", false: "0" },
|
|
412
|
+
quoteIdent({ name }) {
|
|
413
|
+
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
414
|
+
},
|
|
415
|
+
quoteStringLiteral({ value }) {
|
|
416
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
417
|
+
},
|
|
418
|
+
placeholder() {
|
|
419
|
+
return "?";
|
|
420
|
+
},
|
|
421
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
422
|
+
if (offsetSeconds === 0)
|
|
423
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
424
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
429
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
430
|
+
function tableDefinitionParts(params) {
|
|
431
|
+
const sql = params.sql;
|
|
432
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
433
|
+
throw unavailable();
|
|
434
|
+
const parts = [];
|
|
435
|
+
let start = -1;
|
|
436
|
+
let depth = 0;
|
|
437
|
+
let quote;
|
|
438
|
+
for (let index = 0;index < sql.length; index++) {
|
|
439
|
+
const char = sql[index];
|
|
440
|
+
if (quote !== undefined) {
|
|
441
|
+
if (char === quote) {
|
|
442
|
+
if (quote !== "]" && sql[index + 1] === quote)
|
|
443
|
+
index++;
|
|
444
|
+
else
|
|
445
|
+
quote = undefined;
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
450
|
+
quote = char;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (char === "[") {
|
|
454
|
+
quote = "]";
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
458
|
+
const end = sql.indexOf(`
|
|
459
|
+
`, index + 2);
|
|
460
|
+
if (end < 0)
|
|
461
|
+
break;
|
|
462
|
+
index = end;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
466
|
+
const end = sql.indexOf("*/", index + 2);
|
|
467
|
+
if (end < 0)
|
|
468
|
+
break;
|
|
469
|
+
index = end + 1;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (char === "(") {
|
|
473
|
+
if (depth === 0)
|
|
474
|
+
start = index + 1;
|
|
475
|
+
depth++;
|
|
476
|
+
} else if (char === ")") {
|
|
477
|
+
depth--;
|
|
478
|
+
if (depth === 0 && start >= 0) {
|
|
479
|
+
parts.push(sql.slice(start, index).trim());
|
|
480
|
+
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
481
|
+
}
|
|
482
|
+
} else if (char === "," && depth === 1) {
|
|
483
|
+
parts.push(sql.slice(start, index).trim());
|
|
484
|
+
start = index + 1;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
throw unavailable();
|
|
488
|
+
}
|
|
489
|
+
function unavailable() {
|
|
490
|
+
return new VibeError3({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
491
|
+
}
|
|
492
|
+
function sqliteColumnClauses(params) {
|
|
493
|
+
const columns = [];
|
|
494
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
495
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
496
|
+
if (token === null)
|
|
497
|
+
throw unavailable();
|
|
498
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
499
|
+
continue;
|
|
500
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
501
|
+
columns.push({ name, sql: part });
|
|
502
|
+
}
|
|
503
|
+
return columns;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// packages/sql/src/dialects/sqlite-fts.ts
|
|
507
|
+
function updateStatements(params) {
|
|
508
|
+
const quote = (name) => sqliteDialect.quoteIdent({ name });
|
|
509
|
+
const columns = params.columns.map((column) => quote(column)).join(", ");
|
|
510
|
+
const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
511
|
+
const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
512
|
+
const changes = [
|
|
513
|
+
"old.rowid IS NOT new.rowid",
|
|
514
|
+
...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
|
|
515
|
+
].join(" OR ");
|
|
516
|
+
const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
|
|
517
|
+
const body = `BEGIN
|
|
518
|
+
INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
|
|
519
|
+
INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
|
|
520
|
+
END`;
|
|
521
|
+
const tail = `${target} WHEN (${changes}) ${body}`;
|
|
522
|
+
return {
|
|
523
|
+
create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
|
|
524
|
+
definition: `CREATE TRIGGER ${tail}`,
|
|
525
|
+
legacyDefinition: `CREATE TRIGGER ${target} ${body}`
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function renderSqliteFtsUpdateTrigger(params) {
|
|
529
|
+
const { create, definition } = updateStatements(params);
|
|
530
|
+
return { create, definition };
|
|
531
|
+
}
|
|
532
|
+
function sqliteFtsUpdateUpgrade(params) {
|
|
533
|
+
const quoted = '"(?:[^"]|"")+"';
|
|
534
|
+
const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
|
|
535
|
+
for (const match of params.create.matchAll(body)) {
|
|
536
|
+
const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
|
|
537
|
+
const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
|
|
538
|
+
if (params.name !== `${ftsTable}_au`)
|
|
539
|
+
continue;
|
|
540
|
+
const statements = updateStatements({ table: params.table, ftsTable, columns });
|
|
541
|
+
if (params.create !== statements.create)
|
|
542
|
+
continue;
|
|
543
|
+
const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
|
|
544
|
+
if (installed === undefined)
|
|
545
|
+
continue;
|
|
546
|
+
if (params.tableDefinition === undefined)
|
|
547
|
+
return { kind: "tableRequired", ftsTable };
|
|
548
|
+
const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
|
|
549
|
+
if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
|
|
550
|
+
continue;
|
|
551
|
+
return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
173
555
|
// packages/sql/src/dialects/sqlite-session.ts
|
|
174
556
|
var SQLITE_SESSION_SQL = Object.freeze({
|
|
175
557
|
lockingMode: "PRAGMA main.locking_mode",
|
|
@@ -182,10 +564,10 @@ var SQLITE_SESSION_SQL = Object.freeze({
|
|
|
182
564
|
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
183
565
|
});
|
|
184
566
|
// packages/sql/src/composable.ts
|
|
185
|
-
import { VibeError } from "@vibeorm/schema";
|
|
567
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
186
568
|
var fragments = new WeakMap;
|
|
187
569
|
function invalid(message) {
|
|
188
|
-
throw new
|
|
570
|
+
throw new VibeError4({ code: "VIBE_VALIDATION", message });
|
|
189
571
|
}
|
|
190
572
|
function construct(parts) {
|
|
191
573
|
const snapshot = Object.freeze([...parts]);
|
|
@@ -201,6 +583,9 @@ function isSqlFragment(value) {
|
|
|
201
583
|
function partsOf(value) {
|
|
202
584
|
return fragments.get(value) ?? invalid("Expected a constructed SQL fragment");
|
|
203
585
|
}
|
|
586
|
+
function containsGreatest(fragment) {
|
|
587
|
+
return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
|
|
588
|
+
}
|
|
204
589
|
function identifier(parts) {
|
|
205
590
|
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
206
591
|
invalid("SQL identifiers require non-empty, NUL-free name components");
|
|
@@ -263,14 +648,25 @@ var sql = Object.freeze(Object.assign(template, {
|
|
|
263
648
|
function renderSqlFragment(params) {
|
|
264
649
|
const { dialect, collector, conflict } = params;
|
|
265
650
|
const render = (fragment) => renderSqlFragment({ ...params, fragment });
|
|
651
|
+
const encode = (part) => {
|
|
652
|
+
if (part.value === undefined)
|
|
653
|
+
invalid("SQL parameters cannot be undefined; use null explicitly");
|
|
654
|
+
return part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name });
|
|
655
|
+
};
|
|
656
|
+
const frameOperand = (fragment) => {
|
|
657
|
+
const parts = partsOf(fragment).filter((part) => part !== "");
|
|
658
|
+
const only = parts.length === 1 ? parts[0] : undefined;
|
|
659
|
+
const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
|
|
660
|
+
if (value !== undefined && value.encoded === null)
|
|
661
|
+
return "NULL";
|
|
662
|
+
const text = value === undefined ? render(fragment) : collector.add(value.encoded);
|
|
663
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
664
|
+
};
|
|
266
665
|
return partsOf(params.fragment).map((part) => {
|
|
267
666
|
if (typeof part === "string")
|
|
268
667
|
return part;
|
|
269
|
-
if ("value" in part)
|
|
270
|
-
|
|
271
|
-
invalid("SQL parameters cannot be undefined; use null explicitly");
|
|
272
|
-
return collector.add(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
|
|
273
|
-
}
|
|
668
|
+
if ("value" in part)
|
|
669
|
+
return collector.add(encode(part));
|
|
274
670
|
if ("ident" in part)
|
|
275
671
|
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
276
672
|
if ("row" in part) {
|
|
@@ -283,7 +679,14 @@ function renderSqlFragment(params) {
|
|
|
283
679
|
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
284
680
|
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
285
681
|
}
|
|
286
|
-
|
|
682
|
+
if (dialect.greatestForm === "native")
|
|
683
|
+
return `GREATEST(${part.greatest.map(render).join(", ")})`;
|
|
684
|
+
if (!part.greatest.some(containsGreatest)) {
|
|
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)`));
|
|
686
|
+
}
|
|
687
|
+
const [left, right] = part.greatest.map(frameOperand);
|
|
688
|
+
const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
|
|
689
|
+
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})`;
|
|
287
690
|
}).join("");
|
|
288
691
|
}
|
|
289
692
|
function mapConflictColumns(params) {
|
|
@@ -310,7 +713,7 @@ function sqlUsesIncoming(params) {
|
|
|
310
713
|
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
311
714
|
}
|
|
312
715
|
// packages/sql/src/result-decoder.ts
|
|
313
|
-
import { VibeError as
|
|
716
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
314
717
|
|
|
315
718
|
// packages/sql/src/pg-array.ts
|
|
316
719
|
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
@@ -325,7 +728,7 @@ function toPgArrayLiteral(params) {
|
|
|
325
728
|
return `{${parts.join(",")}}`;
|
|
326
729
|
}
|
|
327
730
|
function parsePgArrayLiteral(params) {
|
|
328
|
-
const
|
|
731
|
+
const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
|
|
329
732
|
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
330
733
|
return null;
|
|
331
734
|
const inner = literal.slice(1, -1);
|
|
@@ -363,9 +766,63 @@ function parsePgArrayLiteral(params) {
|
|
|
363
766
|
}
|
|
364
767
|
return out;
|
|
365
768
|
}
|
|
769
|
+
function parsePgArrayLiteralNested(params) {
|
|
770
|
+
const text = params.literal.replace(/^(?:\[-?\d+:-?\d+\])+=/, "");
|
|
771
|
+
const cursor = { index: 0 };
|
|
772
|
+
const parsed = parseDimension({ text, cursor });
|
|
773
|
+
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
774
|
+
}
|
|
775
|
+
function parseDimension(params) {
|
|
776
|
+
const { text, cursor } = params;
|
|
777
|
+
if (text[cursor.index] !== "{")
|
|
778
|
+
return null;
|
|
779
|
+
cursor.index++;
|
|
780
|
+
const out = [];
|
|
781
|
+
if (text[cursor.index] === "}") {
|
|
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++;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (text[cursor.index] !== "}")
|
|
817
|
+
return null;
|
|
818
|
+
cursor.index++;
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
366
823
|
|
|
367
824
|
// packages/sql/src/codecs.ts
|
|
368
|
-
import { VibeError as
|
|
825
|
+
import { VibeError as VibeError5, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
|
|
369
826
|
function enumMembershipCodec(params) {
|
|
370
827
|
const { model, field, enum: enumName } = params;
|
|
371
828
|
const members = new Set(params.members);
|
|
@@ -373,7 +830,7 @@ function enumMembershipCodec(params) {
|
|
|
373
830
|
encode: (value) => {
|
|
374
831
|
if (typeof value === "string" && members.has(value))
|
|
375
832
|
return value;
|
|
376
|
-
throw new
|
|
833
|
+
throw new VibeError5({
|
|
377
834
|
code: "VIBE_VALIDATION",
|
|
378
835
|
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
379
836
|
meta: { model, field, enum: enumName }
|
|
@@ -413,7 +870,7 @@ var bigIntCodec = {
|
|
|
413
870
|
return BigInt(value.trim());
|
|
414
871
|
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
415
872
|
return BigInt(value);
|
|
416
|
-
throw new
|
|
873
|
+
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
|
|
417
874
|
}
|
|
418
875
|
};
|
|
419
876
|
var decimalCodec = {
|
|
@@ -430,6 +887,7 @@ var mysqlDecimalCodec = {
|
|
|
430
887
|
}
|
|
431
888
|
};
|
|
432
889
|
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
890
|
+
var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
|
|
433
891
|
function parseDateTimeText(params) {
|
|
434
892
|
const trimmed = params.text.trim();
|
|
435
893
|
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
@@ -437,13 +895,20 @@ function parseDateTimeText(params) {
|
|
|
437
895
|
if (!Number.isNaN(asUtc.getTime()))
|
|
438
896
|
return asUtc;
|
|
439
897
|
}
|
|
898
|
+
const zoned = PG_ZONED_DATETIME.exec(trimmed);
|
|
899
|
+
if (zoned !== null) {
|
|
900
|
+
const [, date, time, fraction, hours, minutes] = zoned;
|
|
901
|
+
const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
|
|
902
|
+
if (!Number.isNaN(iso.getTime()))
|
|
903
|
+
return iso;
|
|
904
|
+
}
|
|
440
905
|
const parsed = new Date(trimmed);
|
|
441
906
|
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
442
907
|
}
|
|
443
908
|
var dateNativeCodec = {
|
|
444
909
|
encode: (value) => {
|
|
445
910
|
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
446
|
-
throw new
|
|
911
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
447
912
|
}
|
|
448
913
|
return value;
|
|
449
914
|
},
|
|
@@ -451,7 +916,7 @@ var dateNativeCodec = {
|
|
|
451
916
|
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
452
917
|
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
453
918
|
return date;
|
|
454
|
-
throw new
|
|
919
|
+
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
455
920
|
}
|
|
456
921
|
};
|
|
457
922
|
var dateTextCodec = {
|
|
@@ -460,7 +925,7 @@ var dateTextCodec = {
|
|
|
460
925
|
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
461
926
|
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
462
927
|
return date.toISOString();
|
|
463
|
-
throw new
|
|
928
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
464
929
|
},
|
|
465
930
|
decode: dateNativeCodec.decode
|
|
466
931
|
};
|
|
@@ -481,6 +946,44 @@ var mysqlDateCodec = {
|
|
|
481
946
|
},
|
|
482
947
|
decode: dateNativeCodec.decode
|
|
483
948
|
};
|
|
949
|
+
var MILLISECONDS_PER_DAY = 86400000;
|
|
950
|
+
var TIME_OF_DAY_TEXT = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/;
|
|
951
|
+
var pad = (value, width) => String(value).padStart(width, "0");
|
|
952
|
+
var utcMillisecondOfDay = (date) => (date.getTime() % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
|
|
953
|
+
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)}`;
|
|
954
|
+
function makeTimeOfDayCodec(params) {
|
|
955
|
+
const suffix = params.zoned ? "+00" : "";
|
|
956
|
+
return Object.freeze({
|
|
957
|
+
encode: (value) => {
|
|
958
|
+
dateNativeCodec.encode(value);
|
|
959
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
960
|
+
if (date === undefined || !Number.isFinite(date.getTime())) {
|
|
961
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
|
|
962
|
+
}
|
|
963
|
+
return `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
964
|
+
},
|
|
965
|
+
decode: (value) => {
|
|
966
|
+
if (value instanceof Date && Number.isFinite(value.getTime()))
|
|
967
|
+
return new Date(utcMillisecondOfDay(value));
|
|
968
|
+
const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
|
|
969
|
+
if (match !== null) {
|
|
970
|
+
const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
|
|
971
|
+
const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
|
|
972
|
+
const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
|
|
973
|
+
if (millisecond <= MILLISECONDS_PER_DAY) {
|
|
974
|
+
return new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "a time-of-day DateTime column returned a value that is not a time of day", meta: { value } });
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
var timeCodec = makeTimeOfDayCodec({ zoned: false });
|
|
982
|
+
var timetzCodec = makeTimeOfDayCodec({ zoned: true });
|
|
983
|
+
function timeOfDayCodec(params) {
|
|
984
|
+
const type = timeOfDayType({ nativeType: params.nativeType });
|
|
985
|
+
return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
|
|
986
|
+
}
|
|
484
987
|
var booleanIntCodec = {
|
|
485
988
|
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
486
989
|
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
@@ -497,7 +1000,7 @@ var jsonCodec = {
|
|
|
497
1000
|
try {
|
|
498
1001
|
return JSON.parse(value);
|
|
499
1002
|
} catch (cause) {
|
|
500
|
-
throw new
|
|
1003
|
+
throw new VibeError5({
|
|
501
1004
|
code: "VIBE_INTERNAL",
|
|
502
1005
|
message: "a Json column returned text that is not valid JSON",
|
|
503
1006
|
meta: { value },
|
|
@@ -506,6 +1009,39 @@ var jsonCodec = {
|
|
|
506
1009
|
}
|
|
507
1010
|
}
|
|
508
1011
|
};
|
|
1012
|
+
function shortestFloat32(params) {
|
|
1013
|
+
const { value } = params;
|
|
1014
|
+
if (value === 0 || !Number.isFinite(value))
|
|
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;
|
|
1023
|
+
}
|
|
1024
|
+
var float32Codec = {
|
|
1025
|
+
encode: identity,
|
|
1026
|
+
decode: (value) => {
|
|
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
|
+
}
|
|
1044
|
+
}
|
|
509
1045
|
var POSTGRES_CODECS = {
|
|
510
1046
|
String: passthroughCodec,
|
|
511
1047
|
Boolean: passthroughCodec,
|
|
@@ -547,7 +1083,7 @@ var DIALECT_CODECS = {
|
|
|
547
1083
|
|
|
548
1084
|
// packages/sql/src/result-decoder.ts
|
|
549
1085
|
function refuse(encoding, message) {
|
|
550
|
-
throw new
|
|
1086
|
+
throw new VibeError6({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
551
1087
|
}
|
|
552
1088
|
function scalar(type, valid) {
|
|
553
1089
|
const convert = (params, encoding) => {
|
|
@@ -593,7 +1129,7 @@ var extendedDecoder = Object.freeze({
|
|
|
593
1129
|
encode: (params) => {
|
|
594
1130
|
if (params.value === "infinity" || params.value === "-infinity") {
|
|
595
1131
|
if (params.dialect !== "postgres")
|
|
596
|
-
throw new
|
|
1132
|
+
throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
597
1133
|
return params.value;
|
|
598
1134
|
}
|
|
599
1135
|
return dateDecoder.encode(params);
|
|
@@ -651,15 +1187,15 @@ var decode = Object.freeze({
|
|
|
651
1187
|
}
|
|
652
1188
|
});
|
|
653
1189
|
// packages/sql/src/exclusion.ts
|
|
654
|
-
import { VibeError as
|
|
1190
|
+
import { VibeError as VibeError8, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
655
1191
|
|
|
656
1192
|
// packages/sql/src/predicate.ts
|
|
657
|
-
import { VibeError as
|
|
1193
|
+
import { VibeError as VibeError7, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
658
1194
|
function renderPredicate(params) {
|
|
659
1195
|
if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
|
|
660
|
-
throw
|
|
1196
|
+
throw unsupportedCapability8({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
661
1197
|
if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
|
|
662
|
-
throw
|
|
1198
|
+
throw unsupportedCapability8({ dialect: params.dialect.name, feature: "partialIndexes" });
|
|
663
1199
|
}
|
|
664
1200
|
const context = {
|
|
665
1201
|
model: params.model,
|
|
@@ -694,7 +1230,7 @@ var INT64_MIN = -(2n ** 63n);
|
|
|
694
1230
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
695
1231
|
function refuse2(params) {
|
|
696
1232
|
const { context, rule, message } = params;
|
|
697
|
-
throw new
|
|
1233
|
+
throw new VibeError7({
|
|
698
1234
|
code: "VIBE_SCHEMA_INVALID",
|
|
699
1235
|
message: `${context.site} on "${context.model.name}": ${message}`,
|
|
700
1236
|
meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
|
|
@@ -787,7 +1323,7 @@ function resolveField(params) {
|
|
|
787
1323
|
});
|
|
788
1324
|
}
|
|
789
1325
|
if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
|
|
790
|
-
throw
|
|
1326
|
+
throw unsupportedCapability8({
|
|
791
1327
|
dialect: "mysql",
|
|
792
1328
|
feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
|
|
793
1329
|
hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
|
|
@@ -958,12 +1494,12 @@ function describe(params) {
|
|
|
958
1494
|
|
|
959
1495
|
// packages/sql/src/exclusion.ts
|
|
960
1496
|
function invalid2(rule) {
|
|
961
|
-
throw new
|
|
1497
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
962
1498
|
}
|
|
963
1499
|
function compileExclusion(params) {
|
|
964
1500
|
const { exclusion: spec, model, dialect } = params;
|
|
965
1501
|
if (!dialect.capabilities.exclusionConstraints)
|
|
966
|
-
throw
|
|
1502
|
+
throw unsupportedCapability9({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
967
1503
|
if (!spec.name || spec.name.includes("\x00"))
|
|
968
1504
|
invalid2("name");
|
|
969
1505
|
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
@@ -1006,7 +1542,7 @@ function compileExclusion(params) {
|
|
|
1006
1542
|
const column = field(key.field);
|
|
1007
1543
|
if (column.isOptional)
|
|
1008
1544
|
invalid2("nullable-key-requires-expression");
|
|
1009
|
-
if (!["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
|
|
1545
|
+
if (!isEnumType2(column.type) && !["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
|
|
1010
1546
|
invalid2("key-type");
|
|
1011
1547
|
return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
|
|
1012
1548
|
});
|
|
@@ -1016,13 +1552,13 @@ function compileExclusion(params) {
|
|
|
1016
1552
|
}
|
|
1017
1553
|
function renderExclusionSetup(params) {
|
|
1018
1554
|
if (!params.dialect.capabilities.exclusionConstraints)
|
|
1019
|
-
throw
|
|
1555
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
1020
1556
|
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
1021
1557
|
}
|
|
1022
1558
|
function renderExclusionDdl(params) {
|
|
1023
1559
|
const { dialect, table, exclusion, operation } = params;
|
|
1024
1560
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1025
|
-
throw
|
|
1561
|
+
throw unsupportedCapability9({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1026
1562
|
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
1027
1563
|
if (operation === "drop")
|
|
1028
1564
|
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
@@ -1031,15 +1567,15 @@ function renderExclusionDdl(params) {
|
|
|
1031
1567
|
function renderExclusionCatalog(params) {
|
|
1032
1568
|
const { dialect, probe } = params;
|
|
1033
1569
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1034
|
-
throw
|
|
1035
|
-
const scope = probe === undefined ?
|
|
1570
|
+
throw unsupportedCapability9({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1571
|
+
const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
1036
1572
|
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`;
|
|
1037
1573
|
}
|
|
1038
1574
|
function renderExclusionProbe(params) {
|
|
1039
1575
|
const { dialect, table, probe, definition } = params;
|
|
1040
1576
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1041
|
-
throw
|
|
1042
|
-
return [`CREATE TEMP TABLE ${dialect.quoteIdent({ name: probe })} (LIKE ${dialect.quoteIdent({ name: "public" })}.${dialect.quoteIdent({ name: table })})`, `ALTER TABLE ${dialect.quoteIdent({ name: probe })} ADD CONSTRAINT ${dialect.quoteIdent({ name: `${probe}_x` })} ${definition}`];
|
|
1577
|
+
throw unsupportedCapability9({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1578
|
+
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}`];
|
|
1043
1579
|
}
|
|
1044
1580
|
// packages/sql/src/ast.ts
|
|
1045
1581
|
var SET_OPERATORS = ["increment", "decrement", "multiply", "divide"];
|
|
@@ -1081,7 +1617,7 @@ function isSqlDefaultCell(value) {
|
|
|
1081
1617
|
return value === SQL_DEFAULT_CELL;
|
|
1082
1618
|
}
|
|
1083
1619
|
function jsonParamCell(params) {
|
|
1084
|
-
return { __jsonParam: true, text: params.text };
|
|
1620
|
+
return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
|
|
1085
1621
|
}
|
|
1086
1622
|
function isJsonParamCell(value) {
|
|
1087
1623
|
return typeof value === "object" && value !== null && value.__jsonParam === true;
|
|
@@ -1092,9 +1628,32 @@ function excludedRef(params) {
|
|
|
1092
1628
|
function isExcludedRef(value) {
|
|
1093
1629
|
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
1094
1630
|
}
|
|
1631
|
+
function returningColumnName(column) {
|
|
1632
|
+
return typeof column === "string" ? column : column.name;
|
|
1633
|
+
}
|
|
1095
1634
|
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
1635
|
+
function stepColumnRef(params) {
|
|
1636
|
+
return { __stepColumn: true, step: params.step, column: params.column };
|
|
1637
|
+
}
|
|
1638
|
+
function isStepColumnRef(value) {
|
|
1639
|
+
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
1640
|
+
}
|
|
1641
|
+
var CHAIN_VETO_PREFIX = "__vibe_veto:";
|
|
1642
|
+
var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
1643
|
+
function chainVetoToken(params) {
|
|
1644
|
+
const { message, nonce } = params;
|
|
1645
|
+
if (nonce === "")
|
|
1646
|
+
return;
|
|
1647
|
+
for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
|
|
1648
|
+
const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
|
|
1649
|
+
const match = /^([a-z][a-z0-9_]*):/.exec(rest);
|
|
1650
|
+
if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
|
|
1651
|
+
return match[1];
|
|
1652
|
+
}
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1096
1655
|
// packages/sql/src/locks.ts
|
|
1097
|
-
import { internalError, unsupportedCapability as
|
|
1656
|
+
import { internalError, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
1098
1657
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
1099
1658
|
strengths: Object.freeze({
|
|
1100
1659
|
update: "FOR UPDATE",
|
|
@@ -1114,7 +1673,7 @@ function renderRowLockClause(params) {
|
|
|
1114
1673
|
if (syntax === null) {
|
|
1115
1674
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
1116
1675
|
return null;
|
|
1117
|
-
throw
|
|
1676
|
+
throw unsupportedCapability10({
|
|
1118
1677
|
dialect: dialect.name,
|
|
1119
1678
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
1120
1679
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -1122,7 +1681,7 @@ function renderRowLockClause(params) {
|
|
|
1122
1681
|
}
|
|
1123
1682
|
const strengthText = syntax.strengths[lock.strength];
|
|
1124
1683
|
if (strengthText === undefined) {
|
|
1125
|
-
throw
|
|
1684
|
+
throw unsupportedCapability10({
|
|
1126
1685
|
dialect: dialect.name,
|
|
1127
1686
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
1128
1687
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -1130,7 +1689,7 @@ function renderRowLockClause(params) {
|
|
|
1130
1689
|
}
|
|
1131
1690
|
const waitText = syntax.wait[lock.wait];
|
|
1132
1691
|
if (waitText === undefined) {
|
|
1133
|
-
throw
|
|
1692
|
+
throw unsupportedCapability10({
|
|
1134
1693
|
dialect: dialect.name,
|
|
1135
1694
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
1136
1695
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -1149,7 +1708,7 @@ function renderAdvisoryLock(params) {
|
|
|
1149
1708
|
const { dialect, statement, collector } = params;
|
|
1150
1709
|
const functions = dialect.advisoryLockFunctions;
|
|
1151
1710
|
if (functions === null) {
|
|
1152
|
-
throw
|
|
1711
|
+
throw unsupportedCapability10({
|
|
1153
1712
|
dialect: dialect.name,
|
|
1154
1713
|
feature: "advisory locks",
|
|
1155
1714
|
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`
|
|
@@ -1182,33 +1741,13 @@ function projectionOutputName(params) {
|
|
|
1182
1741
|
const { column } = params;
|
|
1183
1742
|
return column.transform === undefined ? column.alias ?? column.name : column.name;
|
|
1184
1743
|
}
|
|
1185
|
-
// packages/sql/src/capabilities.ts
|
|
1186
|
-
import {
|
|
1187
|
-
DIALECT_CAPABILITIES,
|
|
1188
|
-
POSTGRES_CAPABILITIES,
|
|
1189
|
-
SQLITE_CAPABILITIES,
|
|
1190
|
-
MYSQL_CAPABILITIES
|
|
1191
|
-
} from "@vibeorm/schema";
|
|
1192
|
-
|
|
1193
|
-
// packages/sql/src/identifiers.ts
|
|
1194
|
-
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
1195
|
-
function assertNoNul(params) {
|
|
1196
|
-
if (params.name.includes("\x00")) {
|
|
1197
|
-
throw new VibeError6({
|
|
1198
|
-
code: "VIBE_VALIDATION",
|
|
1199
|
-
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
1200
|
-
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
return params.name;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
1744
|
// packages/sql/src/dialects/postgres.ts
|
|
1207
1745
|
var postgresDialect = {
|
|
1208
1746
|
name: "postgres",
|
|
1209
1747
|
capabilities: POSTGRES_CAPABILITIES,
|
|
1210
1748
|
inArrayStrategy: "any",
|
|
1211
1749
|
tupleInStrategy: "rowValue",
|
|
1750
|
+
timeOfDayInStrategy: "in",
|
|
1212
1751
|
upsertForm: "onConflict",
|
|
1213
1752
|
conflictKeyEquality: {
|
|
1214
1753
|
foldingScalars: ["Decimal"],
|
|
@@ -1219,6 +1758,7 @@ var postgresDialect = {
|
|
|
1219
1758
|
doNothingForm: "onConflictDoNothing",
|
|
1220
1759
|
insensitiveLike: "ilike",
|
|
1221
1760
|
strictMatchStrategy: "native",
|
|
1761
|
+
greatestForm: "native",
|
|
1222
1762
|
orderByNullsMode: "native",
|
|
1223
1763
|
offsetRequiresLimit: null,
|
|
1224
1764
|
likeEscapeClause: null,
|
|
@@ -1255,6 +1795,7 @@ var postgresDialect = {
|
|
|
1255
1795
|
keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
|
|
1256
1796
|
},
|
|
1257
1797
|
rowDefaultKeyword: true,
|
|
1798
|
+
statementChain: "dataModifyingCte",
|
|
1258
1799
|
paramBudget: 30000,
|
|
1259
1800
|
booleanLiteral: { true: "true", false: "false" },
|
|
1260
1801
|
quoteIdent({ name }) {
|
|
@@ -1272,63 +1813,13 @@ var postgresDialect = {
|
|
|
1272
1813
|
return `(now() + make_interval(secs => ${collector.add(offsetSeconds)}))`;
|
|
1273
1814
|
}
|
|
1274
1815
|
};
|
|
1275
|
-
// packages/sql/src/dialects/sqlite.ts
|
|
1276
|
-
var sqliteDialect = {
|
|
1277
|
-
name: "sqlite",
|
|
1278
|
-
capabilities: SQLITE_CAPABILITIES,
|
|
1279
|
-
inArrayStrategy: "expand",
|
|
1280
|
-
tupleInStrategy: "rowValue",
|
|
1281
|
-
upsertForm: "onConflict",
|
|
1282
|
-
conflictKeyEquality: {
|
|
1283
|
-
foldingScalars: [],
|
|
1284
|
-
textIsBinaryByDefault: true,
|
|
1285
|
-
duplicatesInOneStatement: "lastWriteWins"
|
|
1286
|
-
},
|
|
1287
|
-
excludedRowRef: { form: "keyword", name: "excluded" },
|
|
1288
|
-
doNothingForm: "onConflictDoNothing",
|
|
1289
|
-
insensitiveLike: "lower",
|
|
1290
|
-
strictMatchStrategy: "glob",
|
|
1291
|
-
orderByNullsMode: "native",
|
|
1292
|
-
offsetRequiresLimit: { limitText: "-1" },
|
|
1293
|
-
likeEscapeClause: " ESCAPE '\\'",
|
|
1294
|
-
jsonPathMode: "sqliteExtract",
|
|
1295
|
-
supportedJsonOps: [
|
|
1296
|
-
"equals",
|
|
1297
|
-
"string_contains",
|
|
1298
|
-
"string_starts_with",
|
|
1299
|
-
"string_ends_with",
|
|
1300
|
-
"array_contains",
|
|
1301
|
-
"array_starts_with",
|
|
1302
|
-
"array_ends_with"
|
|
1303
|
-
],
|
|
1304
|
-
jsonParamCast: "",
|
|
1305
|
-
lastInsertIdSql: null,
|
|
1306
|
-
rowLockSyntax: null,
|
|
1307
|
-
advisoryLockFunctions: null,
|
|
1308
|
-
rowDefaultKeyword: false,
|
|
1309
|
-
paramBudget: 900,
|
|
1310
|
-
booleanLiteral: { true: "1", false: "0" },
|
|
1311
|
-
quoteIdent({ name }) {
|
|
1312
|
-
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
1313
|
-
},
|
|
1314
|
-
quoteStringLiteral({ value }) {
|
|
1315
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
1316
|
-
},
|
|
1317
|
-
placeholder() {
|
|
1318
|
-
return "?";
|
|
1319
|
-
},
|
|
1320
|
-
renderDbNow({ offsetSeconds, collector }) {
|
|
1321
|
-
if (offsetSeconds === 0)
|
|
1322
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1323
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
1324
|
-
}
|
|
1325
|
-
};
|
|
1326
1816
|
// packages/sql/src/dialects/mysql.ts
|
|
1327
1817
|
var mysqlDialect = {
|
|
1328
1818
|
name: "mysql",
|
|
1329
1819
|
capabilities: MYSQL_CAPABILITIES,
|
|
1330
1820
|
inArrayStrategy: "expand",
|
|
1331
1821
|
tupleInStrategy: "rowValue",
|
|
1822
|
+
timeOfDayInStrategy: "equalities",
|
|
1332
1823
|
upsertForm: "onDuplicateKey",
|
|
1333
1824
|
conflictKeyEquality: {
|
|
1334
1825
|
foldingScalars: ["Decimal"],
|
|
@@ -1340,6 +1831,7 @@ var mysqlDialect = {
|
|
|
1340
1831
|
doNothingForm: "insertIgnore",
|
|
1341
1832
|
insensitiveLike: "lower",
|
|
1342
1833
|
strictMatchStrategy: "binary",
|
|
1834
|
+
greatestForm: "wrapped-frame",
|
|
1343
1835
|
orderByNullsMode: "emulate",
|
|
1344
1836
|
offsetRequiresLimit: { limitText: "18446744073709551615" },
|
|
1345
1837
|
likeEscapeClause: null,
|
|
@@ -1361,6 +1853,7 @@ var mysqlDialect = {
|
|
|
1361
1853
|
},
|
|
1362
1854
|
advisoryLockFunctions: null,
|
|
1363
1855
|
rowDefaultKeyword: true,
|
|
1856
|
+
statementChain: null,
|
|
1364
1857
|
paramBudget: 1e4,
|
|
1365
1858
|
booleanLiteral: { true: "1", false: "0" },
|
|
1366
1859
|
quoteIdent({ name }) {
|
|
@@ -1388,7 +1881,7 @@ function getSqlDialect(params) {
|
|
|
1388
1881
|
return SQL_DIALECTS[params.dialect];
|
|
1389
1882
|
}
|
|
1390
1883
|
// packages/sql/src/dialects/diagnostics.ts
|
|
1391
|
-
import { unsupportedCapability as
|
|
1884
|
+
import { unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
1392
1885
|
var QUERIES = Object.freeze({
|
|
1393
1886
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
1394
1887
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -1396,7 +1889,7 @@ var QUERIES = Object.freeze({
|
|
|
1396
1889
|
function renderDiagnostic(params) {
|
|
1397
1890
|
const query = QUERIES[params.query];
|
|
1398
1891
|
if (params.dialect.name !== query.dialect) {
|
|
1399
|
-
throw
|
|
1892
|
+
throw unsupportedCapability11({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
1400
1893
|
}
|
|
1401
1894
|
return { text: query.text, values: [], returning: "none" };
|
|
1402
1895
|
}
|
|
@@ -1405,7 +1898,7 @@ function isFixedDiagnostic(params) {
|
|
|
1405
1898
|
}
|
|
1406
1899
|
|
|
1407
1900
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
1408
|
-
import { VibeError as
|
|
1901
|
+
import { VibeError as VibeError9, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
1409
1902
|
function renderPostgresExplain(params) {
|
|
1410
1903
|
const { dialect, query, options } = params;
|
|
1411
1904
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -1440,7 +1933,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
1440
1933
|
});
|
|
1441
1934
|
function requirePostgres(params) {
|
|
1442
1935
|
if (params.dialect.name !== "postgres") {
|
|
1443
|
-
throw
|
|
1936
|
+
throw unsupportedCapability12({
|
|
1444
1937
|
dialect: params.dialect.name,
|
|
1445
1938
|
feature: params.feature,
|
|
1446
1939
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -1458,7 +1951,7 @@ function renderPostgresWorkload(params) {
|
|
|
1458
1951
|
if (query === "statementSnapshot") {
|
|
1459
1952
|
const bounded = limit ?? 20;
|
|
1460
1953
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
1461
|
-
throw new
|
|
1954
|
+
throw new VibeError9({
|
|
1462
1955
|
code: "VIBE_CONFIG",
|
|
1463
1956
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
1464
1957
|
meta: { limit }
|
|
@@ -1477,7 +1970,7 @@ function renderPostgresWorkload(params) {
|
|
|
1477
1970
|
function requireArg(params) {
|
|
1478
1971
|
const { value, name, query } = params;
|
|
1479
1972
|
if (value === undefined || value.length === 0) {
|
|
1480
|
-
throw new
|
|
1973
|
+
throw new VibeError9({
|
|
1481
1974
|
code: "VIBE_CONFIG",
|
|
1482
1975
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
1483
1976
|
meta: { query, argument: name }
|
|
@@ -1487,10 +1980,10 @@ function requireArg(params) {
|
|
|
1487
1980
|
}
|
|
1488
1981
|
|
|
1489
1982
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
1490
|
-
import { unsupportedCapability as
|
|
1983
|
+
import { unsupportedCapability as unsupportedCapability14 } from "@vibeorm/schema";
|
|
1491
1984
|
|
|
1492
1985
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
1493
|
-
import { RlsContextError, VibeError as
|
|
1986
|
+
import { RlsContextError, VibeError as VibeError10, assertValidSchemaIR, unsupportedCapability as unsupportedCapability13, validateRlsContextIR } from "@vibeorm/schema";
|
|
1494
1987
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
1495
1988
|
var COLUMN_TYPES = {
|
|
1496
1989
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -1503,18 +1996,18 @@ var COLUMN_TYPES = {
|
|
|
1503
1996
|
};
|
|
1504
1997
|
function requireRls(params) {
|
|
1505
1998
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
1506
|
-
throw
|
|
1999
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
1507
2000
|
}
|
|
1508
2001
|
}
|
|
1509
2002
|
function identifier2(params) {
|
|
1510
2003
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
1511
|
-
throw new
|
|
2004
|
+
throw new VibeError10({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
1512
2005
|
}
|
|
1513
2006
|
return params.dialect.quoteIdent({ name: params.name });
|
|
1514
2007
|
}
|
|
1515
2008
|
function tableRef(params) {
|
|
1516
2009
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
1517
|
-
throw
|
|
2010
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
1518
2011
|
}
|
|
1519
2012
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
1520
2013
|
}
|
|
@@ -1555,14 +2048,14 @@ function renderRlsColumnType(params) {
|
|
|
1555
2048
|
if (native !== undefined)
|
|
1556
2049
|
return native;
|
|
1557
2050
|
}
|
|
1558
|
-
throw
|
|
2051
|
+
throw unsupportedCapability13({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
1559
2052
|
}
|
|
1560
2053
|
function protectedModel(params) {
|
|
1561
2054
|
requireRls(params);
|
|
1562
2055
|
assertValidSchemaIR({ schema: params.schema });
|
|
1563
2056
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
1564
2057
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
1565
|
-
throw new
|
|
2058
|
+
throw new VibeError10({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
1566
2059
|
}
|
|
1567
2060
|
return model;
|
|
1568
2061
|
}
|
|
@@ -1595,7 +2088,7 @@ function predicate(params) {
|
|
|
1595
2088
|
}).join(" AND ");
|
|
1596
2089
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
1597
2090
|
}
|
|
1598
|
-
throw new
|
|
2091
|
+
throw new VibeError10({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
1599
2092
|
}
|
|
1600
2093
|
function renderRlsPolicy(params) {
|
|
1601
2094
|
const model = protectedModel(params);
|
|
@@ -1629,7 +2122,7 @@ function renderRlsContext(params) {
|
|
|
1629
2122
|
function renderRlsRole(params) {
|
|
1630
2123
|
requireRls(params);
|
|
1631
2124
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
1632
|
-
throw new
|
|
2125
|
+
throw new VibeError10({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
1633
2126
|
}
|
|
1634
2127
|
const collector = new ParamCollector(params.dialect);
|
|
1635
2128
|
return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
|
|
@@ -1781,7 +2274,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
1781
2274
|
});
|
|
1782
2275
|
function requireRls2(params) {
|
|
1783
2276
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
1784
|
-
throw
|
|
2277
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
1785
2278
|
}
|
|
1786
2279
|
}
|
|
1787
2280
|
function renderRlsReadinessQuery(params) {
|
|
@@ -1813,7 +2306,7 @@ function renderRlsPolicyPath(params) {
|
|
|
1813
2306
|
}
|
|
1814
2307
|
|
|
1815
2308
|
// packages/sql/src/render.ts
|
|
1816
|
-
import { internalError as internalError2, unsupportedCapability as
|
|
2309
|
+
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
1817
2310
|
|
|
1818
2311
|
// packages/sql/src/like.ts
|
|
1819
2312
|
function escapeLikePattern(params) {
|
|
@@ -1867,6 +2360,8 @@ function renderStatement(params) {
|
|
|
1867
2360
|
return renderUpdate({ dialect, statement, collector });
|
|
1868
2361
|
case "delete":
|
|
1869
2362
|
return renderDelete({ dialect, statement, collector });
|
|
2363
|
+
case "chain":
|
|
2364
|
+
return renderChain({ dialect, statement, collector });
|
|
1870
2365
|
case "rlsReadiness":
|
|
1871
2366
|
return renderRlsReadinessQuery({ dialect, ...statement });
|
|
1872
2367
|
case "rlsProbeTable":
|
|
@@ -1900,8 +2395,29 @@ function quoteCol(dialect, column) {
|
|
|
1900
2395
|
}
|
|
1901
2396
|
return `CAST(${ref} AS REAL)`;
|
|
1902
2397
|
}
|
|
2398
|
+
if (column.castJsonb !== undefined) {
|
|
2399
|
+
if (dialect.name !== "postgres") {
|
|
2400
|
+
throw internalError2({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
2401
|
+
}
|
|
2402
|
+
return `${ref}::${column.castJsonb}`;
|
|
2403
|
+
}
|
|
1903
2404
|
return ref;
|
|
1904
2405
|
}
|
|
2406
|
+
function typedOrderRef(params) {
|
|
2407
|
+
const { statement, column } = params;
|
|
2408
|
+
if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined)
|
|
2409
|
+
return column;
|
|
2410
|
+
const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
|
|
2411
|
+
return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
|
|
2412
|
+
}
|
|
2413
|
+
function jsonCellCast(dialect, cell) {
|
|
2414
|
+
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
2415
|
+
return dialect.jsonParamCast;
|
|
2416
|
+
if (dialect.name !== "postgres") {
|
|
2417
|
+
throw internalError2({ message: "JsonParamCell storage json is a postgres construct" });
|
|
2418
|
+
}
|
|
2419
|
+
return "::text::json";
|
|
2420
|
+
}
|
|
1905
2421
|
function quoteProjection(dialect, column) {
|
|
1906
2422
|
if (column.transform === undefined) {
|
|
1907
2423
|
const base = quoteCol(dialect, column);
|
|
@@ -1910,7 +2426,7 @@ function quoteProjection(dialect, column) {
|
|
|
1910
2426
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
1911
2427
|
}
|
|
1912
2428
|
if (dialect.name !== "postgres") {
|
|
1913
|
-
throw internalError2({ message: "ColRef.transform is a postgres
|
|
2429
|
+
throw internalError2({ message: "ColRef.transform is a postgres construct" });
|
|
1914
2430
|
}
|
|
1915
2431
|
const ref = quoteCol(dialect, column);
|
|
1916
2432
|
const alias = dialect.quoteIdent({ name: column.name });
|
|
@@ -1918,8 +2434,15 @@ function quoteProjection(dialect, column) {
|
|
|
1918
2434
|
return `${ref}::text AS ${alias}`;
|
|
1919
2435
|
return `encode(${ref}, 'base64') AS ${alias}`;
|
|
1920
2436
|
}
|
|
2437
|
+
function renderProjection(params) {
|
|
2438
|
+
const { dialect, columns } = params;
|
|
2439
|
+
return columns.map((column) => quoteProjection(dialect, column)).join(", ");
|
|
2440
|
+
}
|
|
1921
2441
|
function renderTable(dialect, table) {
|
|
1922
|
-
|
|
2442
|
+
if (table.namespace !== undefined)
|
|
2443
|
+
assertSqlNamespace2({ namespace: table.namespace, dialect: dialect.name });
|
|
2444
|
+
const relation = dialect.quoteIdent({ name: table.name });
|
|
2445
|
+
const name = table.namespace === undefined ? relation : `${dialect.quoteIdent({ name: table.namespace })}.${relation}`;
|
|
1923
2446
|
if (table.alias === undefined)
|
|
1924
2447
|
return name;
|
|
1925
2448
|
return `${name} AS ${dialect.quoteIdent({ name: table.alias })}`;
|
|
@@ -1958,7 +2481,7 @@ function renderAggRef(dialect, agg) {
|
|
|
1958
2481
|
function assertScalarArrays(dialect, feature) {
|
|
1959
2482
|
if (dialect.capabilities.scalarArrays)
|
|
1960
2483
|
return;
|
|
1961
|
-
throw
|
|
2484
|
+
throw unsupportedCapability15({
|
|
1962
2485
|
dialect: dialect.name,
|
|
1963
2486
|
feature,
|
|
1964
2487
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -1975,7 +2498,8 @@ function renderExpr(params) {
|
|
|
1975
2498
|
if (isDbNowMarker(expr.value)) {
|
|
1976
2499
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
1977
2500
|
}
|
|
1978
|
-
|
|
2501
|
+
const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
|
|
2502
|
+
return `${compared.column} ${op} ${compared.operand}`;
|
|
1979
2503
|
}
|
|
1980
2504
|
case "cmpCol": {
|
|
1981
2505
|
const op = COMPARISON_SQL2[expr.op];
|
|
@@ -1991,8 +2515,12 @@ function renderExpr(params) {
|
|
|
1991
2515
|
return expr.negate === true ? "1 = 1" : "1 = 0";
|
|
1992
2516
|
}
|
|
1993
2517
|
const column = quoteCol(dialect, expr.column);
|
|
2518
|
+
if (expr.timeOfDay === true && dialect.timeOfDayInStrategy === "equalities") {
|
|
2519
|
+
const equalities = collector.addMany(expr.values).map((placeholder) => `${column} = ${placeholder}`);
|
|
2520
|
+
return `${expr.negate === true ? "NOT " : ""}(${equalities.join(" OR ")})`;
|
|
2521
|
+
}
|
|
1994
2522
|
if (dialect.inArrayStrategy === "any") {
|
|
1995
|
-
const base = `${column} = ANY(${collector.add([...expr.values])})`;
|
|
2523
|
+
const base = `${column} = ANY(${collector.add(Array.isArray(expr.values) ? [...expr.values] : expr.values)})`;
|
|
1996
2524
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
1997
2525
|
}
|
|
1998
2526
|
const placeholders = collector.addMany(expr.values);
|
|
@@ -2084,16 +2612,21 @@ function renderExpr(params) {
|
|
|
2084
2612
|
}
|
|
2085
2613
|
case "arrayHas": {
|
|
2086
2614
|
assertScalarArrays(dialect, 'the scalar-list filter "has"');
|
|
2087
|
-
const
|
|
2615
|
+
const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
|
|
2616
|
+
const base = `${compared.operand} = ANY(${compared.column})`;
|
|
2088
2617
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
2089
2618
|
}
|
|
2090
2619
|
case "arrayContainsAll": {
|
|
2091
2620
|
assertScalarArrays(dialect, 'the scalar-list filter "hasEvery"');
|
|
2092
|
-
|
|
2621
|
+
const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
|
|
2622
|
+
const compared = listComparison({ dialect, column: expr.column, value: values, collector });
|
|
2623
|
+
return `${compared.column} @> ${compared.operand}`;
|
|
2093
2624
|
}
|
|
2094
2625
|
case "arrayOverlaps": {
|
|
2095
2626
|
assertScalarArrays(dialect, 'the scalar-list filter "hasSome"');
|
|
2096
|
-
|
|
2627
|
+
const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
|
|
2628
|
+
const compared = listComparison({ dialect, column: expr.column, value: values, collector });
|
|
2629
|
+
return `${compared.column} && ${compared.operand}`;
|
|
2097
2630
|
}
|
|
2098
2631
|
case "arrayIsEmpty": {
|
|
2099
2632
|
assertScalarArrays(dialect, 'the scalar-list filter "isEmpty"');
|
|
@@ -2141,6 +2674,16 @@ function jsonPathString(path) {
|
|
|
2141
2674
|
}
|
|
2142
2675
|
return out;
|
|
2143
2676
|
}
|
|
2677
|
+
function listComparison(params) {
|
|
2678
|
+
const { dialect, column, value, collector } = params;
|
|
2679
|
+
if (isJsonListParameter(value) && value.storage === "json") {
|
|
2680
|
+
return {
|
|
2681
|
+
column: `${quoteCol(dialect, column)}::jsonb[]`,
|
|
2682
|
+
operand: collector.add(jsonListParameter({ value: value.value, storage: "jsonb" }))
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
return { column: quoteCol(dialect, column), operand: collector.add(value) };
|
|
2686
|
+
}
|
|
2144
2687
|
function jsonStringPattern(params) {
|
|
2145
2688
|
const { expr } = params;
|
|
2146
2689
|
if (typeof expr.value !== "string") {
|
|
@@ -2168,7 +2711,7 @@ function renderJsonLike(params) {
|
|
|
2168
2711
|
function renderJsonFilter(params) {
|
|
2169
2712
|
const { dialect, expr, collector } = params;
|
|
2170
2713
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
2171
|
-
throw
|
|
2714
|
+
throw unsupportedCapability15({
|
|
2172
2715
|
dialect: dialect.name,
|
|
2173
2716
|
feature: `the json filter "${expr.op}"`,
|
|
2174
2717
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -2178,19 +2721,22 @@ function renderJsonFilter(params) {
|
|
|
2178
2721
|
if (dialect.jsonPathMode === "operators") {
|
|
2179
2722
|
const column = quoteCol(dialect, expr.column);
|
|
2180
2723
|
const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
|
|
2724
|
+
const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
|
|
2181
2725
|
switch (expr.op) {
|
|
2182
2726
|
case "equals":
|
|
2183
|
-
return
|
|
2727
|
+
return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2184
2728
|
case "string_contains":
|
|
2185
2729
|
case "string_starts_with":
|
|
2186
|
-
case "string_ends_with":
|
|
2187
|
-
|
|
2730
|
+
case "string_ends_with": {
|
|
2731
|
+
const target = expr.storage === "json" ? `(${extracted} #>> '{}')` : `(${column} #>> ${pathParam})`;
|
|
2732
|
+
return renderJsonLike({ dialect, target, expr, collector });
|
|
2733
|
+
}
|
|
2188
2734
|
case "array_contains":
|
|
2189
|
-
return
|
|
2735
|
+
return `${extracted} @> ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2190
2736
|
case "array_starts_with":
|
|
2191
|
-
return
|
|
2737
|
+
return `${extracted} -> 0 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2192
2738
|
case "array_ends_with":
|
|
2193
|
-
return
|
|
2739
|
+
return `${extracted} -> -1 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2194
2740
|
}
|
|
2195
2741
|
}
|
|
2196
2742
|
if (dialect.jsonPathMode === "sqliteExtract") {
|
|
@@ -2232,7 +2778,7 @@ function renderJsonFilter(params) {
|
|
|
2232
2778
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
2233
2779
|
const tests = elements.map((element) => {
|
|
2234
2780
|
if (element === null || typeof element === "object" && element !== null) {
|
|
2235
|
-
throw
|
|
2781
|
+
throw unsupportedCapability15({
|
|
2236
2782
|
dialect: dialect.name,
|
|
2237
2783
|
feature: "json array_contains with object/array/null elements",
|
|
2238
2784
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -2334,7 +2880,14 @@ function renderLateralJoin(params) {
|
|
|
2334
2880
|
const terms = join.select.orderBy;
|
|
2335
2881
|
const columnTerms = terms.filter((term) => ("column" in term));
|
|
2336
2882
|
if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
|
|
2337
|
-
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
2883
|
+
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
2884
|
+
dialect,
|
|
2885
|
+
term: {
|
|
2886
|
+
...term,
|
|
2887
|
+
column: { table: "__sub", name: term.column.name, ...term.column.castJsonb === undefined ? {} : { castJsonb: term.column.castJsonb } }
|
|
2888
|
+
},
|
|
2889
|
+
collector
|
|
2890
|
+
}));
|
|
2338
2891
|
aggOrder = ` ORDER BY ${rendered.join(", ")}`;
|
|
2339
2892
|
}
|
|
2340
2893
|
}
|
|
@@ -2343,6 +2896,15 @@ function renderLateralJoin(params) {
|
|
|
2343
2896
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
2344
2897
|
return `LEFT JOIN LATERAL (SELECT ${projection} FROM (${inner.text}) AS ${subAlias}) AS ${alias} ON true`;
|
|
2345
2898
|
}
|
|
2899
|
+
function renderCountSubquery(params) {
|
|
2900
|
+
const { dialect, subquery, collector } = params;
|
|
2901
|
+
const select = subquery.select;
|
|
2902
|
+
if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined) {
|
|
2903
|
+
throw internalError2({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
2904
|
+
}
|
|
2905
|
+
const inner = renderSelect({ dialect, statement: select, collector });
|
|
2906
|
+
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
2907
|
+
}
|
|
2346
2908
|
function applyReturning(params) {
|
|
2347
2909
|
const { dialect, requested } = params;
|
|
2348
2910
|
if (requested === undefined || requested.length === 0) {
|
|
@@ -2351,9 +2913,65 @@ function applyReturning(params) {
|
|
|
2351
2913
|
if (!dialect.capabilities.returning) {
|
|
2352
2914
|
return { suffix: "", status: "omitted" };
|
|
2353
2915
|
}
|
|
2354
|
-
const columns = requested.map((
|
|
2916
|
+
const columns = requested.map((column) => {
|
|
2917
|
+
if (typeof column === "string")
|
|
2918
|
+
return dialect.quoteIdent({ name: column });
|
|
2919
|
+
if (dialect.name !== "postgres") {
|
|
2920
|
+
throw internalError2({ message: "a castText RETURNING column is a postgres construct" });
|
|
2921
|
+
}
|
|
2922
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
2923
|
+
return `${name}::text AS ${name}`;
|
|
2924
|
+
}).join(", ");
|
|
2355
2925
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
2356
2926
|
}
|
|
2927
|
+
function renderKeyTableJoin(params) {
|
|
2928
|
+
const { dialect, join, collector } = params;
|
|
2929
|
+
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
2930
|
+
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
2931
|
+
if (!admitted) {
|
|
2932
|
+
throw unsupportedCapability15({
|
|
2933
|
+
dialect: dialect.name,
|
|
2934
|
+
feature: "relation key table",
|
|
2935
|
+
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
2939
|
+
throw internalError2({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
2940
|
+
}
|
|
2941
|
+
if (join.rows.length === 0)
|
|
2942
|
+
throw internalError2({ message: "key table needs at least one key row" });
|
|
2943
|
+
for (const member of join.members) {
|
|
2944
|
+
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
2945
|
+
throw internalError2({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
const alias = dialect.quoteIdent({ name: join.alias });
|
|
2949
|
+
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
2950
|
+
const rows = join.rows.map((row, ordinal) => {
|
|
2951
|
+
if (row.length !== join.members.length) {
|
|
2952
|
+
throw internalError2({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
2953
|
+
}
|
|
2954
|
+
const cells = row.map((value, index) => {
|
|
2955
|
+
const placeholder = collector.add(value);
|
|
2956
|
+
const member = join.members[index];
|
|
2957
|
+
switch (member.kind) {
|
|
2958
|
+
case "integer":
|
|
2959
|
+
return dialect.name === "mysql" ? `CAST(${placeholder} AS SIGNED)` : `CAST(${placeholder} AS bigint)`;
|
|
2960
|
+
case "decimal":
|
|
2961
|
+
return `CAST(${placeholder} AS DECIMAL(65,${member.scale}))`;
|
|
2962
|
+
case "citext":
|
|
2963
|
+
return `CAST(${placeholder} AS citext)`;
|
|
2964
|
+
case "text":
|
|
2965
|
+
return `CAST(${placeholder} AS text)`;
|
|
2966
|
+
default:
|
|
2967
|
+
return placeholder;
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
return dialect.name === "mysql" ? `ROW(${cells.join(", ")}, ${ordinal})` : `(${cells.join(", ")}, ${ordinal})`;
|
|
2971
|
+
});
|
|
2972
|
+
const on = join.columns.map((column, index) => `${quoteCol(dialect, column)} = ${alias}.${memberNames[index]}`).join(" AND ");
|
|
2973
|
+
return `INNER JOIN (VALUES ${rows.join(", ")}) AS ${alias} (${[...memberNames, dialect.quoteIdent({ name: join.ordinalAlias })].join(", ")}) ON ${on}`;
|
|
2974
|
+
}
|
|
2357
2975
|
function renderSelect(params) {
|
|
2358
2976
|
const { dialect, statement, collector } = params;
|
|
2359
2977
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
@@ -2371,6 +2989,11 @@ function renderSelect(params) {
|
|
|
2371
2989
|
columnList.push(`${renderAggRef(dialect, selection.agg)} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
2372
2990
|
}
|
|
2373
2991
|
}
|
|
2992
|
+
if (statement.countSubqueries !== undefined) {
|
|
2993
|
+
for (const subquery of statement.countSubqueries) {
|
|
2994
|
+
columnList.push(renderCountSubquery({ dialect, subquery, collector }));
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2374
2997
|
if (statement.rowNumber !== undefined) {
|
|
2375
2998
|
const rowNumber = statement.rowNumber;
|
|
2376
2999
|
if (rowNumber.partitionBy.length === 0) {
|
|
@@ -2383,13 +3006,13 @@ function renderSelect(params) {
|
|
|
2383
3006
|
let distinctPrefix = "";
|
|
2384
3007
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
2385
3008
|
if (!dialect.capabilities.distinctOn) {
|
|
2386
|
-
throw
|
|
3009
|
+
throw unsupportedCapability15({
|
|
2387
3010
|
dialect: dialect.name,
|
|
2388
3011
|
feature: "SELECT DISTINCT ON",
|
|
2389
3012
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
2390
3013
|
});
|
|
2391
3014
|
}
|
|
2392
|
-
distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, column)).join(", ")}) `;
|
|
3015
|
+
distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, typedOrderRef({ statement, column }))).join(", ")}) `;
|
|
2393
3016
|
}
|
|
2394
3017
|
const columns = columnList.length === 0 ? "*" : columnList.join(", ");
|
|
2395
3018
|
let from;
|
|
@@ -2412,9 +3035,12 @@ function renderSelect(params) {
|
|
|
2412
3035
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
2413
3036
|
}
|
|
2414
3037
|
}
|
|
3038
|
+
if (statement.keyTable !== undefined) {
|
|
3039
|
+
text += ` ${renderKeyTableJoin({ dialect, join: statement.keyTable, collector })}`;
|
|
3040
|
+
}
|
|
2415
3041
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
2416
3042
|
if (!dialect.capabilities.lateralJoin) {
|
|
2417
|
-
throw
|
|
3043
|
+
throw unsupportedCapability15({
|
|
2418
3044
|
dialect: dialect.name,
|
|
2419
3045
|
feature: "LATERAL JOIN relation loading",
|
|
2420
3046
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -2434,7 +3060,7 @@ function renderSelect(params) {
|
|
|
2434
3060
|
text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
|
|
2435
3061
|
}
|
|
2436
3062
|
if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
|
|
2437
|
-
const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term, collector }));
|
|
3063
|
+
const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term: "column" in term ? { ...term, column: typedOrderRef({ statement, column: term.column }) } : term, collector }));
|
|
2438
3064
|
text += ` ORDER BY ${terms.join(", ")}`;
|
|
2439
3065
|
}
|
|
2440
3066
|
if (statement.limit !== undefined) {
|
|
@@ -2476,7 +3102,7 @@ function renderInsert(params) {
|
|
|
2476
3102
|
const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
2477
3103
|
const rowsSql = statement.rows.map((row) => `(${row.map((value) => {
|
|
2478
3104
|
if (isJsonParamCell(value))
|
|
2479
|
-
return `${collector.add(value.text)}${dialect
|
|
3105
|
+
return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
2480
3106
|
if (isDbNowMarker(value)) {
|
|
2481
3107
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
2482
3108
|
}
|
|
@@ -2499,7 +3125,7 @@ function renderInsert(params) {
|
|
|
2499
3125
|
let target = "";
|
|
2500
3126
|
if (onConflict.constraint !== undefined) {
|
|
2501
3127
|
if (dialect.name !== "postgres") {
|
|
2502
|
-
throw
|
|
3128
|
+
throw unsupportedCapability15({
|
|
2503
3129
|
dialect: dialect.name,
|
|
2504
3130
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
2505
3131
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -2514,7 +3140,7 @@ function renderInsert(params) {
|
|
|
2514
3140
|
}
|
|
2515
3141
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
2516
3142
|
} else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
2517
|
-
throw
|
|
3143
|
+
throw unsupportedCapability15({
|
|
2518
3144
|
dialect: dialect.name,
|
|
2519
3145
|
feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
|
|
2520
3146
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -2532,7 +3158,7 @@ function renderInsert(params) {
|
|
|
2532
3158
|
throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
2533
3159
|
}
|
|
2534
3160
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
2535
|
-
throw
|
|
3161
|
+
throw unsupportedCapability15({
|
|
2536
3162
|
dialect: dialect.name,
|
|
2537
3163
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
2538
3164
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
@@ -2548,14 +3174,14 @@ function renderInsert(params) {
|
|
|
2548
3174
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
2549
3175
|
} else {
|
|
2550
3176
|
if (onConflict.constraint !== undefined) {
|
|
2551
|
-
throw
|
|
3177
|
+
throw unsupportedCapability15({
|
|
2552
3178
|
dialect: dialect.name,
|
|
2553
3179
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
2554
3180
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
2555
3181
|
});
|
|
2556
3182
|
}
|
|
2557
3183
|
if (onConflict.conflictWhere !== undefined) {
|
|
2558
|
-
throw
|
|
3184
|
+
throw unsupportedCapability15({
|
|
2559
3185
|
dialect: dialect.name,
|
|
2560
3186
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
2561
3187
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -2608,7 +3234,7 @@ function renderAssignments(params) {
|
|
|
2608
3234
|
return `${column} = ${renderIncomingRowColumn({ dialect, column: value.column })}`;
|
|
2609
3235
|
}
|
|
2610
3236
|
if (isJsonParamCell(value)) {
|
|
2611
|
-
return `${column} = ${collector.add(value.text)}${dialect
|
|
3237
|
+
return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
2612
3238
|
}
|
|
2613
3239
|
if (isSetOperation(value)) {
|
|
2614
3240
|
const operand = collector.add(value.value);
|
|
@@ -2637,85 +3263,174 @@ function renderDelete(params) {
|
|
|
2637
3263
|
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
2638
3264
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
2639
3265
|
}
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
function
|
|
2643
|
-
const
|
|
2644
|
-
if (
|
|
2645
|
-
throw
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
}
|
|
2659
|
-
continue;
|
|
2660
|
-
}
|
|
2661
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
2662
|
-
quote = char;
|
|
2663
|
-
continue;
|
|
2664
|
-
}
|
|
2665
|
-
if (char === "[") {
|
|
2666
|
-
quote = "]";
|
|
2667
|
-
continue;
|
|
3266
|
+
var CHAIN_ROWS_ALIAS = "__vibe_rows";
|
|
3267
|
+
var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
3268
|
+
function renderChain(params) {
|
|
3269
|
+
const { dialect, statement, collector } = params;
|
|
3270
|
+
if (dialect.statementChain === null) {
|
|
3271
|
+
throw unsupportedCapability15({
|
|
3272
|
+
dialect: dialect.name,
|
|
3273
|
+
feature: "a statement chain (data-modifying WITH steps)",
|
|
3274
|
+
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
if (statement.steps.length === 0)
|
|
3278
|
+
throw internalError2({ message: "a statement chain needs at least one step" });
|
|
3279
|
+
const defined = new Set;
|
|
3280
|
+
const steps = [];
|
|
3281
|
+
for (const step of statement.steps) {
|
|
3282
|
+
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
3283
|
+
throw internalError2({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
2668
3284
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
3285
|
+
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
3286
|
+
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
3287
|
+
defined.add(step.name);
|
|
3288
|
+
}
|
|
3289
|
+
const result = statement.result;
|
|
3290
|
+
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
3291
|
+
throw internalError2({ message: "a chain result needs at least one source step and one column" });
|
|
3292
|
+
}
|
|
3293
|
+
for (const name of result.rowsFrom) {
|
|
3294
|
+
if (!defined.has(name))
|
|
3295
|
+
throw internalError2({ message: `chain result reads undefined step "${name}"` });
|
|
3296
|
+
}
|
|
3297
|
+
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3298
|
+
const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
|
|
3299
|
+
let select = union;
|
|
3300
|
+
if (result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
3301
|
+
const extra = [];
|
|
3302
|
+
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
3303
|
+
if (alias !== undefined && result.columns.includes(alias)) {
|
|
3304
|
+
throw internalError2({ message: `chain result alias "${alias}" collides with a result column` });
|
|
3305
|
+
}
|
|
2676
3306
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
index = end + 1;
|
|
2682
|
-
continue;
|
|
3307
|
+
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
3308
|
+
const projected = result.columns.map((name) => `${rows}.${dialect.quoteIdent({ name })}`);
|
|
3309
|
+
if (result.verdict !== undefined) {
|
|
3310
|
+
extra.push(`${renderExpr({ dialect, expr: result.verdict.expr, collector })} AS ${dialect.quoteIdent({ name: result.verdict.alias })}`);
|
|
2683
3311
|
}
|
|
2684
|
-
if (
|
|
2685
|
-
|
|
2686
|
-
start = index + 1;
|
|
2687
|
-
depth++;
|
|
2688
|
-
} else if (char === ")") {
|
|
2689
|
-
depth--;
|
|
2690
|
-
if (depth === 0 && start >= 0) {
|
|
2691
|
-
parts.push(sql.slice(start, index).trim());
|
|
2692
|
-
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
2693
|
-
}
|
|
2694
|
-
} else if (char === "," && depth === 1) {
|
|
2695
|
-
parts.push(sql.slice(start, index).trim());
|
|
2696
|
-
start = index + 1;
|
|
3312
|
+
if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
3313
|
+
extra.push(renderTallyColumn({ dialect, result, defined, collector }));
|
|
2697
3314
|
}
|
|
3315
|
+
select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
|
|
2698
3316
|
}
|
|
2699
|
-
|
|
3317
|
+
return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
|
|
2700
3318
|
}
|
|
2701
|
-
function
|
|
2702
|
-
|
|
3319
|
+
function renderChainStep(params) {
|
|
3320
|
+
const { dialect, statement, collector } = params;
|
|
3321
|
+
switch (statement.kind) {
|
|
3322
|
+
case "select":
|
|
3323
|
+
return renderSelect({ dialect, statement, collector }).text;
|
|
3324
|
+
case "insert":
|
|
3325
|
+
return renderInsert({ dialect, statement, collector }).text;
|
|
3326
|
+
case "insertSelect":
|
|
3327
|
+
return renderInsertSelect({ dialect, statement, collector, earlier: params.earlier });
|
|
3328
|
+
case "update":
|
|
3329
|
+
return renderUpdate({ dialect, statement, collector }).text;
|
|
3330
|
+
case "delete":
|
|
3331
|
+
return renderDelete({ dialect, statement, collector }).text;
|
|
3332
|
+
}
|
|
2703
3333
|
}
|
|
2704
|
-
function
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
2713
|
-
columns.push({ name, sql: part });
|
|
3334
|
+
function renderSelectCell(params) {
|
|
3335
|
+
const { dialect, value, collector } = params;
|
|
3336
|
+
if (isJsonParamCell(value))
|
|
3337
|
+
return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
3338
|
+
if (isDbNowMarker(value))
|
|
3339
|
+
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
3340
|
+
if (isSqlDefaultCell(value)) {
|
|
3341
|
+
throw internalError2({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
2714
3342
|
}
|
|
2715
|
-
return
|
|
3343
|
+
return collector.add(value);
|
|
3344
|
+
}
|
|
3345
|
+
function renderRowSource(params) {
|
|
3346
|
+
const { dialect, source, collector } = params;
|
|
3347
|
+
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
3348
|
+
throw internalError2({ message: "a row source needs at least one column and one row" });
|
|
3349
|
+
}
|
|
3350
|
+
const typedBy = renderTable(dialect, source.typedBy);
|
|
3351
|
+
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
3352
|
+
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
3353
|
+
for (const row of source.rows) {
|
|
3354
|
+
if (row.length !== source.columns.length) {
|
|
3355
|
+
throw internalError2({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
3356
|
+
}
|
|
3357
|
+
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
3358
|
+
}
|
|
3359
|
+
const alias = dialect.quoteIdent({ name: source.alias });
|
|
3360
|
+
const columnList = columns.join(", ");
|
|
3361
|
+
return `(SELECT ${columnList} FROM (VALUES ${rows.join(", ")}) AS ${alias} (${columnList}) OFFSET 1) AS ${alias}`;
|
|
3362
|
+
}
|
|
3363
|
+
function renderTallyColumn(params) {
|
|
3364
|
+
const { dialect, result, defined, collector } = params;
|
|
3365
|
+
const tally = result.tally;
|
|
3366
|
+
if (tally === undefined)
|
|
3367
|
+
throw internalError2({ message: "a chain veto needs a tally column to guard" });
|
|
3368
|
+
if (tally.steps.length === 0)
|
|
3369
|
+
throw internalError2({ message: "a chain tally needs at least one step" });
|
|
3370
|
+
const step = (name) => {
|
|
3371
|
+
if (!defined.has(name))
|
|
3372
|
+
throw internalError2({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
3373
|
+
return dialect.quoteIdent({ name });
|
|
3374
|
+
};
|
|
3375
|
+
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
3376
|
+
const sum = tally.steps.map(count).join(" + ");
|
|
3377
|
+
const vetoes = result.veto ?? [];
|
|
3378
|
+
if (vetoes.length === 0)
|
|
3379
|
+
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
3380
|
+
const arms = vetoes.map((veto) => {
|
|
3381
|
+
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
3382
|
+
throw internalError2({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
3383
|
+
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
3384
|
+
throw internalError2({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
3385
|
+
const minRows = veto.minRows ?? 1;
|
|
3386
|
+
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
3387
|
+
throw internalError2({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
3388
|
+
const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
|
|
3389
|
+
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
3390
|
+
});
|
|
3391
|
+
return `CASE ${arms.join(" ")} ELSE ${sum} END AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
3392
|
+
}
|
|
3393
|
+
function renderInsertSelect(params) {
|
|
3394
|
+
const { dialect, statement, collector } = params;
|
|
3395
|
+
if (statement.columns.length === 0)
|
|
3396
|
+
throw internalError2({ message: "insert requires at least one column" });
|
|
3397
|
+
if (statement.cells.length !== statement.columns.length) {
|
|
3398
|
+
throw internalError2({
|
|
3399
|
+
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
3400
|
+
});
|
|
3401
|
+
}
|
|
3402
|
+
const from = statement.from ?? [];
|
|
3403
|
+
for (const name of from) {
|
|
3404
|
+
if (!params.earlier.has(name))
|
|
3405
|
+
throw internalError2({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
3406
|
+
}
|
|
3407
|
+
const source = statement.source;
|
|
3408
|
+
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
3409
|
+
throw internalError2({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
3410
|
+
}
|
|
3411
|
+
const cells = statement.cells.map((value) => {
|
|
3412
|
+
if (isStepColumnRef(value)) {
|
|
3413
|
+
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
3414
|
+
throw internalError2({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
3415
|
+
}
|
|
3416
|
+
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
3417
|
+
}
|
|
3418
|
+
return renderSelectCell({ dialect, value, collector });
|
|
3419
|
+
});
|
|
3420
|
+
const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3421
|
+
let text = `INSERT INTO ${renderTable(dialect, statement.table)} (${columnList}) SELECT ${cells.join(", ")}`;
|
|
3422
|
+
const relations = from.map((name) => dialect.quoteIdent({ name }));
|
|
3423
|
+
if (source !== undefined)
|
|
3424
|
+
relations.push(renderRowSource({ dialect, source, collector }));
|
|
3425
|
+
if (relations.length > 0)
|
|
3426
|
+
text += ` FROM ${relations.join(" CROSS JOIN ")}`;
|
|
3427
|
+
if (statement.where !== undefined) {
|
|
3428
|
+
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
3429
|
+
}
|
|
3430
|
+
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
2716
3431
|
}
|
|
2717
3432
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
2718
|
-
import { unsupportedCapability as
|
|
3433
|
+
import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2719
3434
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
2720
3435
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
2721
3436
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -2742,13 +3457,13 @@ function parsePostgresTypeName(params) {
|
|
|
2742
3457
|
}
|
|
2743
3458
|
function renderPostgresTypeName(params) {
|
|
2744
3459
|
if (params.dialect.name !== "postgres")
|
|
2745
|
-
throw
|
|
3460
|
+
throw unsupportedCapability16({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
2746
3461
|
const { dialect, type } = params;
|
|
2747
3462
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
2748
3463
|
}
|
|
2749
3464
|
function renderPostgresTypeIdentity(params) {
|
|
2750
3465
|
if (params.dialect.name !== "postgres")
|
|
2751
|
-
throw
|
|
3466
|
+
throw unsupportedCapability16({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
2752
3467
|
const collector = new ParamCollector(params.dialect);
|
|
2753
3468
|
const { source } = params;
|
|
2754
3469
|
let text;
|
|
@@ -2761,11 +3476,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
2761
3476
|
return { text, values: collector.values, returning: "none" };
|
|
2762
3477
|
}
|
|
2763
3478
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
2764
|
-
import { unsupportedCapability as
|
|
3479
|
+
import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
2765
3480
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
2766
3481
|
const { dialect, table, column } = params;
|
|
2767
3482
|
if (dialect.name !== "postgres")
|
|
2768
|
-
throw
|
|
3483
|
+
throw unsupportedCapability17({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
2769
3484
|
const collector = new ParamCollector(dialect);
|
|
2770
3485
|
const namespace = collector.add(table.namespace);
|
|
2771
3486
|
const name = collector.add(table.name);
|
|
@@ -2789,28 +3504,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
2789
3504
|
};
|
|
2790
3505
|
}
|
|
2791
3506
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
2792
|
-
import { unsupportedCapability as
|
|
3507
|
+
import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
2793
3508
|
function renderConstraintOwnedIndexDrop(params) {
|
|
2794
3509
|
if (params.dialect.name !== "postgres")
|
|
2795
|
-
throw
|
|
3510
|
+
throw unsupportedCapability18({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
2796
3511
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
2797
3512
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
2798
3513
|
}
|
|
2799
3514
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
2800
|
-
import { unsupportedCapability as
|
|
3515
|
+
import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
2801
3516
|
function renderMysqlCheckProbe(params) {
|
|
2802
3517
|
if (params.dialect.name !== "mysql")
|
|
2803
|
-
throw
|
|
3518
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
2804
3519
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2805
3520
|
const probe = q(params.probe);
|
|
2806
3521
|
switch (params.operation) {
|
|
2807
3522
|
case "clone":
|
|
2808
3523
|
if (params.table === undefined)
|
|
2809
|
-
throw
|
|
3524
|
+
throw unsupportedCapability19({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
2810
3525
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
2811
3526
|
case "add": {
|
|
2812
3527
|
if (params.expression === undefined || params.definition === undefined)
|
|
2813
|
-
throw
|
|
3528
|
+
throw unsupportedCapability19({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
2814
3529
|
const parts = tableDefinitionParts({ sql: params.definition });
|
|
2815
3530
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
2816
3531
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -2856,31 +3571,31 @@ function sqliteTableConstraints(params) {
|
|
|
2856
3571
|
return result;
|
|
2857
3572
|
}
|
|
2858
3573
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
2859
|
-
import { unsupportedCapability as
|
|
3574
|
+
import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
2860
3575
|
function renderMigrationVisibility(params) {
|
|
2861
3576
|
if (params.dialect.name !== "mysql")
|
|
2862
|
-
throw
|
|
3577
|
+
throw unsupportedCapability20({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
2863
3578
|
const collector = new ParamCollector(params.dialect);
|
|
2864
3579
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
2865
3580
|
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" };
|
|
2866
3581
|
}
|
|
2867
3582
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
2868
|
-
import { unsupportedCapability as
|
|
3583
|
+
import { unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
2869
3584
|
function renderSqliteColumnInventory(params) {
|
|
2870
3585
|
if (params.dialect.name !== "sqlite")
|
|
2871
|
-
throw
|
|
3586
|
+
throw unsupportedCapability21({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
2872
3587
|
const collector = new ParamCollector(params.dialect);
|
|
2873
3588
|
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" };
|
|
2874
3589
|
}
|
|
2875
3590
|
function renderSqliteForeignKeyInventory(params) {
|
|
2876
3591
|
if (params.dialect.name !== "sqlite")
|
|
2877
|
-
throw
|
|
3592
|
+
throw unsupportedCapability21({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
2878
3593
|
const collector = new ParamCollector(params.dialect);
|
|
2879
3594
|
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" };
|
|
2880
3595
|
}
|
|
2881
3596
|
function renderSqliteRebuildObjects(params) {
|
|
2882
3597
|
if (params.dialect.name !== "sqlite")
|
|
2883
|
-
throw
|
|
3598
|
+
throw unsupportedCapability21({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
2884
3599
|
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" };
|
|
2885
3600
|
}
|
|
2886
3601
|
function renderMigrationInventory(params) {
|
|
@@ -2900,6 +3615,8 @@ UNION ALL SELECT 'trigger', t.tgname::text, r.relname::text, NULL, NULL, NULL FR
|
|
|
2900
3615
|
UNION ALL SELECT 'column', a.attname::text, r.relname::text, NULL, NULL, NULL FROM relations r JOIN pg_catalog.pg_attribute a ON a.attrelid = r.oid WHERE a.attnum > 0 AND NOT a.attisdropped AND r.relkind IN ('r','p')
|
|
2901
3616
|
UNION ALL SELECT CASE WHEN a.attgenerated <> '' THEN 'generatedColumn' ELSE 'columnDefault' END, a.attname::text, r.relname::text, NULL, NULL, NULL FROM relations r JOIN pg_catalog.pg_attribute a ON a.attrelid = r.oid JOIN pg_catalog.pg_attrdef d ON d.adrelid = r.oid AND d.adnum = a.attnum WHERE a.attnum > 0 AND NOT a.attisdropped
|
|
2902
3617
|
UNION ALL SELECT 'function', p.proname::text, NULL, NULL, COALESCE((SELECT json_agg(json_build_object('namespace', tn.nspname, 'name', t.typname) ORDER BY a.ord)::text FROM unnest(p.proargtypes) WITH ORDINALITY a(oid, ord) JOIN pg_catalog.pg_type t ON t.oid = a.oid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace), '[]'), NULL FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = ${namespace} AND p.prokind = 'f' AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e')
|
|
3618
|
+
UNION ALL SELECT 'enum', t.typname::text, NULL, NULL, NULL, NULL FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND t.typtype = 'e' 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')
|
|
3619
|
+
${params.namespace === "public" ? "" : `UNION ALL SELECT 'namespace', n.nspname::text, NULL, NULL, NULL, NULL FROM pg_catalog.pg_namespace n WHERE n.nspname = ${namespace}`}
|
|
2903
3620
|
ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
2904
3621
|
}
|
|
2905
3622
|
if (params.dialect.name === "mysql") {
|
|
@@ -2917,12 +3634,17 @@ ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
|
2917
3634
|
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" };
|
|
2918
3635
|
}
|
|
2919
3636
|
// packages/sql/src/dialects/migration-objects.ts
|
|
2920
|
-
import { internalError as internalError3, unsupportedCapability as
|
|
3637
|
+
import { internalError as internalError3, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
2921
3638
|
function renderMigrationObjectQuery(params) {
|
|
2922
3639
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
2923
3640
|
}
|
|
2924
3641
|
function objectQuery(params) {
|
|
2925
3642
|
const { dialect, identity: object, collector } = params;
|
|
3643
|
+
if (object.kind === "namespace") {
|
|
3644
|
+
if (dialect.name !== "postgres")
|
|
3645
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3646
|
+
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
|
+
}
|
|
2926
3648
|
const physical = "table" in object ? object.table : object.name;
|
|
2927
3649
|
const namespace = collector.add(physical.namespace);
|
|
2928
3650
|
const name = collector.add(physical.name);
|
|
@@ -2984,7 +3706,7 @@ function objectQuery(params) {
|
|
|
2984
3706
|
break;
|
|
2985
3707
|
case "constraint":
|
|
2986
3708
|
if (object.constraintType === "exclusion")
|
|
2987
|
-
throw
|
|
3709
|
+
throw unsupportedCapability22({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
2988
3710
|
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)}`;
|
|
2989
3711
|
break;
|
|
2990
3712
|
case "index":
|
|
@@ -2994,15 +3716,15 @@ function objectQuery(params) {
|
|
|
2994
3716
|
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}`;
|
|
2995
3717
|
break;
|
|
2996
3718
|
default:
|
|
2997
|
-
throw
|
|
3719
|
+
throw unsupportedCapability22({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
2998
3720
|
}
|
|
2999
3721
|
} else {
|
|
3000
3722
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
3001
|
-
throw
|
|
3723
|
+
throw unsupportedCapability22({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
3002
3724
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
3003
3725
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
3004
3726
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
3005
|
-
throw
|
|
3727
|
+
throw unsupportedCapability22({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
3006
3728
|
const sqliteParams = new ParamCollector(dialect);
|
|
3007
3729
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
3008
3730
|
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)}`;
|
|
@@ -3032,10 +3754,14 @@ function renderMigrationDependencies(params) {
|
|
|
3032
3754
|
}
|
|
3033
3755
|
function dependencyQuery(params) {
|
|
3034
3756
|
if (params.dialect.name !== "postgres")
|
|
3035
|
-
throw
|
|
3757
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
3036
3758
|
const object = params.identity;
|
|
3037
|
-
const physical = "table" in object ? object.table : object.name;
|
|
3038
3759
|
const collector = params.collector;
|
|
3760
|
+
if (object.kind === "namespace") {
|
|
3761
|
+
const namespace = collector.add(object.name);
|
|
3762
|
+
return { text: `SELECT DISTINCT json_build_array(d.deptype, a.type, a.object_names, a.object_args)::text AS address FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_depend d ON d.classid = 'pg_namespace'::regclass AND d.objid = n.oid AND d.objsubid = 0 CROSS JOIN LATERAL pg_catalog.pg_identify_object_as_address(d.refclassid, d.refobjid, d.refobjsubid) a WHERE n.nspname = ${namespace} ORDER BY address`, values: collector.values, returning: "none" };
|
|
3763
|
+
}
|
|
3764
|
+
const physical = "table" in object ? object.table : object.name;
|
|
3039
3765
|
const namespace = collector.add(physical.namespace);
|
|
3040
3766
|
const name = collector.add(physical.name);
|
|
3041
3767
|
const relation = `SELECT c.oid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${namespace} AND c.relname = ${name}`;
|
|
@@ -3085,7 +3811,7 @@ function inspectionReferences(params) {
|
|
|
3085
3811
|
function renderMigrationObjectBatch(params) {
|
|
3086
3812
|
const { dialect } = params;
|
|
3087
3813
|
if (dialect.name !== "postgres")
|
|
3088
|
-
throw
|
|
3814
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
3089
3815
|
const groups = new Map;
|
|
3090
3816
|
for (const [position, identity] of params.identities.entries()) {
|
|
3091
3817
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -3118,16 +3844,16 @@ function renderMigrationNamespace(params) {
|
|
|
3118
3844
|
}
|
|
3119
3845
|
function renderCheckProbe(params) {
|
|
3120
3846
|
if (params.dialect.name !== "postgres")
|
|
3121
|
-
throw
|
|
3847
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
3122
3848
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
3123
3849
|
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" };
|
|
3124
3850
|
}
|
|
3125
3851
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
3126
|
-
import { VibeError as
|
|
3852
|
+
import { VibeError as VibeError11, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
3127
3853
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
3128
3854
|
function requirePostgres2(params) {
|
|
3129
3855
|
if (params.dialect.name !== "postgres") {
|
|
3130
|
-
throw
|
|
3856
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: params.feature });
|
|
3131
3857
|
}
|
|
3132
3858
|
}
|
|
3133
3859
|
function renderMigrationSessionRead(params) {
|
|
@@ -3142,7 +3868,7 @@ function renderMigrationSessionSet(params) {
|
|
|
3142
3868
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
3143
3869
|
const { milliseconds, setting } = params;
|
|
3144
3870
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
3145
|
-
throw new
|
|
3871
|
+
throw new VibeError11({
|
|
3146
3872
|
code: "VIBE_MIGRATION",
|
|
3147
3873
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
3148
3874
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -3232,6 +3958,8 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
3232
3958
|
export {
|
|
3233
3959
|
ADVISORY_KEY_BOUNDS,
|
|
3234
3960
|
ADVISORY_LOCK_RESULT_COLUMN,
|
|
3961
|
+
CHAIN_VETO_PREFIX,
|
|
3962
|
+
CHAIN_VETO_TOKEN_PATTERN,
|
|
3235
3963
|
DIALECT_CAPABILITIES,
|
|
3236
3964
|
DIALECT_CODECS,
|
|
3237
3965
|
JSON_TRANSPORT,
|
|
@@ -3253,6 +3981,7 @@ export {
|
|
|
3253
3981
|
SQLITE_SESSION_SQL,
|
|
3254
3982
|
SQL_DEFAULT_CELL,
|
|
3255
3983
|
SQL_DIALECTS,
|
|
3984
|
+
chainVetoToken,
|
|
3256
3985
|
compileExclusion,
|
|
3257
3986
|
compileSql,
|
|
3258
3987
|
containsPattern,
|
|
@@ -3265,21 +3994,29 @@ export {
|
|
|
3265
3994
|
escapeGlobPattern,
|
|
3266
3995
|
escapeLikePattern,
|
|
3267
3996
|
excludedRef,
|
|
3997
|
+
float32Codec,
|
|
3268
3998
|
getSqlDialect,
|
|
3269
3999
|
isDbNowMarker,
|
|
3270
4000
|
isExcludedRef,
|
|
3271
4001
|
isFixedDiagnostic,
|
|
4002
|
+
isJsonListParameter,
|
|
3272
4003
|
isJsonParamCell,
|
|
3273
4004
|
isListSetOperation,
|
|
4005
|
+
isNativeEnumArrayParameter,
|
|
3274
4006
|
isSetOperation,
|
|
3275
4007
|
isSqlDefaultCell,
|
|
3276
4008
|
isSqlFragment,
|
|
4009
|
+
isStepColumnRef,
|
|
4010
|
+
jsonListParameter,
|
|
3277
4011
|
jsonParamCell,
|
|
3278
4012
|
mapConflictColumns,
|
|
3279
4013
|
mysqlDialect,
|
|
4014
|
+
nativeEnumArrayParameter,
|
|
3280
4015
|
parseDateTimeText,
|
|
3281
4016
|
parsePgArrayLiteral,
|
|
4017
|
+
parsePgArrayLiteralNested,
|
|
3282
4018
|
parsePostgresTypeName,
|
|
4019
|
+
postgresArrayElementFromText,
|
|
3283
4020
|
postgresDialect,
|
|
3284
4021
|
projectionOutputName,
|
|
3285
4022
|
renderAdvisoryLock,
|
|
@@ -3293,7 +4030,10 @@ export {
|
|
|
3293
4030
|
renderExclusionProbe,
|
|
3294
4031
|
renderExclusionSetup,
|
|
3295
4032
|
renderExpr,
|
|
4033
|
+
renderExternalDependents,
|
|
4034
|
+
renderExternalOwnershipCatalog,
|
|
3296
4035
|
renderFragment,
|
|
4036
|
+
renderIndexColumns,
|
|
3297
4037
|
renderIndexStateQuery,
|
|
3298
4038
|
renderMigrationDependencies,
|
|
3299
4039
|
renderMigrationHistoryQuery,
|
|
@@ -3312,10 +4052,13 @@ export {
|
|
|
3312
4052
|
renderMysqlObjectMembers,
|
|
3313
4053
|
renderPostgresDefaultFunctionIdentity,
|
|
3314
4054
|
renderPostgresExplain,
|
|
4055
|
+
renderPostgresIndexKeys,
|
|
4056
|
+
renderPostgresPhysicalCatalog,
|
|
3315
4057
|
renderPostgresTypeIdentity,
|
|
3316
4058
|
renderPostgresTypeName,
|
|
3317
4059
|
renderPostgresWorkload,
|
|
3318
4060
|
renderPredicate,
|
|
4061
|
+
renderProjection,
|
|
3319
4062
|
renderRelationStateQuery,
|
|
3320
4063
|
renderReplayCoverageGaps,
|
|
3321
4064
|
renderRlsColumnType,
|
|
@@ -3336,16 +4079,23 @@ export {
|
|
|
3336
4079
|
renderSqlFragment,
|
|
3337
4080
|
renderSqliteColumnInventory,
|
|
3338
4081
|
renderSqliteForeignKeyInventory,
|
|
4082
|
+
renderSqliteFtsUpdateTrigger,
|
|
3339
4083
|
renderSqliteRebuildObjects,
|
|
3340
4084
|
renderStatement,
|
|
4085
|
+
returningColumnName,
|
|
4086
|
+
shortestFloat32,
|
|
3341
4087
|
sql,
|
|
3342
4088
|
sqlUsesIncoming,
|
|
3343
4089
|
sqliteColumnClauses,
|
|
3344
4090
|
sqliteDialect,
|
|
4091
|
+
sqliteFtsUpdateUpgrade,
|
|
3345
4092
|
sqliteTableConstraints,
|
|
3346
4093
|
startsWithPattern,
|
|
4094
|
+
stepColumnRef,
|
|
3347
4095
|
strictMatchPattern,
|
|
4096
|
+
tableDefinitionParts,
|
|
4097
|
+
timeOfDayCodec,
|
|
3348
4098
|
toPgArrayLiteral
|
|
3349
4099
|
};
|
|
3350
4100
|
|
|
3351
|
-
//# debugId=
|
|
4101
|
+
//# debugId=67799F9417158D9264756E2164756E21
|