@vibeorm/sql 2.5.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ast.d.ts +339 -12
- package/dist/codecs.d.ts +57 -0
- package/dist/dialect.d.ts +54 -0
- package/dist/dialects/postgres-index-keys.d.ts +1 -0
- package/dist/dialects/postgres-physical-catalog.d.ts +7 -1
- package/dist/dialects/sqlite-fts.d.ts +28 -0
- package/dist/index.d.ts +19 -7
- package/dist/index.js +2100 -983
- package/dist/index.js.map +26 -20
- package/dist/json-list-parameter.d.ts +37 -0
- package/dist/parameter-snapshot.d.ts +6 -0
- package/dist/params.d.ts +7 -8
- package/dist/pg-array.d.ts +13 -0
- package/dist/query/expressions.d.ts +70 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +1078 -0
- package/dist/query/index.js.map +16 -0
- package/dist/query/mutations.d.ts +15 -0
- package/dist/query/reusable.d.ts +17 -0
- package/dist/query/select.d.ts +11 -0
- package/dist/query/sources.d.ts +37 -0
- package/dist/query/state.d.ts +73 -0
- package/dist/query/types.d.ts +188 -0
- package/dist/query-plan.d.ts +97 -0
- package/dist/render.d.ts +11 -1
- package/dist/result-decoder.d.ts +41 -10
- package/dist/sql-bindings.d.ts +84 -0
- package/dist/sql-input.d.ts +98 -0
- package/package.json +9 -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/external-dependents.d.ts.map +0 -1
- package/dist/dialects/external-ownership.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-index-keys.d.ts.map +0 -1
- package/dist/dialects/postgres-migration.d.ts.map +0 -1
- package/dist/dialects/postgres-physical-catalog.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-columns.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/native-enum-parameter.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
|
@@ -7,6 +7,14 @@ function nativeEnumArrayParameter(params) {
|
|
|
7
7
|
function isNativeEnumArrayParameter(value) {
|
|
8
8
|
return typeof value === "object" && value !== null && NATIVE_ENUM_ARRAY_PARAMETER in value;
|
|
9
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
|
+
}
|
|
10
18
|
// packages/sql/src/dialects/external-ownership.ts
|
|
11
19
|
import { unsupportedCapability } from "@vibeorm/schema";
|
|
12
20
|
function renderExternalOwnershipCatalog(params) {
|
|
@@ -31,799 +39,1649 @@ function renderExternalOwnershipCatalog(params) {
|
|
|
31
39
|
return { text, values: [], returning: "none" };
|
|
32
40
|
}
|
|
33
41
|
// packages/sql/src/dialects/external-dependents.ts
|
|
34
|
-
import { unsupportedCapability as
|
|
42
|
+
import { unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
35
43
|
|
|
36
44
|
// packages/sql/src/params.ts
|
|
37
|
-
import { unsupportedCapability as
|
|
38
|
-
class ParamCollector {
|
|
39
|
-
dialect;
|
|
40
|
-
values;
|
|
41
|
-
constructor(dialect) {
|
|
42
|
-
this.dialect = dialect;
|
|
43
|
-
this.values = [];
|
|
44
|
-
}
|
|
45
|
-
add(value) {
|
|
46
|
-
if (isNativeEnumArrayParameter(value)) {
|
|
47
|
-
if (!this.dialect.capabilities.scalarArrays)
|
|
48
|
-
throw unsupportedCapability2({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
49
|
-
this.values.push(value.value);
|
|
50
|
-
return `${this.dialect.placeholder({ index: this.values.length })}::${this.dialect.quoteIdent({ name: value.type.namespace })}.${this.dialect.quoteIdent({ name: value.type.name })}[]`;
|
|
51
|
-
}
|
|
52
|
-
this.values.push(value);
|
|
53
|
-
return this.dialect.placeholder({ index: this.values.length });
|
|
54
|
-
}
|
|
55
|
-
addMany(values) {
|
|
56
|
-
return values.map((value) => this.add(value));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
45
|
+
import { unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
59
46
|
|
|
60
|
-
// packages/sql/src/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
FROM referenced
|
|
84
|
-
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'
|
|
85
|
-
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) dependent
|
|
86
|
-
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.refclassid, d.refobjid, d.refobjsubid) ref
|
|
87
|
-
LEFT JOIN pg_catalog.pg_class self ON d.classid = 'pg_class'::regclass AND self.oid = d.objid
|
|
88
|
-
LEFT JOIN pg_catalog.pg_index ix ON ix.indexrelid = self.oid
|
|
89
|
-
LEFT JOIN pg_catalog.pg_attrdef ad ON d.classid = 'pg_attrdef'::regclass AND ad.oid = d.objid
|
|
90
|
-
LEFT JOIN pg_catalog.pg_rewrite rw ON d.classid = 'pg_rewrite'::regclass AND rw.oid = d.objid
|
|
91
|
-
LEFT JOIN pg_catalog.pg_trigger tg ON d.classid = 'pg_trigger'::regclass AND tg.oid = d.objid
|
|
92
|
-
LEFT JOIN pg_catalog.pg_constraint con ON d.classid = 'pg_constraint'::regclass AND con.oid = d.objid
|
|
93
|
-
LEFT JOIN pg_catalog.pg_policy pol ON d.classid = 'pg_policy'::regclass AND pol.oid = d.objid
|
|
94
|
-
LEFT JOIN pg_catalog.pg_type ty ON d.classid = 'pg_type'::regclass AND ty.oid = d.objid
|
|
95
|
-
LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = ty.typnamespace
|
|
96
|
-
LEFT JOIN pg_catalog.pg_proc p ON d.classid = 'pg_proc'::regclass AND p.oid = d.objid
|
|
97
|
-
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
98
|
-
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))
|
|
99
|
-
LEFT JOIN pg_catalog.pg_namespace rn ON rn.oid = rel.relnamespace
|
|
100
|
-
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
|
|
101
|
-
ORDER BY 1, 2, 3, 4`;
|
|
102
|
-
return { text, values: collector.values, returning: "none" };
|
|
47
|
+
// packages/sql/src/sql-bindings.ts
|
|
48
|
+
import { VibeError as VibeError5, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
49
|
+
|
|
50
|
+
// packages/sql/src/parameter-snapshot.ts
|
|
51
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
52
|
+
|
|
53
|
+
// packages/sql/src/sql-input.ts
|
|
54
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
55
|
+
|
|
56
|
+
// packages/sql/src/result-decoder.ts
|
|
57
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
58
|
+
|
|
59
|
+
// packages/sql/src/pg-array.ts
|
|
60
|
+
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
61
|
+
function toPgArrayLiteral(params) {
|
|
62
|
+
const { values } = params;
|
|
63
|
+
const parts = values.map((value) => {
|
|
64
|
+
if (value === null || value === undefined)
|
|
65
|
+
return "NULL";
|
|
66
|
+
const text = String(value);
|
|
67
|
+
return PG_ARRAY_QUOTE.test(text) ? `"${text.replace(/["\\]/g, (char) => `\\${char}`)}"` : text;
|
|
68
|
+
});
|
|
69
|
+
return `{${parts.join(",")}}`;
|
|
103
70
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
text =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
71
|
+
function parsePgArrayLiteral(params) {
|
|
72
|
+
const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
|
|
73
|
+
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
74
|
+
return null;
|
|
75
|
+
const inner = literal.slice(1, -1);
|
|
76
|
+
if (inner.length === 0)
|
|
77
|
+
return [];
|
|
78
|
+
const out = [];
|
|
79
|
+
let index = 0;
|
|
80
|
+
while (index < inner.length) {
|
|
81
|
+
if (inner[index] === '"') {
|
|
82
|
+
let text = "";
|
|
83
|
+
index++;
|
|
84
|
+
while (index < inner.length) {
|
|
85
|
+
const char = inner[index];
|
|
86
|
+
if (char === "\\") {
|
|
87
|
+
text += inner[index + 1] ?? "";
|
|
88
|
+
index += 2;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
index++;
|
|
92
|
+
if (char === '"')
|
|
93
|
+
break;
|
|
94
|
+
text += char;
|
|
95
|
+
}
|
|
96
|
+
out.push(text);
|
|
97
|
+
} else {
|
|
98
|
+
let text = "";
|
|
99
|
+
while (index < inner.length && inner[index] !== ",") {
|
|
100
|
+
text += inner[index];
|
|
101
|
+
index++;
|
|
102
|
+
}
|
|
103
|
+
out.push(text === "NULL" ? null : text);
|
|
104
|
+
}
|
|
105
|
+
if (inner[index] === ",")
|
|
106
|
+
index++;
|
|
139
107
|
}
|
|
140
|
-
return
|
|
108
|
+
return out;
|
|
141
109
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const {
|
|
146
|
-
|
|
147
|
-
throw unsupportedCapability5({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
148
|
-
const collector = new ParamCollector(dialect);
|
|
149
|
-
const scope = params.table === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : `n.nspname = ${collector.add(params.table.namespace)} AND c.relname = ${collector.add(params.table.name)}`;
|
|
150
|
-
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`;
|
|
151
|
-
return {
|
|
152
|
-
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`,
|
|
153
|
-
values: collector.values,
|
|
154
|
-
returning: "none"
|
|
155
|
-
};
|
|
110
|
+
function parsePgArrayLiteralNested(params) {
|
|
111
|
+
const text = params.literal.replace(/^(?:\[-?\d+:-?\d+\])+=/, "");
|
|
112
|
+
const cursor = { index: 0 };
|
|
113
|
+
const parsed = parseDimension({ text, cursor });
|
|
114
|
+
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
156
115
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
|
|
116
|
+
function parseDimension(params) {
|
|
117
|
+
const { text, cursor } = params;
|
|
118
|
+
if (text[cursor.index] !== "{")
|
|
119
|
+
return null;
|
|
120
|
+
cursor.index++;
|
|
121
|
+
const out = [];
|
|
122
|
+
if (text[cursor.index] === "}") {
|
|
123
|
+
cursor.index++;
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
while (cursor.index < text.length) {
|
|
127
|
+
if (text[cursor.index] === "{") {
|
|
128
|
+
const nested = parseDimension({ text, cursor });
|
|
129
|
+
if (nested === null)
|
|
130
|
+
return null;
|
|
131
|
+
out.push(nested);
|
|
132
|
+
} else if (text[cursor.index] === '"') {
|
|
133
|
+
let element = "";
|
|
134
|
+
cursor.index++;
|
|
135
|
+
while (cursor.index < text.length && text[cursor.index] !== '"') {
|
|
136
|
+
if (text[cursor.index] === "\\")
|
|
137
|
+
cursor.index++;
|
|
138
|
+
element += text[cursor.index] ?? "";
|
|
139
|
+
cursor.index++;
|
|
140
|
+
}
|
|
141
|
+
if (text[cursor.index] !== '"')
|
|
142
|
+
return null;
|
|
143
|
+
cursor.index++;
|
|
144
|
+
out.push(element);
|
|
145
|
+
} else {
|
|
146
|
+
let element = "";
|
|
147
|
+
while (cursor.index < text.length && text[cursor.index] !== "," && text[cursor.index] !== "}") {
|
|
148
|
+
element += text[cursor.index];
|
|
149
|
+
cursor.index++;
|
|
150
|
+
}
|
|
151
|
+
out.push(element === "NULL" ? null : element);
|
|
170
152
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
153
|
+
if (text[cursor.index] === ",") {
|
|
154
|
+
cursor.index++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (text[cursor.index] !== "}")
|
|
158
|
+
return null;
|
|
159
|
+
cursor.index++;
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
174
163
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
const collector = new ParamCollector(dialect);
|
|
183
|
-
const owned = operation.tables.map((table, index) => `SELECT ${collector.add(operation.namespace)}${index === 0 ? " AS namespace" : ""}, ${collector.add(table)}${index === 0 ? " AS table_name" : ""}`).join(" UNION ALL ");
|
|
184
|
-
const pg = dialect.name === "postgres";
|
|
185
|
-
const nameKey = ({ column }) => pg ? column : `CAST(IF(@@lower_case_table_names = 0, ${column}, LOWER(${column})) AS BINARY)`;
|
|
186
|
-
const isOwned = ({ namespace, table }) => `EXISTS (SELECT 1 FROM owned_tables o WHERE ${nameKey({ column: namespace })} = ${nameKey({ column: "o.namespace" })} AND ${nameKey({ column: table })} = ${nameKey({ column: "o.table_name" })})`;
|
|
187
|
-
const sourceNamespace = pg ? "sn.nspname" : "fk.CONSTRAINT_SCHEMA";
|
|
188
|
-
const sourceTable = pg ? "s.relname" : "fk.TABLE_NAME";
|
|
189
|
-
const targetNamespace = pg ? "tn.nspname" : "fk.UNIQUE_CONSTRAINT_SCHEMA";
|
|
190
|
-
const targetTable = pg ? "t.relname" : "fk.REFERENCED_TABLE_NAME";
|
|
191
|
-
const from = pg ? "pg_catalog.pg_constraint fk JOIN pg_catalog.pg_class s ON s.oid = fk.conrelid JOIN pg_catalog.pg_namespace sn ON sn.oid = s.relnamespace JOIN pg_catalog.pg_class t ON t.oid = fk.confrelid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.relnamespace WHERE fk.contype = 'f'" : "information_schema.REFERENTIAL_CONSTRAINTS fk";
|
|
164
|
+
|
|
165
|
+
// packages/sql/src/codecs.ts
|
|
166
|
+
import { VibeError, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
|
|
167
|
+
function enumMembershipCodec(params) {
|
|
168
|
+
const { model, field, enum: enumName } = params;
|
|
169
|
+
const members = new Set(params.members);
|
|
192
170
|
return {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
171
|
+
encode: (value) => {
|
|
172
|
+
if (typeof value === "string" && members.has(value))
|
|
173
|
+
return value;
|
|
174
|
+
throw new VibeError({
|
|
175
|
+
code: "VIBE_VALIDATION",
|
|
176
|
+
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
177
|
+
meta: { model, field, enum: enumName }
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
decode: (value) => {
|
|
181
|
+
if (typeof value === "string" && members.has(value))
|
|
182
|
+
return value;
|
|
183
|
+
throw invalidStoredData({ model, field, enum: enumName, value });
|
|
184
|
+
}
|
|
199
185
|
};
|
|
200
186
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
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")}
|
|
213
|
-
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" };
|
|
214
|
-
if (dialect.name === "mysql")
|
|
215
|
-
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'
|
|
216
|
-
UNION ALL SELECT 'event', EVENT_NAME, EVENT_SCHEMA, NULL FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ${collector.add(params.namespace)}
|
|
217
|
-
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" };
|
|
218
|
-
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" };
|
|
187
|
+
function decodeIsNoop(params) {
|
|
188
|
+
const { scalarType, wire } = params;
|
|
189
|
+
if (wire === undefined)
|
|
190
|
+
return false;
|
|
191
|
+
if (scalarType === "DateTime")
|
|
192
|
+
return false;
|
|
193
|
+
if (scalarType === "Json")
|
|
194
|
+
return wire.json === "parsed";
|
|
195
|
+
if (scalarType === "Int" || scalarType === "Float")
|
|
196
|
+
return wire.numbers === "native";
|
|
197
|
+
return false;
|
|
219
198
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
var
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
switch (operation.kind) {
|
|
237
|
-
case "createDatabase":
|
|
238
|
-
return result(`CREATE DATABASE ${dialect.quoteIdent({ name: operation.name })}${pg ? " TEMPLATE template0" : ""}`);
|
|
239
|
-
case "dropDatabase":
|
|
240
|
-
return result(`DROP DATABASE ${dialect.quoteIdent({ name: operation.name })}`);
|
|
241
|
-
case "identity":
|
|
242
|
-
return result(pg ? `SELECT current_database()::text AS database, (SELECT oid::text FROM pg_catalog.pg_database WHERE datname = current_database()) AS database_id, current_schema()::text AS namespace` : `SELECT DATABASE() AS \`database\`, @@lower_case_table_names AS name_folding, @@server_uuid AS server_id`);
|
|
243
|
-
case "lock":
|
|
244
|
-
return result(pg ? `SELECT pg_catalog.pg_advisory_lock(${collector.add(SHADOW_DATABASE.lock)})` : `SELECT GET_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}, ${collector.add(SHADOW_DATABASE.timeoutSeconds)}) AS acquired`);
|
|
245
|
-
case "unlock":
|
|
246
|
-
return result(pg ? `SELECT pg_catalog.pg_advisory_unlock(${collector.add(SHADOW_DATABASE.lock)}) AS released` : `SELECT RELEASE_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}) AS released`);
|
|
247
|
-
case "markerExists":
|
|
248
|
-
return result(pg ? `SELECT c.relname AS name FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${collector.add(SHADOW_DATABASE.schema)} AND c.relname = ${collector.add(SHADOW_DATABASE.table)}` : `SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)}`);
|
|
249
|
-
case "markerRead":
|
|
250
|
-
return result(`SELECT payload FROM ${marker}`);
|
|
251
|
-
case "markerColumns":
|
|
252
|
-
return result(`SELECT column_name AS name, data_type AS type, is_nullable AS nullable FROM information_schema.columns AS marker_column WHERE table_schema = ${pg ? collector.add(SHADOW_DATABASE.schema) : "DATABASE()"} AND table_name = ${collector.add(SHADOW_DATABASE.table)} AND ${pg ? `EXISTS (SELECT 1 FROM pg_catalog.pg_class r JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = marker_column.table_schema AND r.relname = marker_column.table_name AND r.relkind = 'r' AND r.relpersistence = 'p' AND NOT r.relrowsecurity AND NOT r.relforcerowsecurity AND NOT r.relhasrules AND NOT r.relhastriggers AND NOT r.relispartition AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attrdef a WHERE a.adrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_constraint c WHERE c.conrelid = r.oid AND c.contype <> 'n') AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_index i WHERE i.indrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = r.oid OR i.inhparent = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attribute a WHERE a.attrelid = r.oid AND (a.attisdropped OR a.attidentity <> '' OR a.attgenerated <> '')))` : `EXISTS (SELECT 1 FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = marker_column.table_schema AND t.TABLE_NAME = marker_column.table_name AND t.TABLE_TYPE = 'BASE TABLE' AND t.ENGINE = 'InnoDB') AND NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS c WHERE c.TABLE_SCHEMA = marker_column.table_schema AND c.TABLE_NAME = marker_column.table_name AND (c.COLUMN_DEFAULT IS NOT NULL OR c.EXTRA <> '')) AND NOT EXISTS (SELECT 1 FROM information_schema.TRIGGERS t WHERE t.EVENT_OBJECT_SCHEMA = DATABASE() AND t.EVENT_OBJECT_TABLE = ${collector.add(SHADOW_DATABASE.table)}) AND NOT EXISTS (SELECT 1 FROM information_schema.TABLE_CONSTRAINTS c WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)})`}
|
|
253
|
-
ORDER BY ordinal_position`);
|
|
254
|
-
case "grants": {
|
|
255
|
-
if (pg)
|
|
256
|
-
throw unsupportedCapability7({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
|
|
257
|
-
return result("SHOW GRANTS FOR CURRENT_USER");
|
|
258
|
-
}
|
|
259
|
-
case "markerCreate":
|
|
260
|
-
return pg ? [{ text: `CREATE SCHEMA ${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}`, values: [], returning: "none" }, { text: `CREATE TABLE ${marker} (payload text NOT NULL)`, values: [], returning: "none" }] : result(`CREATE TABLE ${marker} (payload LONGTEXT NOT NULL) ENGINE=InnoDB`);
|
|
261
|
-
case "markerWrite":
|
|
262
|
-
return result(operation.insert ? `INSERT INTO ${marker} (payload) VALUES (${collector.add(operation.value)})` : `UPDATE ${marker} SET payload = ${collector.add(operation.value)}`);
|
|
263
|
-
case "journal":
|
|
264
|
-
return result(`SELECT name, checksum, statement_index, error FROM ${dialect.quoteIdent({ name: "_vibe_migrations" })} ORDER BY name`);
|
|
265
|
-
case "inventory":
|
|
266
|
-
return result(pg ? `SELECT 'namespace' AS kind, nspname::text AS name, nspname::text AS namespace FROM pg_catalog.pg_namespace WHERE nspname NOT IN ('public', 'information_schema') AND nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
|
|
267
|
-
UNION ALL SELECT 'relation', c.relname::text, n.nspname::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
|
|
268
|
-
UNION ALL SELECT 'routine', p.proname::text, n.nspname::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
|
|
269
|
-
UNION ALL SELECT 'type', t.typname::text, n.nspname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND t.typrelid = 0 AND t.typelem = 0
|
|
270
|
-
UNION ALL SELECT 'extension', e.extname::text, n.nspname::text FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE e.extname <> 'plpgsql'
|
|
271
|
-
UNION ALL SELECT 'marker-dependency', d.objid::text, '${SHADOW_DATABASE.schema}' FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON d.classid = 'pg_class'::regclass AND d.objid = c.oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}' AND d.deptype NOT IN ('i', 'a') AND d.refclassid <> 'pg_namespace'::regclass
|
|
272
|
-
UNION ALL SELECT 'marker-trigger', t.tgname::text, n.nspname::text FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}'
|
|
273
|
-
UNION ALL SELECT 'namespace-object', o.name::text, n.nspname::text FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_namespace n ON d.refclassid = 'pg_namespace'::regclass AND d.refobjid = n.oid CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) o WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND d.classid NOT IN ('pg_class'::regclass, 'pg_proc'::regclass, 'pg_type'::regclass, 'pg_extension'::regclass)
|
|
274
|
-
UNION ALL SELECT 'database-object', evtname::text, '' FROM pg_catalog.pg_event_trigger
|
|
275
|
-
UNION ALL SELECT 'database-object', pubname::text, '' FROM pg_catalog.pg_publication
|
|
276
|
-
UNION ALL SELECT 'database-object', subname::text, '' FROM pg_catalog.pg_subscription WHERE subdbid = (SELECT oid FROM pg_catalog.pg_database WHERE datname = current_database())
|
|
277
|
-
UNION ALL SELECT 'database-object', srvname::text, '' FROM pg_catalog.pg_foreign_server
|
|
278
|
-
UNION ALL SELECT 'database-object', fdwname::text, '' FROM pg_catalog.pg_foreign_data_wrapper
|
|
279
|
-
UNION ALL SELECT 'database-object', c.oid::text, '' FROM pg_catalog.pg_cast c JOIN pg_catalog.pg_type s ON s.oid = c.castsource JOIN pg_catalog.pg_type t ON t.oid = c.casttarget LEFT JOIN pg_catalog.pg_proc p ON p.oid = c.castfunc WHERE s.typnamespace <> 'pg_catalog'::regnamespace OR t.typnamespace <> 'pg_catalog'::regnamespace OR p.pronamespace <> 'pg_catalog'::regnamespace` : `SELECT TABLE_TYPE AS kind, TABLE_NAME AS name, TABLE_SCHEMA AS namespace FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()
|
|
280
|
-
UNION ALL SELECT ROUTINE_TYPE, ROUTINE_NAME, ROUTINE_SCHEMA FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()
|
|
281
|
-
UNION ALL SELECT 'EVENT', EVENT_NAME, EVENT_SCHEMA FROM information_schema.EVENTS WHERE EVENT_SCHEMA = DATABASE()
|
|
282
|
-
UNION ALL SELECT 'TRIGGER', TRIGGER_NAME, TRIGGER_SCHEMA FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = DATABASE()
|
|
283
|
-
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)`);
|
|
284
|
-
case "resetPostgres":
|
|
285
|
-
case "resetDedicatedPostgres": {
|
|
286
|
-
if (!pg)
|
|
287
|
-
throw unsupportedCapability7({ dialect: dialect.name, feature: "postgresShadowReset" });
|
|
288
|
-
const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
|
|
289
|
-
for (const namespace of owned)
|
|
290
|
-
assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
|
|
291
|
-
return [
|
|
292
|
-
...owned.map((namespace) => ({ text: `DROP SCHEMA IF EXISTS ${dialect.quoteIdent({ name: namespace })} CASCADE`, values: [], returning: "none" })),
|
|
293
|
-
{ text: 'DROP SCHEMA "public" CASCADE', values: [], returning: "none" },
|
|
294
|
-
{ text: 'CREATE SCHEMA "public"', values: [], returning: "none" }
|
|
295
|
-
];
|
|
296
|
-
}
|
|
297
|
-
case "mysqlForeignKeys": {
|
|
298
|
-
if (pg)
|
|
299
|
-
throw unsupportedCapability7({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
300
|
-
return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
|
|
301
|
-
}
|
|
302
|
-
case "dropMysqlObject": {
|
|
303
|
-
if (pg)
|
|
304
|
-
throw unsupportedCapability7({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
305
|
-
return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
|
|
306
|
-
}
|
|
199
|
+
var identity = (value) => value;
|
|
200
|
+
var passthroughCodec = { encode: identity, decode: identity };
|
|
201
|
+
var numberCodec = {
|
|
202
|
+
encode: identity,
|
|
203
|
+
decode: (value) => typeof value === "string" ? Number(value) : value
|
|
204
|
+
};
|
|
205
|
+
var bigIntCodec = {
|
|
206
|
+
encode: (value) => typeof value === "bigint" ? value.toString() : value,
|
|
207
|
+
decode: (value) => {
|
|
208
|
+
if (typeof value === "bigint")
|
|
209
|
+
return value;
|
|
210
|
+
if (typeof value === "string" && /^[+-]?\d+$/.test(value.trim()))
|
|
211
|
+
return BigInt(value.trim());
|
|
212
|
+
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
213
|
+
return BigInt(value);
|
|
214
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
|
|
307
215
|
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
text = dialect.name === "sqlite" ? `PRAGMA table_info(${table})` : `SELECT column_name AS name FROM information_schema.columns WHERE table_schema = ${dialect.name === "postgres" ? "current_schema()" : "DATABASE()"} AND table_name = ${collector.add(params.table)}`;
|
|
321
|
-
break;
|
|
322
|
-
case "addColumn":
|
|
323
|
-
text = `ALTER TABLE ${table} ADD COLUMN ${column} ${dialect.name === "mysql" ? "LONGTEXT" : "TEXT"}`;
|
|
324
|
-
break;
|
|
325
|
-
case "read":
|
|
326
|
-
text = `SELECT * FROM ${table} ORDER BY ${q("id")}`;
|
|
327
|
-
break;
|
|
328
|
-
case "insert":
|
|
329
|
-
text = `INSERT INTO ${table} (${["name", "checksum", "statement_index", MODULE_JOURNAL_COLUMN].map(q).join(", ")}) VALUES (${collector.addMany([operation.name, operation.checksum, operation.index, operation.entry]).join(", ")})`;
|
|
330
|
-
break;
|
|
331
|
-
case "complete":
|
|
332
|
-
text = `UPDATE ${table} SET ${column} = ${collector.add(operation.entry)} WHERE ${q("name")} = ${collector.add(operation.name)}`;
|
|
333
|
-
break;
|
|
216
|
+
};
|
|
217
|
+
var decimalCodec = {
|
|
218
|
+
encode: (value) => typeof value === "number" || typeof value === "bigint" ? String(value) : value,
|
|
219
|
+
decode: identity
|
|
220
|
+
};
|
|
221
|
+
var mysqlDecimalCodec = {
|
|
222
|
+
encode: decimalCodec.encode,
|
|
223
|
+
decode: (value) => {
|
|
224
|
+
if (typeof value !== "string" || !value.includes("."))
|
|
225
|
+
return value;
|
|
226
|
+
const trimmed = value.replace(/0+$/, "").replace(/\.$/, "");
|
|
227
|
+
return trimmed === "" || trimmed === "-" ? "0" : trimmed;
|
|
334
228
|
}
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
function construct(parts) {
|
|
355
|
-
const snapshot = Object.freeze([...parts]);
|
|
356
|
-
const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
|
|
357
|
-
value: () => ({ parts: snapshot })
|
|
358
|
-
}));
|
|
359
|
-
fragments.set(fragment, snapshot);
|
|
360
|
-
return fragment;
|
|
229
|
+
};
|
|
230
|
+
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
231
|
+
var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
|
|
232
|
+
function parseDateTimeText(params) {
|
|
233
|
+
const trimmed = params.text.trim();
|
|
234
|
+
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
235
|
+
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
236
|
+
if (!Number.isNaN(asUtc.getTime()))
|
|
237
|
+
return asUtc;
|
|
238
|
+
}
|
|
239
|
+
const zoned = PG_ZONED_DATETIME.exec(trimmed);
|
|
240
|
+
if (zoned !== null) {
|
|
241
|
+
const [, date, time, fraction, hours, minutes] = zoned;
|
|
242
|
+
const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
|
|
243
|
+
if (!Number.isNaN(iso.getTime()))
|
|
244
|
+
return iso;
|
|
245
|
+
}
|
|
246
|
+
const parsed = new Date(trimmed);
|
|
247
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
361
248
|
}
|
|
362
|
-
|
|
363
|
-
|
|
249
|
+
var dateNativeCodec = {
|
|
250
|
+
encode: (value) => {
|
|
251
|
+
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
252
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
253
|
+
}
|
|
254
|
+
return value;
|
|
255
|
+
},
|
|
256
|
+
decode: (value) => {
|
|
257
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
258
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
259
|
+
return date;
|
|
260
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var dateTextCodec = {
|
|
264
|
+
encode: (value) => {
|
|
265
|
+
dateNativeCodec.encode(value);
|
|
266
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
267
|
+
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
268
|
+
return date.toISOString();
|
|
269
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
270
|
+
},
|
|
271
|
+
decode: dateNativeCodec.decode
|
|
272
|
+
};
|
|
273
|
+
var mysqlDateCodec = {
|
|
274
|
+
encode: (value) => {
|
|
275
|
+
dateNativeCodec.encode(value);
|
|
276
|
+
if (value instanceof Date)
|
|
277
|
+
return value;
|
|
278
|
+
if (typeof value === "number") {
|
|
279
|
+
const parsed = new Date(value);
|
|
280
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
281
|
+
return parsed;
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
if (typeof value === "string")
|
|
285
|
+
return parseDateTimeText({ text: value }) ?? value;
|
|
286
|
+
return value;
|
|
287
|
+
},
|
|
288
|
+
decode: dateNativeCodec.decode
|
|
289
|
+
};
|
|
290
|
+
var MILLISECONDS_PER_DAY = 86400000;
|
|
291
|
+
var TIME_OF_DAY_TEXT = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/;
|
|
292
|
+
var pad = (value, width) => String(value).padStart(width, "0");
|
|
293
|
+
var utcMillisecondOfDay = (date) => (date.getTime() % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
|
|
294
|
+
var timeOfDayText = (millisecond) => `${pad(Math.floor(millisecond / 3600000), 2)}:${pad(Math.floor(millisecond / 60000) % 60, 2)}:${pad(Math.floor(millisecond / 1000) % 60, 2)}.${pad(millisecond % 1000, 3)}`;
|
|
295
|
+
function makeTimeOfDayCodec(params) {
|
|
296
|
+
const suffix = params.zoned ? "+00" : "";
|
|
297
|
+
return Object.freeze({
|
|
298
|
+
encode: (value) => {
|
|
299
|
+
dateNativeCodec.encode(value);
|
|
300
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
301
|
+
if (date === undefined || !Number.isFinite(date.getTime())) {
|
|
302
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
|
|
303
|
+
}
|
|
304
|
+
return `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
305
|
+
},
|
|
306
|
+
decode: (value) => {
|
|
307
|
+
if (value instanceof Date && Number.isFinite(value.getTime()))
|
|
308
|
+
return new Date(utcMillisecondOfDay(value));
|
|
309
|
+
const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
|
|
310
|
+
if (match !== null) {
|
|
311
|
+
const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
|
|
312
|
+
const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
|
|
313
|
+
const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
|
|
314
|
+
if (millisecond <= MILLISECONDS_PER_DAY) {
|
|
315
|
+
return new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "a time-of-day DateTime column returned a value that is not a time of day", meta: { value } });
|
|
319
|
+
}
|
|
320
|
+
});
|
|
364
321
|
}
|
|
365
|
-
|
|
366
|
-
|
|
322
|
+
var timeCodec = makeTimeOfDayCodec({ zoned: false });
|
|
323
|
+
var timetzCodec = makeTimeOfDayCodec({ zoned: true });
|
|
324
|
+
function timeOfDayCodec(params) {
|
|
325
|
+
const type = timeOfDayType({ nativeType: params.nativeType });
|
|
326
|
+
return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
|
|
367
327
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
328
|
+
var booleanIntCodec = {
|
|
329
|
+
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
330
|
+
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
331
|
+
};
|
|
332
|
+
var booleanNativeInCodec = {
|
|
333
|
+
encode: identity,
|
|
334
|
+
decode: (value) => value === "0" ? false : value === "1" ? true : booleanIntCodec.decode(value)
|
|
335
|
+
};
|
|
336
|
+
function parseJsonText(params) {
|
|
337
|
+
const { text, context } = params;
|
|
338
|
+
try {
|
|
339
|
+
return JSON.parse(text);
|
|
340
|
+
} catch (cause) {
|
|
341
|
+
if (context !== undefined) {
|
|
342
|
+
throw new VibeError({
|
|
343
|
+
code: "VIBE_INTERNAL",
|
|
344
|
+
message: "a bound Json field returned text that is not valid JSON",
|
|
345
|
+
meta: { ...context }
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
throw new VibeError({
|
|
349
|
+
code: "VIBE_INTERNAL",
|
|
350
|
+
message: "a Json column returned text that is not valid JSON",
|
|
351
|
+
meta: { value: text },
|
|
352
|
+
cause
|
|
353
|
+
});
|
|
371
354
|
}
|
|
372
355
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
356
|
+
var jsonCodec = {
|
|
357
|
+
encode: (value) => JSON.stringify(value),
|
|
358
|
+
decode: (value) => {
|
|
359
|
+
if (typeof value !== "string")
|
|
360
|
+
return value;
|
|
361
|
+
return parseJsonText({ text: value });
|
|
376
362
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
363
|
+
};
|
|
364
|
+
function shortestFloat32(params) {
|
|
365
|
+
const { value } = params;
|
|
366
|
+
if (value === 0 || !Number.isFinite(value))
|
|
367
|
+
return value;
|
|
368
|
+
const target = Math.fround(value);
|
|
369
|
+
for (let precision = 1;precision <= 9; precision++) {
|
|
370
|
+
const candidate = Number(value.toPrecision(precision));
|
|
371
|
+
if (Math.fround(candidate) === target)
|
|
372
|
+
return candidate;
|
|
387
373
|
}
|
|
388
|
-
return
|
|
374
|
+
return value;
|
|
389
375
|
}
|
|
390
|
-
var
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return
|
|
395
|
-
},
|
|
396
|
-
ident: ({ parts }) => {
|
|
397
|
-
identifier(parts);
|
|
398
|
-
return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
|
|
399
|
-
},
|
|
400
|
-
value: ({ value, decoder }) => construct([Object.freeze({ value, ...decoder === undefined ? {} : { decoder } })]),
|
|
401
|
-
join: ({ items, separator }) => {
|
|
402
|
-
if (!Array.isArray(items))
|
|
403
|
-
invalid("sql.join requires an array");
|
|
404
|
-
const between = separator === undefined ? [", "] : partsOf(separator);
|
|
405
|
-
const parts = [];
|
|
406
|
-
items.forEach((value, index) => {
|
|
407
|
-
if (index > 0)
|
|
408
|
-
parts.push(...between);
|
|
409
|
-
parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value })]);
|
|
410
|
-
});
|
|
411
|
-
return construct(parts);
|
|
412
|
-
},
|
|
413
|
-
existing: ({ column }) => {
|
|
414
|
-
identifier([column]);
|
|
415
|
-
return construct([Object.freeze({ row: "existing", column })]);
|
|
416
|
-
},
|
|
417
|
-
incoming: ({ column }) => {
|
|
418
|
-
identifier([column]);
|
|
419
|
-
return construct([Object.freeze({ row: "incoming", column })]);
|
|
420
|
-
},
|
|
421
|
-
greatest: ({ values }) => {
|
|
422
|
-
if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
|
|
423
|
-
invalid("sql.greatest requires two SQL fragments");
|
|
424
|
-
return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
|
|
376
|
+
var float32Codec = {
|
|
377
|
+
encode: identity,
|
|
378
|
+
decode: (value) => {
|
|
379
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
380
|
+
return typeof number === "number" ? shortestFloat32({ value: number }) : number;
|
|
425
381
|
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
382
|
+
};
|
|
383
|
+
var booleanFromText = (text) => text === "t" ? true : text === "f" ? false : text;
|
|
384
|
+
var bytesFromText = (text) => /^\\x(?:[0-9a-fA-F]{2})*$/.test(text) ? new Uint8Array(Buffer.from(text.slice(2), "hex")) : text;
|
|
385
|
+
function postgresArrayElementFromText(params) {
|
|
386
|
+
switch (params.scalarType) {
|
|
387
|
+
case "Boolean":
|
|
388
|
+
return booleanFromText;
|
|
389
|
+
case "Bytes":
|
|
390
|
+
return bytesFromText;
|
|
391
|
+
case "Json":
|
|
392
|
+
return jsonCodec.decode;
|
|
393
|
+
default:
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
var POSTGRES_CODECS = {
|
|
398
|
+
String: passthroughCodec,
|
|
399
|
+
Boolean: passthroughCodec,
|
|
400
|
+
Int: numberCodec,
|
|
401
|
+
BigInt: bigIntCodec,
|
|
402
|
+
Float: numberCodec,
|
|
403
|
+
Decimal: decimalCodec,
|
|
404
|
+
DateTime: dateNativeCodec,
|
|
405
|
+
Json: jsonCodec,
|
|
406
|
+
Bytes: passthroughCodec
|
|
407
|
+
};
|
|
408
|
+
var SQLITE_CODECS = {
|
|
409
|
+
String: passthroughCodec,
|
|
410
|
+
Boolean: booleanIntCodec,
|
|
411
|
+
Int: numberCodec,
|
|
412
|
+
BigInt: bigIntCodec,
|
|
413
|
+
Float: numberCodec,
|
|
414
|
+
Decimal: decimalCodec,
|
|
415
|
+
DateTime: dateTextCodec,
|
|
416
|
+
Json: jsonCodec,
|
|
417
|
+
Bytes: passthroughCodec
|
|
418
|
+
};
|
|
419
|
+
var MYSQL_CODECS = {
|
|
420
|
+
String: passthroughCodec,
|
|
421
|
+
Boolean: booleanNativeInCodec,
|
|
422
|
+
Int: numberCodec,
|
|
423
|
+
BigInt: bigIntCodec,
|
|
424
|
+
Float: numberCodec,
|
|
425
|
+
Decimal: mysqlDecimalCodec,
|
|
426
|
+
DateTime: mysqlDateCodec,
|
|
427
|
+
Json: jsonCodec,
|
|
428
|
+
Bytes: passthroughCodec
|
|
429
|
+
};
|
|
430
|
+
var DIALECT_CODECS = {
|
|
431
|
+
postgres: POSTGRES_CODECS,
|
|
432
|
+
sqlite: SQLITE_CODECS,
|
|
433
|
+
mysql: MYSQL_CODECS
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// packages/sql/src/result-decoder.ts
|
|
437
|
+
var scalarContracts = new WeakMap;
|
|
438
|
+
function resultScalarContract(params) {
|
|
439
|
+
return scalarContracts.get(params.decoder);
|
|
440
|
+
}
|
|
441
|
+
function registerScalar(params) {
|
|
442
|
+
if (params.contract !== undefined)
|
|
443
|
+
scalarContracts.set(params.decoder, Object.freeze(params.contract));
|
|
444
|
+
return params.decoder;
|
|
445
|
+
}
|
|
446
|
+
function wrappedContract(params) {
|
|
447
|
+
const contract = scalarContracts.get(params.decoder);
|
|
448
|
+
if (contract === undefined || params.list === true && contract.list)
|
|
449
|
+
return;
|
|
450
|
+
return { ...contract, ...params.list === true ? { list: true, nullable: false } : {}, ...params.nullable === true ? { nullable: true } : {} };
|
|
451
|
+
}
|
|
452
|
+
function refuse(encoding, message) {
|
|
453
|
+
throw new VibeError2({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
454
|
+
}
|
|
455
|
+
function scalar(type, valid) {
|
|
456
|
+
const convert = (params, encoding) => {
|
|
457
|
+
const { value, dialect } = params;
|
|
458
|
+
if (value === null || value === undefined)
|
|
459
|
+
refuse(encoding, `A non-null ${type} projection received null or a missing column`);
|
|
460
|
+
if (type === "BigInt" && !(typeof value === "bigint" || typeof value === "string" && /^[+-]?\d+$/.test(value) || typeof value === "number" && Number.isSafeInteger(value))) {
|
|
461
|
+
refuse(encoding, "BigInt requires exact integer text, bigint, or a safe integer");
|
|
437
462
|
}
|
|
438
|
-
if ("
|
|
439
|
-
|
|
440
|
-
if ("
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (name === undefined)
|
|
445
|
-
invalid(`Unknown conflict-expression field "${part.column}"`);
|
|
446
|
-
const ref = dialect.excludedRowRef;
|
|
447
|
-
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
448
|
-
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
463
|
+
if (type === "Boolean" && value !== true && value !== false && value !== 0 && value !== 1 && !(!encoding && dialect === "mysql" && (value === "0" || value === "1")))
|
|
464
|
+
refuse(encoding, "Boolean requires true, false, 0, or 1");
|
|
465
|
+
if (type === "Decimal" && typeof value === "number")
|
|
466
|
+
refuse(encoding, "Decimal requires exact decimal text, not a floating-point number");
|
|
467
|
+
if ((type === "Int" || type === "Float") && typeof value === "string" && !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) {
|
|
468
|
+
refuse(encoding, `Invalid ${type} text`);
|
|
449
469
|
}
|
|
450
|
-
|
|
451
|
-
|
|
470
|
+
const codec = DIALECT_CODECS[dialect][type];
|
|
471
|
+
const result = type === "Json" && params.wire?.json === "parsed" ? value : codec.decode(value);
|
|
472
|
+
if (!valid(result))
|
|
473
|
+
refuse(encoding, `Invalid ${type} projection value`);
|
|
474
|
+
return result;
|
|
475
|
+
};
|
|
476
|
+
return registerScalar({ contract: { scalar: type, nullable: false, list: false }, decoder: Object.freeze({
|
|
477
|
+
decode: (params) => convert(params, false),
|
|
478
|
+
encode: (params) => {
|
|
479
|
+
const value = type === "Json" ? params.value : convert(params, true);
|
|
480
|
+
if (value === undefined || value === null)
|
|
481
|
+
refuse(true, `A non-null ${type} parameter cannot be null or undefined`);
|
|
482
|
+
return DIALECT_CODECS[params.dialect][type].encode(value);
|
|
483
|
+
}
|
|
484
|
+
}) });
|
|
452
485
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if ("
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
486
|
+
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
487
|
+
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
488
|
+
var extendedDecoder = registerScalar({ contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
|
|
489
|
+
decode: (params) => {
|
|
490
|
+
if (params.value === "infinity" || params.value === Infinity)
|
|
491
|
+
return "infinity";
|
|
492
|
+
if (params.value === "-infinity" || params.value === -Infinity)
|
|
493
|
+
return "-infinity";
|
|
494
|
+
return dateDecoder.decode(params);
|
|
495
|
+
},
|
|
496
|
+
encode: (params) => {
|
|
497
|
+
if (params.value === "infinity" || params.value === "-infinity") {
|
|
498
|
+
if (params.dialect !== "postgres")
|
|
499
|
+
throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
500
|
+
return params.value;
|
|
462
501
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
502
|
+
return dateDecoder.encode(params);
|
|
503
|
+
}
|
|
504
|
+
}) });
|
|
505
|
+
function hasEncoder(decoder) {
|
|
506
|
+
return "encode" in decoder && typeof decoder.encode === "function";
|
|
507
|
+
}
|
|
508
|
+
function nullable({ decoder }) {
|
|
509
|
+
const read = (params) => params.value === null ? null : decoder.decode(params);
|
|
510
|
+
if (!hasEncoder(decoder))
|
|
511
|
+
return Object.freeze({ decode: read });
|
|
512
|
+
return registerScalar({ contract: wrappedContract({ decoder, nullable: true }), decoder: Object.freeze({
|
|
513
|
+
decode: read,
|
|
514
|
+
encode: (params) => params.value === null ? null : decoder.encode(params)
|
|
515
|
+
}) });
|
|
516
|
+
}
|
|
517
|
+
function array({ decoder }) {
|
|
518
|
+
const bidirectional = hasEncoder(decoder);
|
|
519
|
+
const read = (params) => {
|
|
520
|
+
const raw = params.value;
|
|
521
|
+
let value;
|
|
522
|
+
if (params.dialect === "postgres" && typeof raw === "string") {
|
|
523
|
+
if (bidirectional)
|
|
524
|
+
value = parsePgArrayLiteral({ literal: raw });
|
|
525
|
+
else {
|
|
526
|
+
const elements = parsePgArrayLiteralNested({ literal: raw });
|
|
527
|
+
if (elements !== null && elements.some((element) => Array.isArray(element)))
|
|
528
|
+
refuse(false, "Read-only array decoders support one-dimensional SQL arrays only");
|
|
529
|
+
value = elements;
|
|
530
|
+
}
|
|
531
|
+
} else
|
|
532
|
+
value = ArrayBuffer.isView(raw) && "length" in raw ? Array.from(raw) : raw;
|
|
533
|
+
if (!Array.isArray(value))
|
|
534
|
+
refuse(false, "Expected an array projection");
|
|
535
|
+
return value.map((element) => decoder.decode({ ...params, value: element }));
|
|
536
|
+
};
|
|
537
|
+
if (!hasEncoder(decoder))
|
|
538
|
+
return Object.freeze({ decode: read });
|
|
539
|
+
return registerScalar({ contract: wrappedContract({ decoder, list: true }), decoder: Object.freeze({
|
|
540
|
+
decode: read,
|
|
541
|
+
encode: (params) => {
|
|
542
|
+
if (!Array.isArray(params.value))
|
|
543
|
+
refuse(true, "Expected an array parameter");
|
|
544
|
+
return params.value.map((value) => decoder.encode({ ...params, value }));
|
|
545
|
+
}
|
|
546
|
+
}) });
|
|
547
|
+
}
|
|
548
|
+
var encode = Object.freeze({
|
|
549
|
+
array: ({ encoder }) => Object.freeze({
|
|
550
|
+
encode: (params) => {
|
|
551
|
+
if (!Array.isArray(params.value))
|
|
552
|
+
refuse(true, "Expected an array parameter");
|
|
553
|
+
return Array.from(params.value, (value) => encoder.encode({ ...params, value }));
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
});
|
|
557
|
+
var decode = Object.freeze({
|
|
558
|
+
string: stringDecoder,
|
|
559
|
+
boolean: scalar("Boolean", (value) => typeof value === "boolean"),
|
|
560
|
+
int: scalar("Int", (value) => typeof value === "number" && Number.isSafeInteger(value)),
|
|
561
|
+
float: scalar("Float", (value) => typeof value === "number" && Number.isFinite(value)),
|
|
562
|
+
bigInt: scalar("BigInt", (value) => typeof value === "bigint"),
|
|
563
|
+
decimal: scalar("Decimal", (value) => typeof value === "string" && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)),
|
|
564
|
+
dateTime: dateDecoder,
|
|
565
|
+
extendedDateTime: extendedDecoder,
|
|
566
|
+
json: scalar("Json", (value) => value !== undefined),
|
|
567
|
+
bytes: scalar("Bytes", (value) => value instanceof Uint8Array),
|
|
568
|
+
nullable,
|
|
569
|
+
array,
|
|
570
|
+
enum: ({ values }) => {
|
|
571
|
+
const members = new Set(values);
|
|
572
|
+
return registerScalar({ contract: { scalar: "String", nullable: false, list: false }, decoder: Object.freeze({
|
|
573
|
+
decode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(false, "Unknown enum projection member"),
|
|
574
|
+
encode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(true, "Unknown enum parameter member")
|
|
575
|
+
}) });
|
|
576
|
+
},
|
|
577
|
+
object: ({ fields }) => {
|
|
578
|
+
const entries = Object.entries(fields);
|
|
579
|
+
const convert = (params, encoding) => {
|
|
580
|
+
if (typeof params.value !== "object" || params.value === null || Array.isArray(params.value))
|
|
581
|
+
refuse(encoding, "Expected a result row object");
|
|
582
|
+
const row = params.value;
|
|
583
|
+
return Object.fromEntries(entries.map(([name, decoder]) => {
|
|
584
|
+
if (!Object.hasOwn(row, name))
|
|
585
|
+
refuse(encoding, `Missing projection column "${name}"`);
|
|
586
|
+
return [name, encoding ? decoder.encode({ ...params, value: row[name] }) : decoder.decode({ ...params, value: row[name] })];
|
|
587
|
+
}));
|
|
588
|
+
};
|
|
589
|
+
return Object.freeze({ decode: (params) => convert(params, false), encode: (params) => convert(params, true) });
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// packages/sql/src/sql-input.ts
|
|
594
|
+
var inputs = new WeakMap;
|
|
595
|
+
var tokens = new WeakMap;
|
|
596
|
+
var scopes = new WeakMap;
|
|
597
|
+
function invalid(message) {
|
|
598
|
+
throw new VibeError3({ code: "VIBE_VALIDATION", message });
|
|
599
|
+
}
|
|
600
|
+
function isSqlParam(value) {
|
|
601
|
+
return typeof value === "object" && value !== null && tokens.has(value);
|
|
602
|
+
}
|
|
603
|
+
function isSqlInput(value) {
|
|
604
|
+
return typeof value === "object" && value !== null && inputs.has(value);
|
|
605
|
+
}
|
|
606
|
+
function createSqlInput(params) {
|
|
607
|
+
if (typeof params.contract.encoder?.encode !== "function")
|
|
608
|
+
invalid("SQL input contracts require a parameter encoder");
|
|
609
|
+
const input = Object.freeze(Object.create(null));
|
|
610
|
+
inputs.set(input, Object.freeze({
|
|
611
|
+
...params.contract,
|
|
612
|
+
...params.contract.enumValues === undefined ? {} : { enumValues: Object.freeze([...params.contract.enumValues]) },
|
|
613
|
+
...params.contract.enumType === undefined ? {} : { enumType: Object.freeze({ ...params.contract.enumType }) }
|
|
466
614
|
}));
|
|
615
|
+
return input;
|
|
616
|
+
}
|
|
617
|
+
function sqlInputContract(params) {
|
|
618
|
+
const value = params.input;
|
|
619
|
+
if (typeof value !== "object" || value === null)
|
|
620
|
+
invalid("SQL input declarations require a decoder or qb.input descriptor");
|
|
621
|
+
const declared = inputs.get(value);
|
|
622
|
+
if (declared !== undefined)
|
|
623
|
+
return declared;
|
|
624
|
+
if (!("encode" in value) || !("decode" in value) || typeof value.encode !== "function" || typeof value.decode !== "function")
|
|
625
|
+
invalid("Expected an authentic SQL input descriptor or bidirectional decoder");
|
|
626
|
+
const decoder = value;
|
|
627
|
+
const physical = resultScalarContract({ decoder });
|
|
628
|
+
return Object.freeze({ encoder: decoder, scalar: physical?.scalar ?? "Unknown", nullable: physical?.nullable ?? true, list: physical?.list ?? false });
|
|
629
|
+
}
|
|
630
|
+
function createSqlInputScope(params) {
|
|
631
|
+
if (typeof params.isHandle !== "function")
|
|
632
|
+
invalid("SQL input scopes require the builder's handle check");
|
|
633
|
+
const declarations = params.inputs;
|
|
634
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations))
|
|
635
|
+
invalid("Reusable SQL requires an input declaration record");
|
|
636
|
+
const prototype = Object.getPrototypeOf(declarations);
|
|
637
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
638
|
+
invalid("SQL input declarations require a plain record");
|
|
639
|
+
const keys = Reflect.ownKeys(declarations);
|
|
640
|
+
const entries = keys.map((key) => {
|
|
641
|
+
if (typeof key !== "string" || key.length === 0 || key.includes("\x00"))
|
|
642
|
+
invalid("SQL input names must be nonempty strings without NUL");
|
|
643
|
+
const descriptor = Object.getOwnPropertyDescriptor(declarations, key);
|
|
644
|
+
if (!("value" in descriptor))
|
|
645
|
+
invalid("SQL input declarations cannot contain accessors");
|
|
646
|
+
return [key, sqlInputContract({ input: descriptor.value })];
|
|
647
|
+
});
|
|
648
|
+
const scope = Object.freeze(Object.create(null));
|
|
649
|
+
scopes.set(scope, { open: true, keys: Object.freeze(entries.map(([key]) => key)), isHandle: params.isHandle });
|
|
650
|
+
const record = Object.create(null);
|
|
651
|
+
for (const [key, contract] of entries) {
|
|
652
|
+
const param = Object.freeze(Object.create(null));
|
|
653
|
+
tokens.set(param, { scope, key, contract });
|
|
654
|
+
record[key] = param;
|
|
655
|
+
}
|
|
656
|
+
return { scope, params: Object.freeze(record) };
|
|
467
657
|
}
|
|
468
|
-
function
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
return { text, values: collector.values };
|
|
658
|
+
function sealSqlInputScope(params) {
|
|
659
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
660
|
+
state.open = false;
|
|
472
661
|
}
|
|
473
|
-
function
|
|
474
|
-
|
|
662
|
+
function assertSqlParamBuilding(params) {
|
|
663
|
+
const state = tokens.get(params.param) ?? invalid("Expected an authentic SQL input token");
|
|
664
|
+
if (scopes.get(state.scope)?.open !== true)
|
|
665
|
+
invalid("SQL input tokens cannot escape their reusable build callback");
|
|
666
|
+
}
|
|
667
|
+
function sqlParamContract(params) {
|
|
668
|
+
return (tokens.get(params.param) ?? invalid("Expected an authentic SQL input token")).contract;
|
|
669
|
+
}
|
|
670
|
+
function sqlParamKey(params) {
|
|
671
|
+
const state = tokens.get(params.param) ?? invalid("Expected an authentic SQL input token");
|
|
672
|
+
if (state.scope !== params.scope)
|
|
673
|
+
invalid("A reusable query cannot capture another template's input token");
|
|
674
|
+
return state.key;
|
|
675
|
+
}
|
|
676
|
+
function sqlInputScopeHandle(params) {
|
|
677
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
678
|
+
return state.isHandle(params.value);
|
|
679
|
+
}
|
|
680
|
+
function sqlInputScopeKeys(params) {
|
|
681
|
+
const state = scopes.get(params.scope) ?? invalid("Expected an authentic SQL input scope");
|
|
682
|
+
if (state.open)
|
|
683
|
+
invalid("A reusable SQL build must finish before compilation");
|
|
684
|
+
return state.keys;
|
|
475
685
|
}
|
|
476
|
-
// packages/sql/src/result-decoder.ts
|
|
477
|
-
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
478
686
|
|
|
479
|
-
// packages/sql/src/
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return
|
|
687
|
+
// packages/sql/src/parameter-snapshot.ts
|
|
688
|
+
function snapshotSqlValue(value, ancestors = new Set, isHandle) {
|
|
689
|
+
const invalid = (message) => {
|
|
690
|
+
throw new VibeError4({ code: "VIBE_VALIDATION", message });
|
|
691
|
+
};
|
|
692
|
+
if (isSqlParam(value))
|
|
693
|
+
return invalid("SQL input tokens are valid only in admitted reusable builder positions");
|
|
694
|
+
if (isHandle !== undefined && typeof value === "object" && value !== null && isHandle(value))
|
|
695
|
+
return invalid("SQL builder, plan, template, input and fragment handles are not execution values");
|
|
696
|
+
if (value === undefined)
|
|
697
|
+
return invalid("SQL values cannot be undefined; omit a defaulted column or use null");
|
|
698
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "bigint")
|
|
699
|
+
return value;
|
|
700
|
+
if (typeof value === "number")
|
|
701
|
+
return value;
|
|
702
|
+
if (value instanceof Date)
|
|
703
|
+
return Number.isFinite(value.getTime()) ? new Date(value.getTime()) : invalid("SQL dates must be finite");
|
|
704
|
+
if (value instanceof Uint8Array)
|
|
705
|
+
return new Uint8Array(value);
|
|
706
|
+
if (typeof value !== "object")
|
|
707
|
+
return invalid("Unsupported SQL parameter value");
|
|
708
|
+
if (ancestors.has(value))
|
|
709
|
+
return invalid("Cyclic SQL parameter values are unsupported");
|
|
710
|
+
const array = Array.isArray(value);
|
|
711
|
+
const prototype = Object.getPrototypeOf(value);
|
|
712
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
|
|
713
|
+
return invalid("SQL parameters require standard arrays or plain objects");
|
|
714
|
+
const nested = new Set([...ancestors, value]);
|
|
715
|
+
const snapshot = array ? [] : Object.create(prototype === null ? null : Object.prototype);
|
|
716
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
717
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
718
|
+
if (!("value" in descriptor))
|
|
719
|
+
return invalid("SQL parameter objects and arrays cannot contain accessors");
|
|
720
|
+
Object.defineProperty(snapshot, key, { ...descriptor, value: snapshotSqlValue(descriptor.value, nested, isHandle) });
|
|
721
|
+
}
|
|
722
|
+
return snapshot;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// packages/sql/src/sql-bindings.ts
|
|
726
|
+
var recordings = new WeakMap;
|
|
727
|
+
var programs = new WeakMap;
|
|
728
|
+
function invalid2(message) {
|
|
729
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message });
|
|
730
|
+
}
|
|
731
|
+
function unsupported(message) {
|
|
732
|
+
throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message });
|
|
733
|
+
}
|
|
734
|
+
function sqlParameterTransport(params) {
|
|
735
|
+
if (isNativeEnumArrayParameter(params.value))
|
|
736
|
+
return { kind: "native-enum-array", type: params.value.type };
|
|
737
|
+
if (isJsonListParameter(params.value))
|
|
738
|
+
return { kind: "json-list", storage: params.value.storage, whole: typeof params.value.value !== "string" };
|
|
739
|
+
return { kind: "plain" };
|
|
740
|
+
}
|
|
741
|
+
function renderSqlParameterTransport(params) {
|
|
742
|
+
const { dialect, placeholder, transport } = params;
|
|
743
|
+
if (transport.kind === "plain")
|
|
744
|
+
return placeholder;
|
|
745
|
+
if (!dialect.capabilities.scalarArrays)
|
|
746
|
+
throw unsupportedCapability2({ dialect: dialect.name, feature: "scalarArrays" });
|
|
747
|
+
if (transport.kind === "native-enum-array")
|
|
748
|
+
return `${placeholder}::${dialect.quoteIdent({ name: transport.type.namespace })}.${dialect.quoteIdent({ name: transport.type.name })}[]`;
|
|
749
|
+
return `${placeholder}::text::${transport.storage}${transport.whole ? "[]" : ""}`;
|
|
750
|
+
}
|
|
751
|
+
function sqlParameterWireValue(params) {
|
|
752
|
+
if (isNativeEnumArrayParameter(params.value))
|
|
753
|
+
return params.value.value;
|
|
754
|
+
if (isJsonListParameter(params.value))
|
|
755
|
+
return typeof params.value.value === "string" ? params.value.value : toPgArrayLiteral({ values: params.value.value });
|
|
756
|
+
return params.value;
|
|
757
|
+
}
|
|
758
|
+
function isSqlBindingCollector(params) {
|
|
759
|
+
return recordings.has(params.collector);
|
|
760
|
+
}
|
|
761
|
+
function startSqlBindingCollection(params) {
|
|
762
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
763
|
+
if (recordings.has(params.collector))
|
|
764
|
+
invalid2("SQL binding collection was already started");
|
|
765
|
+
recordings.set(params.collector, { dialect: params.dialect, scope: params.scope, steps: [], contracts: new Map, used: new Set });
|
|
766
|
+
}
|
|
767
|
+
function contractShape(contract) {
|
|
768
|
+
return JSON.stringify({
|
|
769
|
+
scalar: contract.scalar,
|
|
770
|
+
nullable: contract.nullable,
|
|
771
|
+
list: contract.list,
|
|
772
|
+
nativeType: contract.nativeType,
|
|
773
|
+
enumValues: contract.enumValues,
|
|
774
|
+
enumType: contract.enumType === undefined ? undefined : [contract.enumType.namespace, contract.enumType.name],
|
|
775
|
+
jsonStorage: contract.jsonStorage
|
|
488
776
|
});
|
|
489
|
-
return `{${parts.join(",")}}`;
|
|
490
777
|
}
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
778
|
+
function sameContract(left, right) {
|
|
779
|
+
return left.encoder === right.encoder && left.columnIdentity === right.columnIdentity && contractShape(left) === contractShape(right);
|
|
780
|
+
}
|
|
781
|
+
function recordSqlBinding(params) {
|
|
782
|
+
const state = recordings.get(params.collector) ?? invalid2("SQL input tokens require execution through their reusable template");
|
|
783
|
+
let key;
|
|
784
|
+
if (params.source.kind === "input") {
|
|
785
|
+
key = sqlParamKey({ param: params.source.param, scope: state.scope });
|
|
786
|
+
if (params.contract === undefined)
|
|
787
|
+
invalid2("Named SQL inputs require an effective encoding contract");
|
|
788
|
+
const previous = state.contracts.get(key);
|
|
789
|
+
if (previous !== undefined && !sameContract(previous, params.contract))
|
|
790
|
+
invalid2(`SQL input "${key}" has incompatible encoding contracts`);
|
|
791
|
+
state.contracts.set(key, params.contract);
|
|
792
|
+
state.used.add(key);
|
|
793
|
+
}
|
|
794
|
+
const transport = params.transport.kind === "native-enum-array" ? Object.freeze({ ...params.transport, type: Object.freeze({ ...params.transport.type }) }) : Object.freeze({ ...params.transport });
|
|
795
|
+
const source = params.source.kind === "literal" ? Object.freeze({ kind: "literal", value: snapshotSqlValue(params.source.value) }) : Object.freeze({ ...params.source });
|
|
796
|
+
const placeholder = params.collector.reserve();
|
|
797
|
+
const text = renderSqlParameterTransport({ dialect: state.dialect, placeholder, transport });
|
|
798
|
+
state.steps.push(Object.freeze({ kind: "slot", source, key, transport, encode: params.encode, rejectNull: params.rejectNull === true, ...params.validate === undefined ? {} : { validate: params.validate } }));
|
|
799
|
+
return text;
|
|
800
|
+
}
|
|
801
|
+
function recordSqlBindingCheck(params) {
|
|
802
|
+
const state = recordings.get(params.collector) ?? invalid2("Expected a structural SQL collector");
|
|
803
|
+
state.steps.push(Object.freeze({ kind: "check", check: params.check }));
|
|
804
|
+
}
|
|
805
|
+
function finishSqlBindingCollection(params) {
|
|
806
|
+
const state = recordings.get(params.collector) ?? invalid2("Expected a structural SQL collector");
|
|
807
|
+
const keys = sqlInputScopeKeys({ scope: state.scope });
|
|
808
|
+
for (const key of keys)
|
|
809
|
+
if (!state.used.has(key))
|
|
810
|
+
invalid2(`Declared SQL input "${key}" is unused by the finished query`);
|
|
811
|
+
const program = Object.freeze(Object.create(null));
|
|
812
|
+
programs.set(program, { dialect: state.dialect, keys, steps: Object.freeze([...state.steps]) });
|
|
813
|
+
return program;
|
|
814
|
+
}
|
|
815
|
+
function inputSnapshot(params) {
|
|
816
|
+
const value = params.value === undefined && params.keys.length === 0 ? Object.create(null) : params.value;
|
|
817
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
818
|
+
invalid2("Reusable SQL parameters require an exact plain record");
|
|
819
|
+
const prototype = Object.getPrototypeOf(value);
|
|
820
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
821
|
+
invalid2("Reusable SQL parameters require a plain record without inherited inputs");
|
|
822
|
+
const own = Reflect.ownKeys(value);
|
|
823
|
+
if (own.length !== params.keys.length || own.some((key) => typeof key !== "string" || !params.keys.includes(key)))
|
|
824
|
+
invalid2("Reusable SQL parameters must contain exactly the declared names");
|
|
825
|
+
const descriptors = params.keys.map((key) => {
|
|
826
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
827
|
+
if (descriptor === undefined || !("value" in descriptor) || descriptor.value === undefined)
|
|
828
|
+
invalid2(`SQL input "${key}" must be an own defined data property`);
|
|
829
|
+
return { key, value: descriptor.value };
|
|
830
|
+
});
|
|
831
|
+
const snapshot = Object.create(null);
|
|
832
|
+
for (const entry of descriptors)
|
|
833
|
+
snapshot[entry.key] = snapshotSqlValue(entry.value, new Set, params.isHandle);
|
|
834
|
+
return snapshot;
|
|
835
|
+
}
|
|
836
|
+
function sameTransport(left, right) {
|
|
837
|
+
if (left.kind !== right.kind)
|
|
838
|
+
return false;
|
|
839
|
+
if (left.kind === "native-enum-array" && right.kind === "native-enum-array")
|
|
840
|
+
return left.type.namespace === right.type.namespace && left.type.name === right.type.name;
|
|
841
|
+
if (left.kind === "json-list" && right.kind === "json-list")
|
|
842
|
+
return left.storage === right.storage && left.whole === right.whole;
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
function bindSqlBindingProgram(params) {
|
|
846
|
+
const state = programs.get(params.program) ?? invalid2("Expected an authentic SQL binding program");
|
|
847
|
+
const inputs = inputSnapshot({ keys: state.keys, value: params.params, isHandle: params.isHandle });
|
|
848
|
+
const encodedInputs = new Map;
|
|
849
|
+
const values = [];
|
|
850
|
+
for (const step of state.steps) {
|
|
851
|
+
if (step.kind === "check") {
|
|
852
|
+
step.check();
|
|
853
|
+
continue;
|
|
524
854
|
}
|
|
525
|
-
|
|
526
|
-
|
|
855
|
+
const value = step.source.kind === "literal" ? step.source.value : inputs[step.key];
|
|
856
|
+
step.validate?.(value);
|
|
857
|
+
if (step.rejectNull && value === null)
|
|
858
|
+
invalid2("SQL comparison and membership inputs cannot be SQL NULL; use isNull");
|
|
859
|
+
let encoded;
|
|
860
|
+
if (step.key !== undefined && encodedInputs.has(step.key))
|
|
861
|
+
encoded = encodedInputs.get(step.key);
|
|
862
|
+
else {
|
|
863
|
+
encoded = snapshotSqlValue(step.encode({ value: snapshotSqlValue(value), dialect: state.dialect }));
|
|
864
|
+
if (step.key !== undefined)
|
|
865
|
+
encodedInputs.set(step.key, encoded);
|
|
866
|
+
}
|
|
867
|
+
step.validate?.(encoded);
|
|
868
|
+
if (step.rejectNull && encoded === null)
|
|
869
|
+
invalid2("SQL comparison and membership encoders cannot produce SQL NULL; use isNull");
|
|
870
|
+
if (encoded !== null && !sameTransport(step.transport, sqlParameterTransport({ value: encoded })))
|
|
871
|
+
unsupported("A reusable SQL encoder produced an undeclared parameter transport shape");
|
|
872
|
+
values.push(snapshotSqlValue(sqlParameterWireValue({ value: encoded })));
|
|
527
873
|
}
|
|
528
|
-
return
|
|
874
|
+
return values;
|
|
529
875
|
}
|
|
530
876
|
|
|
531
|
-
// packages/sql/src/
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
877
|
+
// packages/sql/src/params.ts
|
|
878
|
+
class ParamCollector {
|
|
879
|
+
dialect;
|
|
880
|
+
reserved = 0;
|
|
881
|
+
values;
|
|
882
|
+
constructor(dialect) {
|
|
883
|
+
this.dialect = dialect;
|
|
884
|
+
this.values = [];
|
|
885
|
+
}
|
|
886
|
+
reserve() {
|
|
887
|
+
const index = isSqlBindingCollector({ collector: this }) ? ++this.reserved : this.values.length;
|
|
888
|
+
return this.dialect.placeholder({ index });
|
|
889
|
+
}
|
|
890
|
+
count() {
|
|
891
|
+
return isSqlBindingCollector({ collector: this }) ? this.reserved : this.values.length;
|
|
892
|
+
}
|
|
893
|
+
add(value) {
|
|
894
|
+
const transport = sqlParameterTransport({ value });
|
|
895
|
+
if (isSqlBindingCollector({ collector: this }))
|
|
896
|
+
return recordSqlBinding({
|
|
897
|
+
collector: this,
|
|
898
|
+
source: { kind: "literal", value },
|
|
899
|
+
transport,
|
|
900
|
+
encode: (params) => params.value
|
|
544
901
|
});
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
902
|
+
if (transport.kind !== "plain" && !this.dialect.capabilities.scalarArrays)
|
|
903
|
+
throw unsupportedCapability3({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
904
|
+
this.values.push(sqlParameterWireValue({ value }));
|
|
905
|
+
return renderSqlParameterTransport({ dialect: this.dialect, placeholder: this.reserve(), transport });
|
|
906
|
+
}
|
|
907
|
+
addMany(values) {
|
|
908
|
+
return values.map((value) => this.add(value));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// packages/sql/src/dialects/external-dependents.ts
|
|
913
|
+
function renderExternalDependents(params) {
|
|
914
|
+
if (params.dialect.name !== "postgres")
|
|
915
|
+
throw unsupportedCapability4({ dialect: params.dialect.name, feature: "externalObjects" });
|
|
916
|
+
const collector = new ParamCollector(params.dialect);
|
|
917
|
+
const requests = params.targets.map((target, position) => ({ position, kind: target.kind, namespace: target.namespace, name: target.name, column: target.kind === "column" ? target.column : null }));
|
|
918
|
+
const request = collector.add(JSON.stringify(requests));
|
|
919
|
+
const text = `WITH request AS (
|
|
920
|
+
SELECT * FROM jsonb_to_recordset(${request}::text::jsonb) AS request(position integer, kind text, namespace text, name text, "column" text)
|
|
921
|
+
), target AS (
|
|
922
|
+
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'
|
|
923
|
+
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
|
|
924
|
+
), owned AS (
|
|
925
|
+
SELECT position, classid, objid, objsubid FROM target
|
|
926
|
+
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
|
|
927
|
+
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'
|
|
928
|
+
), referenced AS (
|
|
929
|
+
SELECT position, classid, objid, objsubid FROM owned
|
|
930
|
+
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
|
|
931
|
+
)
|
|
932
|
+
SELECT DISTINCT referenced.position, dependent.type AS "dependentType", dependent.identity AS "dependentIdentity", ref.identity AS referenced, d.classid::regclass::text AS "dependentClass",
|
|
933
|
+
rn.nspname::text AS "relationNamespace", rel.relname::text AS "relationName", rel.relkind::text AS "relationKind", col.attname::text AS "columnName",
|
|
934
|
+
COALESCE(pn.nspname, tn.nspname, dependent.schema)::text AS "objectNamespace", COALESCE(p.proname, ty.typname)::text AS "objectName"
|
|
935
|
+
FROM referenced
|
|
936
|
+
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'
|
|
937
|
+
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) dependent
|
|
938
|
+
CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.refclassid, d.refobjid, d.refobjsubid) ref
|
|
939
|
+
LEFT JOIN pg_catalog.pg_class self ON d.classid = 'pg_class'::regclass AND self.oid = d.objid
|
|
940
|
+
LEFT JOIN pg_catalog.pg_index ix ON ix.indexrelid = self.oid
|
|
941
|
+
LEFT JOIN pg_catalog.pg_attrdef ad ON d.classid = 'pg_attrdef'::regclass AND ad.oid = d.objid
|
|
942
|
+
LEFT JOIN pg_catalog.pg_rewrite rw ON d.classid = 'pg_rewrite'::regclass AND rw.oid = d.objid
|
|
943
|
+
LEFT JOIN pg_catalog.pg_trigger tg ON d.classid = 'pg_trigger'::regclass AND tg.oid = d.objid
|
|
944
|
+
LEFT JOIN pg_catalog.pg_constraint con ON d.classid = 'pg_constraint'::regclass AND con.oid = d.objid
|
|
945
|
+
LEFT JOIN pg_catalog.pg_policy pol ON d.classid = 'pg_policy'::regclass AND pol.oid = d.objid
|
|
946
|
+
LEFT JOIN pg_catalog.pg_type ty ON d.classid = 'pg_type'::regclass AND ty.oid = d.objid
|
|
947
|
+
LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = ty.typnamespace
|
|
948
|
+
LEFT JOIN pg_catalog.pg_proc p ON d.classid = 'pg_proc'::regclass AND p.oid = d.objid
|
|
949
|
+
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
950
|
+
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))
|
|
951
|
+
LEFT JOIN pg_catalog.pg_namespace rn ON rn.oid = rel.relnamespace
|
|
952
|
+
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
|
|
953
|
+
ORDER BY 1, 2, 3, 4`;
|
|
954
|
+
return { text, values: collector.values, returning: "none" };
|
|
955
|
+
}
|
|
956
|
+
// packages/sql/src/dialects/postgres-physical-catalog.ts
|
|
957
|
+
import { unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
958
|
+
function renderPostgresPhysicalCatalog(params) {
|
|
959
|
+
const { dialect } = params;
|
|
960
|
+
if (dialect.name !== "postgres")
|
|
961
|
+
throw unsupportedCapability5({ dialect: dialect.name, feature: "postgresPhysicalCatalog" });
|
|
962
|
+
const collector = new ParamCollector(dialect);
|
|
963
|
+
const namespace = collector.add(params.namespace);
|
|
964
|
+
const extensionOwnedFilter = `NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON c.oid = d.objid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE d.classid = 'pg_class'::regclass AND d.deptype = 'e' AND n.nspname = ${namespace} AND c.relname = table_name)`;
|
|
965
|
+
let text;
|
|
966
|
+
switch (params.query) {
|
|
967
|
+
case "tables":
|
|
968
|
+
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`;
|
|
969
|
+
break;
|
|
970
|
+
case "views":
|
|
971
|
+
text = `SELECT table_name FROM information_schema.views WHERE table_schema = ${namespace} AND ${extensionOwnedFilter} ORDER BY table_name`;
|
|
972
|
+
break;
|
|
973
|
+
case "columns":
|
|
974
|
+
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`;
|
|
975
|
+
break;
|
|
976
|
+
case "constraints":
|
|
977
|
+
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`;
|
|
978
|
+
break;
|
|
979
|
+
case "checks":
|
|
980
|
+
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`;
|
|
981
|
+
break;
|
|
982
|
+
case "foreignKeys":
|
|
983
|
+
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`;
|
|
984
|
+
break;
|
|
985
|
+
case "enums":
|
|
986
|
+
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`;
|
|
987
|
+
break;
|
|
988
|
+
case "indexes":
|
|
989
|
+
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`;
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
return { text, values: collector.values, returning: "none" };
|
|
993
|
+
}
|
|
994
|
+
// packages/sql/src/dialects/postgres-index-keys.ts
|
|
995
|
+
import { unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
996
|
+
function renderPostgresIndexKeys(params) {
|
|
997
|
+
const { dialect } = params;
|
|
998
|
+
if (dialect.name !== "postgres")
|
|
999
|
+
throw unsupportedCapability6({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
1000
|
+
const collector = new ParamCollector(dialect);
|
|
1001
|
+
const scope = params.table !== undefined ? `n.nspname = ${collector.add(params.table.namespace)} AND c.relname = ${collector.add(params.table.name)}` : params.namespaces === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : params.namespaces.length === 0 ? "false" : `n.nspname IN (${[...new Set(params.namespaces)].map((namespace) => collector.add(namespace)).join(", ")})`;
|
|
1002
|
+
const defaultResolution = `LEFT JOIN LATERAL (WITH RECURSIVE input_types AS (SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t WHERE t.oid = a.atttypid UNION ALL SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t JOIN input_types previous ON t.oid = previous.typbasetype WHERE previous.typtype = 'd'), candidates AS (SELECT candidate.oid, CASE WHEN candidate.opcintype = input.oid THEN 0 WHEN class_input.typispreferred AND class_input.typcategory = input.typcategory THEN 1 ELSE 2 END AS priority FROM pg_catalog.pg_opclass candidate JOIN pg_catalog.pg_type class_input ON class_input.oid = candidate.opcintype CROSS JOIN input_types input WHERE input.typtype <> 'd' AND candidate.opcdefault AND candidate.opcmethod = idx.relam AND (candidate.opcintype = input.oid OR EXISTS (SELECT 1 FROM pg_catalog.pg_cast cast_entry WHERE cast_entry.castsource = input.oid AND cast_entry.casttarget = candidate.opcintype AND cast_entry.castmethod = 'b' AND cast_entry.castcontext = 'i') OR input.typtype = 'e' AND candidate.opcintype = 'pg_catalog.anyenum'::regtype OR input.typelem <> 0 AND candidate.opcintype = 'pg_catalog.anyarray'::regtype)) SELECT (array_agg(candidate.oid))[1] AS oid FROM candidates candidate WHERE candidate.priority = (SELECT min(priority) FROM candidates) HAVING count(*) = 1) resolved_default ON true`;
|
|
1003
|
+
return {
|
|
1004
|
+
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`,
|
|
1005
|
+
values: collector.values,
|
|
1006
|
+
returning: "none"
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
// packages/sql/src/index-columns.ts
|
|
1010
|
+
import { VibeError as VibeError6, isSqlObjectIdentity, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
1011
|
+
function renderIndexColumns(params) {
|
|
1012
|
+
const { dialect, columns, unique } = params;
|
|
1013
|
+
const hasOperatorClass = columns.some((column) => column.operatorClass !== undefined);
|
|
1014
|
+
if (hasOperatorClass && !dialect.capabilities.indexOperatorClasses)
|
|
1015
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
1016
|
+
if (hasOperatorClass && unique)
|
|
1017
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "uniqueIndexOperatorClasses", hint: "custom unique operator classes can change client selector equality" });
|
|
1018
|
+
return columns.map((column) => {
|
|
1019
|
+
const operatorClass = column.operatorClass;
|
|
1020
|
+
if (operatorClass !== undefined && !isSqlObjectIdentity({ kind: "table", name: operatorClass })) {
|
|
1021
|
+
throw new VibeError6({ code: "VIBE_SCHEMA_INVALID", message: "Index operator class must be a qualified SQL name { namespace, name }", meta: { rule: "index-operator-class-identity", column: column.name } });
|
|
550
1022
|
}
|
|
1023
|
+
const ops = operatorClass === undefined ? "" : ` ${dialect.quoteIdent({ name: operatorClass.namespace })}.${dialect.quoteIdent({ name: operatorClass.name })}`;
|
|
1024
|
+
return `${dialect.quoteIdent({ name: column.name })}${ops}${column.desc ? " DESC" : ""}`;
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
// packages/sql/src/dialects/module-dependency.ts
|
|
1028
|
+
function renderModuleDependencyQuery(params) {
|
|
1029
|
+
const { dialect, operation } = params;
|
|
1030
|
+
if (operation.kind === "visibility")
|
|
1031
|
+
return dialect.name === "mysql" ? { text: "SHOW GRANTS FOR CURRENT_USER", values: [], returning: "none" } : undefined;
|
|
1032
|
+
if (dialect.name === "sqlite" || operation.tables.length === 0)
|
|
1033
|
+
return;
|
|
1034
|
+
const collector = new ParamCollector(dialect);
|
|
1035
|
+
const owned = operation.tables.map((table, index) => `SELECT ${collector.add(operation.namespace)}${index === 0 ? " AS namespace" : ""}, ${collector.add(table)}${index === 0 ? " AS table_name" : ""}`).join(" UNION ALL ");
|
|
1036
|
+
const pg = dialect.name === "postgres";
|
|
1037
|
+
const nameKey = ({ column }) => pg ? column : `CAST(IF(@@lower_case_table_names = 0, ${column}, LOWER(${column})) AS BINARY)`;
|
|
1038
|
+
const isOwned = ({ namespace, table }) => `EXISTS (SELECT 1 FROM owned_tables o WHERE ${nameKey({ column: namespace })} = ${nameKey({ column: "o.namespace" })} AND ${nameKey({ column: table })} = ${nameKey({ column: "o.table_name" })})`;
|
|
1039
|
+
const sourceNamespace = pg ? "sn.nspname" : "fk.CONSTRAINT_SCHEMA";
|
|
1040
|
+
const sourceTable = pg ? "s.relname" : "fk.TABLE_NAME";
|
|
1041
|
+
const targetNamespace = pg ? "tn.nspname" : "fk.UNIQUE_CONSTRAINT_SCHEMA";
|
|
1042
|
+
const targetTable = pg ? "t.relname" : "fk.REFERENCED_TABLE_NAME";
|
|
1043
|
+
const from = pg ? "pg_catalog.pg_constraint fk JOIN pg_catalog.pg_class s ON s.oid = fk.conrelid JOIN pg_catalog.pg_namespace sn ON sn.oid = s.relnamespace JOIN pg_catalog.pg_class t ON t.oid = fk.confrelid JOIN pg_catalog.pg_namespace tn ON tn.oid = t.relnamespace WHERE fk.contype = 'f'" : "information_schema.REFERENTIAL_CONSTRAINTS fk";
|
|
1044
|
+
return {
|
|
1045
|
+
text: `WITH owned_tables AS (${owned})
|
|
1046
|
+
SELECT * FROM (SELECT ${pg ? "fk.conname" : "fk.CONSTRAINT_NAME"} AS constraint_name, ${sourceNamespace} AS source_namespace, ${sourceTable} AS source_table, ${targetNamespace} AS target_namespace, ${targetTable} AS target_table, ${isOwned({ namespace: sourceNamespace, table: sourceTable })} AS source_owned, ${isOwned({ namespace: targetNamespace, table: targetTable })} AS target_owned FROM ${from}) dependencies
|
|
1047
|
+
WHERE source_owned OR target_owned
|
|
1048
|
+
ORDER BY source_namespace, source_table, constraint_name`,
|
|
1049
|
+
values: collector.values,
|
|
1050
|
+
returning: "none"
|
|
551
1051
|
};
|
|
552
1052
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1053
|
+
// packages/sql/src/dialects/migration-replay-coverage.ts
|
|
1054
|
+
var extensionMember = (classid, objid) => `EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = '${classid}'::regclass AND d.objid = ${objid} AND d.deptype = 'e')`;
|
|
1055
|
+
var extensionOwnedNamespace = `(EXISTS (SELECT 1 FROM pg_catalog.pg_extension e WHERE e.extnamespace = n.oid) OR ${extensionMember("pg_namespace", "n.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.relnamespace = n.oid AND c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S') AND NOT ${extensionMember("pg_class", "c.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_proc p WHERE p.pronamespace = n.oid AND NOT ${extensionMember("pg_proc", "p.oid")}) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type t WHERE t.typnamespace = n.oid AND (t.typrelid = 0 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 ${extensionMember("pg_type", "t.oid")})`;
|
|
1056
|
+
function renderReplayCoverageGaps(params) {
|
|
1057
|
+
const { dialect } = params;
|
|
1058
|
+
const collector = new ParamCollector(dialect);
|
|
1059
|
+
const namespace = collector.add(params.namespace);
|
|
1060
|
+
if (dialect.name === "postgres")
|
|
1061
|
+
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')
|
|
1062
|
+
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')
|
|
1063
|
+
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')
|
|
1064
|
+
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")}
|
|
1065
|
+
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" };
|
|
1066
|
+
if (dialect.name === "mysql")
|
|
1067
|
+
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'
|
|
1068
|
+
UNION ALL SELECT 'event', EVENT_NAME, EVENT_SCHEMA, NULL FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ${collector.add(params.namespace)}
|
|
1069
|
+
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" };
|
|
1070
|
+
return { text: `SELECT 'table-options' AS kind, name, schema AS namespace, NULL AS parent FROM pragma_table_list WHERE schema = ${namespace} AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND (type = 'virtual' OR wr <> 0 OR strict <> 0)`, values: collector.values, returning: "none" };
|
|
564
1071
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
var
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
1072
|
+
// packages/sql/src/dialects/migration-shadow.ts
|
|
1073
|
+
import { assertSqlNamespace, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
1074
|
+
var SHADOW_DATABASE = Object.freeze({ schema: "_vibeorm_shadow", table: "_vibe_shadow", lock: 1447645267, timeoutSeconds: 60 });
|
|
1075
|
+
var SHADOW_CAPABILITIES = Object.freeze({
|
|
1076
|
+
postgres: Object.freeze({ serverProvisioning: true, dedicated: true }),
|
|
1077
|
+
mysql: Object.freeze({ serverProvisioning: true, dedicated: true }),
|
|
1078
|
+
sqlite: Object.freeze({ serverProvisioning: false, dedicated: false })
|
|
1079
|
+
});
|
|
1080
|
+
function renderShadowOperation(params) {
|
|
1081
|
+
const { dialect, operation } = params;
|
|
1082
|
+
if (!SHADOW_CAPABILITIES[dialect.name].serverProvisioning)
|
|
1083
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "shadowDatabaseProvisioning" });
|
|
1084
|
+
const pg = dialect.name === "postgres";
|
|
1085
|
+
const collector = new ParamCollector(dialect);
|
|
1086
|
+
const marker = pg ? `${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}.${dialect.quoteIdent({ name: SHADOW_DATABASE.table })}` : dialect.quoteIdent({ name: SHADOW_DATABASE.table });
|
|
1087
|
+
const result = (text) => [{ text, values: collector.values, returning: "none" }];
|
|
1088
|
+
switch (operation.kind) {
|
|
1089
|
+
case "createDatabase":
|
|
1090
|
+
return result(`CREATE DATABASE ${dialect.quoteIdent({ name: operation.name })}${pg ? " TEMPLATE template0" : ""}`);
|
|
1091
|
+
case "dropDatabase":
|
|
1092
|
+
return result(`DROP DATABASE ${dialect.quoteIdent({ name: operation.name })}`);
|
|
1093
|
+
case "identity":
|
|
1094
|
+
return result(pg ? `SELECT current_database()::text AS database, (SELECT oid::text FROM pg_catalog.pg_database WHERE datname = current_database()) AS database_id, current_schema()::text AS namespace` : `SELECT DATABASE() AS \`database\`, @@lower_case_table_names AS name_folding, @@server_uuid AS server_id`);
|
|
1095
|
+
case "lock":
|
|
1096
|
+
return result(pg ? `SELECT pg_catalog.pg_advisory_lock(${collector.add(SHADOW_DATABASE.lock)})` : `SELECT GET_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}, ${collector.add(SHADOW_DATABASE.timeoutSeconds)}) AS acquired`);
|
|
1097
|
+
case "unlock":
|
|
1098
|
+
return result(pg ? `SELECT pg_catalog.pg_advisory_unlock(${collector.add(SHADOW_DATABASE.lock)}) AS released` : `SELECT RELEASE_LOCK(${collector.add(`vibeorm_shadow:${operation.database}`)}) AS released`);
|
|
1099
|
+
case "markerExists":
|
|
1100
|
+
return result(pg ? `SELECT c.relname AS name FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ${collector.add(SHADOW_DATABASE.schema)} AND c.relname = ${collector.add(SHADOW_DATABASE.table)}` : `SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)}`);
|
|
1101
|
+
case "markerRead":
|
|
1102
|
+
return result(`SELECT payload FROM ${marker}`);
|
|
1103
|
+
case "markerColumns":
|
|
1104
|
+
return result(`SELECT column_name AS name, data_type AS type, is_nullable AS nullable FROM information_schema.columns AS marker_column WHERE table_schema = ${pg ? collector.add(SHADOW_DATABASE.schema) : "DATABASE()"} AND table_name = ${collector.add(SHADOW_DATABASE.table)} AND ${pg ? `EXISTS (SELECT 1 FROM pg_catalog.pg_class r JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace WHERE n.nspname = marker_column.table_schema AND r.relname = marker_column.table_name AND r.relkind = 'r' AND r.relpersistence = 'p' AND NOT r.relrowsecurity AND NOT r.relforcerowsecurity AND NOT r.relhasrules AND NOT r.relhastriggers AND NOT r.relispartition AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attrdef a WHERE a.adrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_constraint c WHERE c.conrelid = r.oid AND c.contype <> 'n') AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_index i WHERE i.indrelid = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = r.oid OR i.inhparent = r.oid) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_attribute a WHERE a.attrelid = r.oid AND (a.attisdropped OR a.attidentity <> '' OR a.attgenerated <> '')))` : `EXISTS (SELECT 1 FROM information_schema.TABLES t WHERE t.TABLE_SCHEMA = marker_column.table_schema AND t.TABLE_NAME = marker_column.table_name AND t.TABLE_TYPE = 'BASE TABLE' AND t.ENGINE = 'InnoDB') AND NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS c WHERE c.TABLE_SCHEMA = marker_column.table_schema AND c.TABLE_NAME = marker_column.table_name AND (c.COLUMN_DEFAULT IS NOT NULL OR c.EXTRA <> '')) AND NOT EXISTS (SELECT 1 FROM information_schema.TRIGGERS t WHERE t.EVENT_OBJECT_SCHEMA = DATABASE() AND t.EVENT_OBJECT_TABLE = ${collector.add(SHADOW_DATABASE.table)}) AND NOT EXISTS (SELECT 1 FROM information_schema.TABLE_CONSTRAINTS c WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = ${collector.add(SHADOW_DATABASE.table)})`}
|
|
1105
|
+
ORDER BY ordinal_position`);
|
|
1106
|
+
case "grants": {
|
|
1107
|
+
if (pg)
|
|
1108
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowPrivileges" });
|
|
1109
|
+
return result("SHOW GRANTS FOR CURRENT_USER");
|
|
1110
|
+
}
|
|
1111
|
+
case "markerCreate":
|
|
1112
|
+
return pg ? [{ text: `CREATE SCHEMA ${dialect.quoteIdent({ name: SHADOW_DATABASE.schema })}`, values: [], returning: "none" }, { text: `CREATE TABLE ${marker} (payload text NOT NULL)`, values: [], returning: "none" }] : result(`CREATE TABLE ${marker} (payload LONGTEXT NOT NULL) ENGINE=InnoDB`);
|
|
1113
|
+
case "markerWrite":
|
|
1114
|
+
return result(operation.insert ? `INSERT INTO ${marker} (payload) VALUES (${collector.add(operation.value)})` : `UPDATE ${marker} SET payload = ${collector.add(operation.value)}`);
|
|
1115
|
+
case "journal":
|
|
1116
|
+
return result(`SELECT name, checksum, statement_index, error FROM ${dialect.quoteIdent({ name: "_vibe_migrations" })} ORDER BY name`);
|
|
1117
|
+
case "inventory":
|
|
1118
|
+
return result(pg ? `SELECT 'namespace' AS kind, nspname::text AS name, nspname::text AS namespace FROM pg_catalog.pg_namespace WHERE nspname NOT IN ('public', 'information_schema') AND nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
|
|
1119
|
+
UNION ALL SELECT 'relation', c.relname::text, n.nspname::text FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
|
|
1120
|
+
UNION ALL SELECT 'routine', p.proname::text, n.nspname::text FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'
|
|
1121
|
+
UNION ALL SELECT 'type', t.typname::text, n.nspname::text FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND t.typrelid = 0 AND t.typelem = 0
|
|
1122
|
+
UNION ALL SELECT 'extension', e.extname::text, n.nspname::text FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE e.extname <> 'plpgsql'
|
|
1123
|
+
UNION ALL SELECT 'marker-dependency', d.objid::text, '${SHADOW_DATABASE.schema}' FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_class c ON d.classid = 'pg_class'::regclass AND d.objid = c.oid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}' AND d.deptype NOT IN ('i', 'a') AND d.refclassid <> 'pg_namespace'::regclass
|
|
1124
|
+
UNION ALL SELECT 'marker-trigger', t.tgname::text, n.nspname::text FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '${SHADOW_DATABASE.schema}'
|
|
1125
|
+
UNION ALL SELECT 'namespace-object', o.name::text, n.nspname::text FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_namespace n ON d.refclassid = 'pg_namespace'::regclass AND d.refobjid = n.oid CROSS JOIN LATERAL pg_catalog.pg_identify_object(d.classid, d.objid, d.objsubid) o WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\' AND d.classid NOT IN ('pg_class'::regclass, 'pg_proc'::regclass, 'pg_type'::regclass, 'pg_extension'::regclass)
|
|
1126
|
+
UNION ALL SELECT 'database-object', evtname::text, '' FROM pg_catalog.pg_event_trigger
|
|
1127
|
+
UNION ALL SELECT 'database-object', pubname::text, '' FROM pg_catalog.pg_publication
|
|
1128
|
+
UNION ALL SELECT 'database-object', subname::text, '' FROM pg_catalog.pg_subscription WHERE subdbid = (SELECT oid FROM pg_catalog.pg_database WHERE datname = current_database())
|
|
1129
|
+
UNION ALL SELECT 'database-object', srvname::text, '' FROM pg_catalog.pg_foreign_server
|
|
1130
|
+
UNION ALL SELECT 'database-object', fdwname::text, '' FROM pg_catalog.pg_foreign_data_wrapper
|
|
1131
|
+
UNION ALL SELECT 'database-object', c.oid::text, '' FROM pg_catalog.pg_cast c JOIN pg_catalog.pg_type s ON s.oid = c.castsource JOIN pg_catalog.pg_type t ON t.oid = c.casttarget LEFT JOIN pg_catalog.pg_proc p ON p.oid = c.castfunc WHERE s.typnamespace <> 'pg_catalog'::regnamespace OR t.typnamespace <> 'pg_catalog'::regnamespace OR p.pronamespace <> 'pg_catalog'::regnamespace` : `SELECT TABLE_TYPE AS kind, TABLE_NAME AS name, TABLE_SCHEMA AS namespace FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()
|
|
1132
|
+
UNION ALL SELECT ROUTINE_TYPE, ROUTINE_NAME, ROUTINE_SCHEMA FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()
|
|
1133
|
+
UNION ALL SELECT 'EVENT', EVENT_NAME, EVENT_SCHEMA FROM information_schema.EVENTS WHERE EVENT_SCHEMA = DATABASE()
|
|
1134
|
+
UNION ALL SELECT 'TRIGGER', TRIGGER_NAME, TRIGGER_SCHEMA FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = DATABASE()
|
|
1135
|
+
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)`);
|
|
1136
|
+
case "resetPostgres":
|
|
1137
|
+
case "resetDedicatedPostgres": {
|
|
1138
|
+
if (!pg)
|
|
1139
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "postgresShadowReset" });
|
|
1140
|
+
const owned = operation.kind === "resetDedicatedPostgres" ? operation.ownedNamespaces : [];
|
|
1141
|
+
for (const namespace of owned)
|
|
1142
|
+
assertSqlNamespace({ namespace, dialect: "postgres", allowPublic: false });
|
|
1143
|
+
return [
|
|
1144
|
+
...owned.map((namespace) => ({ text: `DROP SCHEMA IF EXISTS ${dialect.quoteIdent({ name: namespace })} CASCADE`, values: [], returning: "none" })),
|
|
1145
|
+
{ text: 'DROP SCHEMA "public" CASCADE', values: [], returning: "none" },
|
|
1146
|
+
{ text: 'CREATE SCHEMA "public"', values: [], returning: "none" }
|
|
1147
|
+
];
|
|
1148
|
+
}
|
|
1149
|
+
case "mysqlForeignKeys": {
|
|
1150
|
+
if (pg)
|
|
1151
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
1152
|
+
return result(`SET FOREIGN_KEY_CHECKS = ${operation.enabled ? "1" : "0"}`);
|
|
1153
|
+
}
|
|
1154
|
+
case "dropMysqlObject": {
|
|
1155
|
+
if (pg)
|
|
1156
|
+
throw unsupportedCapability8({ dialect: dialect.name, feature: "mysqlShadowReset" });
|
|
1157
|
+
return result(`DROP ${operation.object} ${dialect.quoteIdent({ name: operation.name })}`);
|
|
1158
|
+
}
|
|
581
1159
|
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1160
|
+
}
|
|
1161
|
+
// packages/sql/src/module-journal.ts
|
|
1162
|
+
var MODULE_JOURNAL_COLUMN = "module_origin";
|
|
1163
|
+
function renderModuleJournal(params) {
|
|
1164
|
+
const { dialect, operation } = params;
|
|
1165
|
+
const q = (name) => dialect.quoteIdent({ name });
|
|
1166
|
+
const table = q(params.table);
|
|
1167
|
+
const column = q(MODULE_JOURNAL_COLUMN);
|
|
1168
|
+
const collector = new ParamCollector(dialect);
|
|
1169
|
+
let text;
|
|
1170
|
+
switch (operation.kind) {
|
|
1171
|
+
case "columns":
|
|
1172
|
+
text = dialect.name === "sqlite" ? `PRAGMA table_info(${table})` : `SELECT column_name AS name FROM information_schema.columns WHERE table_schema = ${dialect.name === "postgres" ? "current_schema()" : "DATABASE()"} AND table_name = ${collector.add(params.table)}`;
|
|
1173
|
+
break;
|
|
1174
|
+
case "addColumn":
|
|
1175
|
+
text = `ALTER TABLE ${table} ADD COLUMN ${column} ${dialect.name === "mysql" ? "LONGTEXT" : "TEXT"}`;
|
|
1176
|
+
break;
|
|
1177
|
+
case "read":
|
|
1178
|
+
text = `SELECT * FROM ${table} ORDER BY ${q("id")}`;
|
|
1179
|
+
break;
|
|
1180
|
+
case "insert":
|
|
1181
|
+
text = `INSERT INTO ${table} (${["name", "checksum", "statement_index", MODULE_JOURNAL_COLUMN].map(q).join(", ")}) VALUES (${collector.addMany([operation.name, operation.checksum, operation.index, operation.entry]).join(", ")})`;
|
|
1182
|
+
break;
|
|
1183
|
+
case "complete":
|
|
1184
|
+
text = `UPDATE ${table} SET ${column} = ${collector.add(operation.entry)} WHERE ${q("name")} = ${collector.add(operation.name)}`;
|
|
1185
|
+
break;
|
|
594
1186
|
}
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
1187
|
+
return { text, values: collector.values };
|
|
1188
|
+
}
|
|
1189
|
+
// packages/sql/src/capabilities.ts
|
|
1190
|
+
import {
|
|
1191
|
+
DIALECT_CAPABILITIES,
|
|
1192
|
+
POSTGRES_CAPABILITIES,
|
|
1193
|
+
SQLITE_CAPABILITIES,
|
|
1194
|
+
MYSQL_CAPABILITIES
|
|
1195
|
+
} from "@vibeorm/schema";
|
|
1196
|
+
|
|
1197
|
+
// packages/sql/src/identifiers.ts
|
|
1198
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
1199
|
+
function assertNoNul(params) {
|
|
1200
|
+
if (params.name.includes("\x00")) {
|
|
1201
|
+
throw new VibeError7({
|
|
1202
|
+
code: "VIBE_VALIDATION",
|
|
1203
|
+
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
1204
|
+
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
1205
|
+
});
|
|
603
1206
|
}
|
|
604
|
-
|
|
605
|
-
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
1207
|
+
return params.name;
|
|
606
1208
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1209
|
+
|
|
1210
|
+
// packages/sql/src/dialects/sqlite.ts
|
|
1211
|
+
var sqliteDialect = {
|
|
1212
|
+
name: "sqlite",
|
|
1213
|
+
capabilities: SQLITE_CAPABILITIES,
|
|
1214
|
+
inArrayStrategy: "expand",
|
|
1215
|
+
tupleInStrategy: "rowValue",
|
|
1216
|
+
timeOfDayInStrategy: "in",
|
|
1217
|
+
upsertForm: "onConflict",
|
|
1218
|
+
conflictKeyEquality: {
|
|
1219
|
+
foldingScalars: [],
|
|
1220
|
+
textIsBinaryByDefault: true,
|
|
1221
|
+
duplicatesInOneStatement: "lastWriteWins"
|
|
613
1222
|
},
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1223
|
+
excludedRowRef: { form: "keyword", name: "excluded" },
|
|
1224
|
+
doNothingForm: "onConflictDoNothing",
|
|
1225
|
+
insensitiveLike: "lower",
|
|
1226
|
+
strictMatchStrategy: "glob",
|
|
1227
|
+
greatestForm: "frame",
|
|
1228
|
+
orderByNullsMode: "native",
|
|
1229
|
+
offsetRequiresLimit: { limitText: "-1" },
|
|
1230
|
+
likeEscapeClause: " ESCAPE '\\'",
|
|
1231
|
+
jsonPathMode: "sqliteExtract",
|
|
1232
|
+
supportedJsonOps: [
|
|
1233
|
+
"equals",
|
|
1234
|
+
"string_contains",
|
|
1235
|
+
"string_starts_with",
|
|
1236
|
+
"string_ends_with",
|
|
1237
|
+
"array_contains",
|
|
1238
|
+
"array_starts_with",
|
|
1239
|
+
"array_ends_with"
|
|
1240
|
+
],
|
|
1241
|
+
jsonParamCast: "",
|
|
1242
|
+
valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "INTEGER", Int: "INTEGER", BigInt: "INTEGER", Float: "REAL", Decimal: "TEXT", DateTime: "TEXT", Json: "TEXT", Bytes: "BLOB" }),
|
|
1243
|
+
lastInsertIdSql: null,
|
|
1244
|
+
rowLockSyntax: null,
|
|
1245
|
+
advisoryLockFunctions: null,
|
|
1246
|
+
rowDefaultKeyword: false,
|
|
1247
|
+
statementChain: null,
|
|
1248
|
+
paramBudget: 900,
|
|
1249
|
+
booleanLiteral: { true: "1", false: "0" },
|
|
1250
|
+
quoteIdent({ name }) {
|
|
1251
|
+
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
628
1252
|
},
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
var mysqlDateCodec = {
|
|
632
|
-
encode: (value) => {
|
|
633
|
-
dateNativeCodec.encode(value);
|
|
634
|
-
if (value instanceof Date)
|
|
635
|
-
return value;
|
|
636
|
-
if (typeof value === "number") {
|
|
637
|
-
const parsed = new Date(value);
|
|
638
|
-
if (!Number.isNaN(parsed.getTime()))
|
|
639
|
-
return parsed;
|
|
640
|
-
return value;
|
|
641
|
-
}
|
|
642
|
-
if (typeof value === "string")
|
|
643
|
-
return parseDateTimeText({ text: value }) ?? value;
|
|
644
|
-
return value;
|
|
1253
|
+
quoteStringLiteral({ value }) {
|
|
1254
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
645
1255
|
},
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
var jsonCodec = {
|
|
657
|
-
encode: (value) => JSON.stringify(value),
|
|
658
|
-
decode: (value) => {
|
|
659
|
-
if (typeof value !== "string")
|
|
660
|
-
return value;
|
|
661
|
-
try {
|
|
662
|
-
return JSON.parse(value);
|
|
663
|
-
} catch (cause) {
|
|
664
|
-
throw new VibeError3({
|
|
665
|
-
code: "VIBE_INTERNAL",
|
|
666
|
-
message: "a Json column returned text that is not valid JSON",
|
|
667
|
-
meta: { value },
|
|
668
|
-
cause
|
|
669
|
-
});
|
|
670
|
-
}
|
|
1256
|
+
placeholder() {
|
|
1257
|
+
return "?";
|
|
1258
|
+
},
|
|
1259
|
+
jsonTextProjection({ expression }) {
|
|
1260
|
+
return expression;
|
|
1261
|
+
},
|
|
1262
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
1263
|
+
if (offsetSeconds === 0)
|
|
1264
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1265
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
671
1266
|
}
|
|
672
1267
|
};
|
|
673
|
-
var POSTGRES_CODECS = {
|
|
674
|
-
String: passthroughCodec,
|
|
675
|
-
Boolean: passthroughCodec,
|
|
676
|
-
Int: numberCodec,
|
|
677
|
-
BigInt: bigIntCodec,
|
|
678
|
-
Float: numberCodec,
|
|
679
|
-
Decimal: decimalCodec,
|
|
680
|
-
DateTime: dateNativeCodec,
|
|
681
|
-
Json: jsonCodec,
|
|
682
|
-
Bytes: passthroughCodec
|
|
683
|
-
};
|
|
684
|
-
var SQLITE_CODECS = {
|
|
685
|
-
String: passthroughCodec,
|
|
686
|
-
Boolean: booleanIntCodec,
|
|
687
|
-
Int: numberCodec,
|
|
688
|
-
BigInt: bigIntCodec,
|
|
689
|
-
Float: numberCodec,
|
|
690
|
-
Decimal: decimalCodec,
|
|
691
|
-
DateTime: dateTextCodec,
|
|
692
|
-
Json: jsonCodec,
|
|
693
|
-
Bytes: passthroughCodec
|
|
694
|
-
};
|
|
695
|
-
var MYSQL_CODECS = {
|
|
696
|
-
String: passthroughCodec,
|
|
697
|
-
Boolean: booleanNativeInCodec,
|
|
698
|
-
Int: numberCodec,
|
|
699
|
-
BigInt: bigIntCodec,
|
|
700
|
-
Float: numberCodec,
|
|
701
|
-
Decimal: mysqlDecimalCodec,
|
|
702
|
-
DateTime: mysqlDateCodec,
|
|
703
|
-
Json: jsonCodec,
|
|
704
|
-
Bytes: passthroughCodec
|
|
705
|
-
};
|
|
706
|
-
var DIALECT_CODECS = {
|
|
707
|
-
postgres: POSTGRES_CODECS,
|
|
708
|
-
sqlite: SQLITE_CODECS,
|
|
709
|
-
mysql: MYSQL_CODECS
|
|
710
|
-
};
|
|
711
1268
|
|
|
712
|
-
// packages/sql/src/
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1269
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
1270
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
1271
|
+
function tableDefinitionParts(params) {
|
|
1272
|
+
const sql = params.sql;
|
|
1273
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
1274
|
+
throw unavailable();
|
|
1275
|
+
const parts = [];
|
|
1276
|
+
let start = -1;
|
|
1277
|
+
let depth = 0;
|
|
1278
|
+
let quote;
|
|
1279
|
+
for (let index = 0;index < sql.length; index++) {
|
|
1280
|
+
const char = sql[index];
|
|
1281
|
+
if (quote !== undefined) {
|
|
1282
|
+
if (char === quote) {
|
|
1283
|
+
if (quote !== "]" && sql[index + 1] === quote)
|
|
1284
|
+
index++;
|
|
1285
|
+
else
|
|
1286
|
+
quote = undefined;
|
|
1287
|
+
}
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
1291
|
+
quote = char;
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
if (char === "[") {
|
|
1295
|
+
quote = "]";
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
1299
|
+
const end = sql.indexOf(`
|
|
1300
|
+
`, index + 2);
|
|
1301
|
+
if (end < 0)
|
|
1302
|
+
break;
|
|
1303
|
+
index = end;
|
|
1304
|
+
continue;
|
|
723
1305
|
}
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1306
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
1307
|
+
const end = sql.indexOf("*/", index + 2);
|
|
1308
|
+
if (end < 0)
|
|
1309
|
+
break;
|
|
1310
|
+
index = end + 1;
|
|
1311
|
+
continue;
|
|
730
1312
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1313
|
+
if (char === "(") {
|
|
1314
|
+
if (depth === 0)
|
|
1315
|
+
start = index + 1;
|
|
1316
|
+
depth++;
|
|
1317
|
+
} else if (char === ")") {
|
|
1318
|
+
depth--;
|
|
1319
|
+
if (depth === 0 && start >= 0) {
|
|
1320
|
+
parts.push(sql.slice(start, index).trim());
|
|
1321
|
+
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
1322
|
+
}
|
|
1323
|
+
} else if (char === "," && depth === 1) {
|
|
1324
|
+
parts.push(sql.slice(start, index).trim());
|
|
1325
|
+
start = index + 1;
|
|
744
1326
|
}
|
|
745
|
-
}
|
|
1327
|
+
}
|
|
1328
|
+
throw unavailable();
|
|
746
1329
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1330
|
+
function unavailable() {
|
|
1331
|
+
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
1332
|
+
}
|
|
1333
|
+
function sqliteColumnClauses(params) {
|
|
1334
|
+
const columns = [];
|
|
1335
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
1336
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
1337
|
+
if (token === null)
|
|
1338
|
+
throw unavailable();
|
|
1339
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
1340
|
+
continue;
|
|
1341
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
1342
|
+
columns.push({ name, sql: part });
|
|
1343
|
+
}
|
|
1344
|
+
return columns;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// packages/sql/src/dialects/sqlite-fts.ts
|
|
1348
|
+
function updateStatements(params) {
|
|
1349
|
+
const quote = (name) => sqliteDialect.quoteIdent({ name });
|
|
1350
|
+
const columns = params.columns.map((column) => quote(column)).join(", ");
|
|
1351
|
+
const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
1352
|
+
const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
1353
|
+
const changes = [
|
|
1354
|
+
"old.rowid IS NOT new.rowid",
|
|
1355
|
+
...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
|
|
1356
|
+
].join(" OR ");
|
|
1357
|
+
const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
|
|
1358
|
+
const body = `BEGIN
|
|
1359
|
+
INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
|
|
1360
|
+
INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
|
|
1361
|
+
END`;
|
|
1362
|
+
const tail = `${target} WHEN (${changes}) ${body}`;
|
|
1363
|
+
return {
|
|
1364
|
+
create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
|
|
1365
|
+
definition: `CREATE TRIGGER ${tail}`,
|
|
1366
|
+
legacyDefinition: `CREATE TRIGGER ${target} ${body}`
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function renderSqliteFtsUpdateTrigger(params) {
|
|
1370
|
+
const { create, definition } = updateStatements(params);
|
|
1371
|
+
return { create, definition };
|
|
1372
|
+
}
|
|
1373
|
+
function sqliteFtsUpdateUpgrade(params) {
|
|
1374
|
+
const quoted = '"(?:[^"]|"")+"';
|
|
1375
|
+
const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
|
|
1376
|
+
for (const match of params.create.matchAll(body)) {
|
|
1377
|
+
const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
|
|
1378
|
+
const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
|
|
1379
|
+
if (params.name !== `${ftsTable}_au`)
|
|
1380
|
+
continue;
|
|
1381
|
+
const statements = updateStatements({ table: params.table, ftsTable, columns });
|
|
1382
|
+
if (params.create !== statements.create)
|
|
1383
|
+
continue;
|
|
1384
|
+
const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
|
|
1385
|
+
if (installed === undefined)
|
|
1386
|
+
continue;
|
|
1387
|
+
if (params.tableDefinition === undefined)
|
|
1388
|
+
return { kind: "tableRequired", ftsTable };
|
|
1389
|
+
const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
|
|
1390
|
+
if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
|
|
1391
|
+
continue;
|
|
1392
|
+
return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
|
|
764
1393
|
}
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
// packages/sql/src/dialects/sqlite-session.ts
|
|
1397
|
+
var SQLITE_SESSION_SQL = Object.freeze({
|
|
1398
|
+
lockingMode: "PRAGMA main.locking_mode",
|
|
1399
|
+
exclusive: "PRAGMA main.locking_mode = EXCLUSIVE",
|
|
1400
|
+
normal: "PRAGMA main.locking_mode = NORMAL",
|
|
1401
|
+
acquire: "BEGIN EXCLUSIVE",
|
|
1402
|
+
commit: "COMMIT",
|
|
1403
|
+
rollback: "ROLLBACK",
|
|
1404
|
+
touch: "SELECT name FROM main.sqlite_schema LIMIT 1",
|
|
1405
|
+
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
765
1406
|
});
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1407
|
+
// packages/sql/src/composable.ts
|
|
1408
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1409
|
+
var fragments = new WeakMap;
|
|
1410
|
+
function invalid3(message) {
|
|
1411
|
+
throw new VibeError9({ code: "VIBE_VALIDATION", message });
|
|
1412
|
+
}
|
|
1413
|
+
function construct(parts) {
|
|
1414
|
+
const snapshot = Object.freeze([...parts]);
|
|
1415
|
+
const fragment = Object.freeze(Object.defineProperty(Object.create(null), "toJSON", {
|
|
1416
|
+
value: () => ({ parts: snapshot })
|
|
1417
|
+
}));
|
|
1418
|
+
fragments.set(fragment, snapshot);
|
|
1419
|
+
return fragment;
|
|
1420
|
+
}
|
|
1421
|
+
function isSqlFragment(value) {
|
|
1422
|
+
return typeof value === "object" && value !== null && fragments.has(value);
|
|
1423
|
+
}
|
|
1424
|
+
function partsOf(value) {
|
|
1425
|
+
return fragments.get(value) ?? invalid3("Expected a constructed SQL fragment");
|
|
1426
|
+
}
|
|
1427
|
+
function containsGreatest(fragment) {
|
|
1428
|
+
return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
|
|
1429
|
+
}
|
|
1430
|
+
function identifier(parts) {
|
|
1431
|
+
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
1432
|
+
invalid3("SQL identifiers require non-empty, NUL-free name components");
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
function template(strings, ...values) {
|
|
1436
|
+
if (!Array.isArray(strings) || !Array.isArray(strings.raw) || strings.length !== values.length + 1 || strings.some((text) => typeof text !== "string")) {
|
|
1437
|
+
invalid3("Use sql as a template tag");
|
|
1438
|
+
}
|
|
1439
|
+
const parts = [];
|
|
1440
|
+
for (let index = 0;index < strings.length; index++) {
|
|
1441
|
+
parts.push(strings[index]);
|
|
1442
|
+
if (index < values.length) {
|
|
1443
|
+
const value = values[index];
|
|
1444
|
+
if (isSqlFragment(value))
|
|
1445
|
+
parts.push(...partsOf(value));
|
|
1446
|
+
else
|
|
1447
|
+
parts.push(Object.freeze({ value: snapshotSqlValue(value) }));
|
|
793
1448
|
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1449
|
+
}
|
|
1450
|
+
return construct(parts);
|
|
1451
|
+
}
|
|
1452
|
+
var sql = Object.freeze(Object.assign(template, {
|
|
1453
|
+
text: ({ text }) => {
|
|
1454
|
+
if (typeof text !== "string" || text.includes("\x00"))
|
|
1455
|
+
invalid3("SQL text must be a NUL-free string");
|
|
1456
|
+
return construct([text]);
|
|
1457
|
+
},
|
|
1458
|
+
ident: ({ parts }) => {
|
|
1459
|
+
identifier(parts);
|
|
1460
|
+
return construct([Object.freeze({ ident: Object.freeze([...parts]) })]);
|
|
1461
|
+
},
|
|
1462
|
+
value: ({ value, decoder }) => construct([Object.freeze({ value: snapshotSqlValue(value), ...decoder === undefined ? {} : { decoder } })]),
|
|
1463
|
+
join: ({ items, separator }) => {
|
|
1464
|
+
if (!Array.isArray(items))
|
|
1465
|
+
invalid3("sql.join requires an array");
|
|
1466
|
+
const between = separator === undefined ? [", "] : partsOf(separator);
|
|
1467
|
+
const parts = [];
|
|
1468
|
+
items.forEach((value, index) => {
|
|
1469
|
+
if (index > 0)
|
|
1470
|
+
parts.push(...between);
|
|
1471
|
+
parts.push(...isSqlFragment(value) ? partsOf(value) : [Object.freeze({ value: snapshotSqlValue(value) })]);
|
|
800
1472
|
});
|
|
1473
|
+
return construct(parts);
|
|
801
1474
|
},
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
};
|
|
814
|
-
return Object.freeze({ decode: (params) => convert(params, false), encode: (params) => convert(params, true) });
|
|
1475
|
+
existing: ({ column }) => {
|
|
1476
|
+
identifier([column]);
|
|
1477
|
+
return construct([Object.freeze({ row: "existing", column })]);
|
|
1478
|
+
},
|
|
1479
|
+
incoming: ({ column }) => {
|
|
1480
|
+
identifier([column]);
|
|
1481
|
+
return construct([Object.freeze({ row: "incoming", column })]);
|
|
1482
|
+
},
|
|
1483
|
+
greatest: ({ values }) => {
|
|
1484
|
+
if (!Array.isArray(values) || values.length !== 2 || values.some((value) => !isSqlFragment(value)))
|
|
1485
|
+
invalid3("sql.greatest requires two SQL fragments");
|
|
1486
|
+
return construct([Object.freeze({ greatest: Object.freeze([...values]) })]);
|
|
815
1487
|
}
|
|
816
|
-
});
|
|
1488
|
+
}));
|
|
1489
|
+
function renderSqlFragment(params) {
|
|
1490
|
+
const { dialect, collector, conflict } = params;
|
|
1491
|
+
const render = (fragment) => renderSqlFragment({ ...params, fragment });
|
|
1492
|
+
const encode = (part) => {
|
|
1493
|
+
if (part.value === undefined)
|
|
1494
|
+
invalid3("SQL parameters cannot be undefined; use null explicitly");
|
|
1495
|
+
return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
|
|
1496
|
+
};
|
|
1497
|
+
const recording = isSqlBindingCollector({ collector });
|
|
1498
|
+
const recordValue = (part) => recordSqlBinding({
|
|
1499
|
+
collector,
|
|
1500
|
+
source: { kind: "literal", value: part.value },
|
|
1501
|
+
transport: part.decoder === undefined ? sqlParameterTransport({ value: part.value }) : { kind: "plain" },
|
|
1502
|
+
encode: ({ value, dialect: target }) => part.decoder === undefined ? value : part.decoder.encode({ value, dialect: target.name })
|
|
1503
|
+
});
|
|
1504
|
+
const frameOperand = (fragment) => {
|
|
1505
|
+
const parts = partsOf(fragment).filter((part) => part !== "");
|
|
1506
|
+
const only = parts.length === 1 ? parts[0] : undefined;
|
|
1507
|
+
if (recording && only !== undefined && typeof only !== "string" && "value" in only) {
|
|
1508
|
+
if (only.decoder !== undefined && resultScalarContract({ decoder: only.decoder }) === undefined)
|
|
1509
|
+
throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL cannot compile an opaque encoder whose output controls a greatest NULL frame" });
|
|
1510
|
+
if (only.value === null) {
|
|
1511
|
+
if (only.decoder !== undefined)
|
|
1512
|
+
recordSqlBindingCheck({ collector, check: () => {
|
|
1513
|
+
if (encode({ ...only, value: snapshotSqlValue(only.value) }) !== null)
|
|
1514
|
+
throw new VibeError9({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Reusable SQL NULL frame encoding changed its declared structure" });
|
|
1515
|
+
} });
|
|
1516
|
+
return "NULL";
|
|
1517
|
+
}
|
|
1518
|
+
const text = recordValue(only);
|
|
1519
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
1520
|
+
}
|
|
1521
|
+
const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
|
|
1522
|
+
if (value !== undefined && value.encoded === null)
|
|
1523
|
+
return "NULL";
|
|
1524
|
+
const text = value === undefined ? render(fragment) : collector.add(value.encoded);
|
|
1525
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
1526
|
+
};
|
|
1527
|
+
return partsOf(params.fragment).map((part) => {
|
|
1528
|
+
if (typeof part === "string")
|
|
1529
|
+
return part;
|
|
1530
|
+
if ("value" in part)
|
|
1531
|
+
return recording ? recordValue(part) : collector.add(encode(part));
|
|
1532
|
+
if ("ident" in part)
|
|
1533
|
+
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
1534
|
+
if ("row" in part) {
|
|
1535
|
+
if (conflict === undefined)
|
|
1536
|
+
invalid3("Existing/incoming row references require a conflict update");
|
|
1537
|
+
const name = conflict.columns === undefined ? part.column : conflict.columns[part.column];
|
|
1538
|
+
if (name === undefined)
|
|
1539
|
+
invalid3(`Unknown conflict-expression field "${part.column}"`);
|
|
1540
|
+
const ref = dialect.excludedRowRef;
|
|
1541
|
+
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
1542
|
+
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
1543
|
+
}
|
|
1544
|
+
if (dialect.greatestForm === "native")
|
|
1545
|
+
return `GREATEST(${part.greatest.map(render).join(", ")})`;
|
|
1546
|
+
if (!part.greatest.some(containsGreatest)) {
|
|
1547
|
+
return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
|
|
1548
|
+
}
|
|
1549
|
+
const [left, right] = part.greatest.map(frameOperand);
|
|
1550
|
+
const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
|
|
1551
|
+
return `(SELECT CASE WHEN ${a} IS NULL THEN ${b} WHEN ${b} IS NULL THEN ${a} WHEN ${a} >= ${b} THEN ${a} ELSE ${b} END FROM (SELECT ${left} AS ${a}, ${right} AS ${b}) AS ${frame})`;
|
|
1552
|
+
}).join("");
|
|
1553
|
+
}
|
|
1554
|
+
function mapConflictColumns(params) {
|
|
1555
|
+
return construct(partsOf(params.fragment).map((part) => {
|
|
1556
|
+
if (typeof part === "string")
|
|
1557
|
+
return part;
|
|
1558
|
+
if ("row" in part) {
|
|
1559
|
+
const column = Object.hasOwn(params.columns, part.column) ? params.columns[part.column] : undefined;
|
|
1560
|
+
if (column === undefined)
|
|
1561
|
+
invalid3(`Unknown conflict-expression field "${part.column}"`);
|
|
1562
|
+
return Object.freeze({ ...part, column });
|
|
1563
|
+
}
|
|
1564
|
+
if ("greatest" in part)
|
|
1565
|
+
return Object.freeze({ greatest: Object.freeze(part.greatest.map((fragment) => mapConflictColumns({ ...params, fragment }))) });
|
|
1566
|
+
return part;
|
|
1567
|
+
}));
|
|
1568
|
+
}
|
|
1569
|
+
function compileSql(params) {
|
|
1570
|
+
const collector = new ParamCollector(params.dialect);
|
|
1571
|
+
const text = renderSqlFragment({ dialect: params.dialect, fragment: params.query, collector });
|
|
1572
|
+
return { text, values: collector.values };
|
|
1573
|
+
}
|
|
1574
|
+
function sqlUsesIncoming(params) {
|
|
1575
|
+
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
1576
|
+
}
|
|
1577
|
+
// packages/sql/src/query-plan.ts
|
|
1578
|
+
import { VibeError as VibeError10 } from "@vibeorm/schema";
|
|
1579
|
+
var queries = new WeakMap;
|
|
1580
|
+
var commands = new WeakMap;
|
|
1581
|
+
var reusableQueries = new WeakMap;
|
|
1582
|
+
var reusableCommands = new WeakMap;
|
|
1583
|
+
function invalid4(message) {
|
|
1584
|
+
throw new VibeError10({ code: "VIBE_VALIDATION", message });
|
|
1585
|
+
}
|
|
1586
|
+
function createSqlQuery(params) {
|
|
1587
|
+
if (typeof params.render !== "function" || typeof params.decoder?.decode !== "function")
|
|
1588
|
+
invalid4("SQL queries require a renderer and result decoder");
|
|
1589
|
+
const query = Object.freeze(Object.create(null));
|
|
1590
|
+
queries.set(query, Object.freeze({ render: params.render, decoder: params.decoder }));
|
|
1591
|
+
return query;
|
|
1592
|
+
}
|
|
1593
|
+
function createSqlCommand(params) {
|
|
1594
|
+
if (typeof params.render !== "function")
|
|
1595
|
+
invalid4("SQL commands require a renderer");
|
|
1596
|
+
const command = Object.freeze(Object.create(null));
|
|
1597
|
+
commands.set(command, params.render);
|
|
1598
|
+
return command;
|
|
1599
|
+
}
|
|
1600
|
+
function isSqlQuery(value) {
|
|
1601
|
+
return typeof value === "object" && value !== null && queries.has(value);
|
|
1602
|
+
}
|
|
1603
|
+
function isSqlCommand(value) {
|
|
1604
|
+
return typeof value === "object" && value !== null && commands.has(value);
|
|
1605
|
+
}
|
|
1606
|
+
function isReusableSqlQuery(value) {
|
|
1607
|
+
return typeof value === "object" && value !== null && reusableQueries.has(value);
|
|
1608
|
+
}
|
|
1609
|
+
function isReusableSqlCommand(value) {
|
|
1610
|
+
return typeof value === "object" && value !== null && reusableCommands.has(value);
|
|
1611
|
+
}
|
|
1612
|
+
function createReusableSqlQuery(params) {
|
|
1613
|
+
const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
|
|
1614
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
1615
|
+
const query = Object.freeze(Object.create(null));
|
|
1616
|
+
reusableQueries.set(query, { ...state, scope: params.scope, cache: new WeakMap });
|
|
1617
|
+
return query;
|
|
1618
|
+
}
|
|
1619
|
+
function createReusableSqlCommand(params) {
|
|
1620
|
+
const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
|
|
1621
|
+
sqlInputScopeKeys({ scope: params.scope });
|
|
1622
|
+
const query = Object.freeze(Object.create(null));
|
|
1623
|
+
reusableCommands.set(query, { render, scope: params.scope, cache: new WeakMap });
|
|
1624
|
+
return query;
|
|
1625
|
+
}
|
|
1626
|
+
function compiledTemplate(params) {
|
|
1627
|
+
const cached = params.state.cache.get(params.dialect);
|
|
1628
|
+
if (cached !== undefined)
|
|
1629
|
+
return cached;
|
|
1630
|
+
const collector = new ParamCollector(params.dialect);
|
|
1631
|
+
startSqlBindingCollection({ collector, dialect: params.dialect, scope: params.state.scope });
|
|
1632
|
+
const text = params.state.render({ dialect: params.dialect, collector });
|
|
1633
|
+
if (collector.count() > params.dialect.paramBudget)
|
|
1634
|
+
invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1635
|
+
const program = finishSqlBindingCollection({ collector });
|
|
1636
|
+
const compiled = Object.freeze({ text, program });
|
|
1637
|
+
params.state.cache.set(params.dialect, compiled);
|
|
1638
|
+
return compiled;
|
|
1639
|
+
}
|
|
1640
|
+
function executionHandle(params) {
|
|
1641
|
+
const { value } = params;
|
|
1642
|
+
return queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) || isSqlFragment(value) || isSqlInput(value) || sqlInputScopeHandle(params);
|
|
1643
|
+
}
|
|
1644
|
+
function bindTemplate(params) {
|
|
1645
|
+
const { scope } = params.state;
|
|
1646
|
+
return bindSqlBindingProgram({ program: params.compiled.program, params: params.inputs, isHandle: (value) => executionHandle({ scope, value }) });
|
|
1647
|
+
}
|
|
1648
|
+
function compileReusableSqlQuery(params) {
|
|
1649
|
+
const state = reusableQueries.get(params.query) ?? invalid4("Expected an authentic reusable SQL row query");
|
|
1650
|
+
const compiled = compiledTemplate({ dialect: params.dialect, state });
|
|
1651
|
+
return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }), decoder: state.decoder };
|
|
1652
|
+
}
|
|
1653
|
+
function compileReusableSqlCommand(params) {
|
|
1654
|
+
const state = reusableCommands.get(params.query) ?? invalid4("Expected an authentic reusable SQL command");
|
|
1655
|
+
const compiled = compiledTemplate({ dialect: params.dialect, state });
|
|
1656
|
+
return { text: compiled.text, values: bindTemplate({ compiled, state, inputs: params.params }) };
|
|
1657
|
+
}
|
|
1658
|
+
function compileSqlQuery(params) {
|
|
1659
|
+
const state = queries.get(params.query) ?? invalid4("Expected an authentic SQL row query");
|
|
1660
|
+
const collector = new ParamCollector(params.dialect);
|
|
1661
|
+
const text = state.render({ dialect: params.dialect, collector });
|
|
1662
|
+
if (collector.values.length > params.dialect.paramBudget)
|
|
1663
|
+
invalid4("SQL query exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1664
|
+
return { text, values: collector.values, decoder: state.decoder };
|
|
1665
|
+
}
|
|
1666
|
+
function compileSqlCommand(params) {
|
|
1667
|
+
const render = commands.get(params.query) ?? invalid4("Expected an authentic SQL command");
|
|
1668
|
+
const collector = new ParamCollector(params.dialect);
|
|
1669
|
+
const text = render({ dialect: params.dialect, collector });
|
|
1670
|
+
if (collector.values.length > params.dialect.paramBudget)
|
|
1671
|
+
invalid4("SQL command exceeds this dialect's parameter budget; statements are never split implicitly");
|
|
1672
|
+
return { text, values: collector.values };
|
|
1673
|
+
}
|
|
817
1674
|
// packages/sql/src/exclusion.ts
|
|
818
|
-
import { VibeError as
|
|
1675
|
+
import { VibeError as VibeError12, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
819
1676
|
|
|
820
1677
|
// packages/sql/src/predicate.ts
|
|
821
|
-
import { VibeError as
|
|
1678
|
+
import { VibeError as VibeError11, assertPredicateCapabilities, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
822
1679
|
function renderPredicate(params) {
|
|
1680
|
+
assertPredicateCapabilities({ predicate: params.predicate, dialect: params.dialect.name, usage: params.usage });
|
|
823
1681
|
if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
|
|
824
|
-
throw
|
|
1682
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
825
1683
|
if (params.usage === "indexPredicate" && !params.dialect.capabilities.partialIndexes) {
|
|
826
|
-
throw
|
|
1684
|
+
throw unsupportedCapability9({ dialect: params.dialect.name, feature: "partialIndexes" });
|
|
827
1685
|
}
|
|
828
1686
|
const context = {
|
|
829
1687
|
model: params.model,
|
|
@@ -840,7 +1698,8 @@ var PRECEDENCE = {
|
|
|
840
1698
|
not: 3,
|
|
841
1699
|
compare: 4,
|
|
842
1700
|
inList: 4,
|
|
843
|
-
isNull: 4
|
|
1701
|
+
isNull: 4,
|
|
1702
|
+
isTrue: 4
|
|
844
1703
|
};
|
|
845
1704
|
var COMPARISON_SQL = {
|
|
846
1705
|
eq: "=",
|
|
@@ -858,7 +1717,7 @@ var INT64_MIN = -(2n ** 63n);
|
|
|
858
1717
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
859
1718
|
function refuse2(params) {
|
|
860
1719
|
const { context, rule, message } = params;
|
|
861
|
-
throw new
|
|
1720
|
+
throw new VibeError11({
|
|
862
1721
|
code: "VIBE_SCHEMA_INVALID",
|
|
863
1722
|
message: `${context.site} on "${context.model.name}": ${message}`,
|
|
864
1723
|
meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
|
|
@@ -891,14 +1750,16 @@ function renderNode(params) {
|
|
|
891
1750
|
}
|
|
892
1751
|
case "not":
|
|
893
1752
|
return `NOT ${renderChild({ node: node.operand, parent: "not", context })}`;
|
|
1753
|
+
case "isTrue":
|
|
1754
|
+
return `COALESCE(${renderNode({ node: node.operand, context })}, FALSE)`;
|
|
894
1755
|
case "isNull": {
|
|
895
|
-
const field = resolveField({ name: node.field.name, context });
|
|
1756
|
+
const field = resolveField({ name: node.field.name, context, nullTest: true });
|
|
896
1757
|
return `${quoteColumn({ field, context })} IS ${node.negate === true ? "NOT NULL" : "NULL"}`;
|
|
897
1758
|
}
|
|
898
1759
|
case "compare": {
|
|
899
|
-
const left =
|
|
900
|
-
const right = renderOperand({ operand: node.right, field: left, context });
|
|
901
|
-
return `${
|
|
1760
|
+
const left = renderExpression({ expression: node.left, context });
|
|
1761
|
+
const right = renderOperand({ operand: node.right, field: left.field, context });
|
|
1762
|
+
return `${left.text} ${COMPARISON_SQL[node.op]} ${right}`;
|
|
902
1763
|
}
|
|
903
1764
|
case "inList": {
|
|
904
1765
|
if (node.values.length === 0) {
|
|
@@ -942,7 +1803,7 @@ function resolveField(params) {
|
|
|
942
1803
|
meta: { field: name }
|
|
943
1804
|
});
|
|
944
1805
|
}
|
|
945
|
-
if (field.type === "Json" || field.type === "Bytes") {
|
|
1806
|
+
if (params.nullTest !== true && (field.type === "Json" || field.type === "Bytes")) {
|
|
946
1807
|
refuse2({
|
|
947
1808
|
context,
|
|
948
1809
|
rule: "predicate-unsupported-type",
|
|
@@ -951,7 +1812,7 @@ function resolveField(params) {
|
|
|
951
1812
|
});
|
|
952
1813
|
}
|
|
953
1814
|
if (context.usage === "check" && context.dialect.name === "mysql" && field.default?.kind === "autoincrement") {
|
|
954
|
-
throw
|
|
1815
|
+
throw unsupportedCapability9({
|
|
955
1816
|
dialect: "mysql",
|
|
956
1817
|
feature: `CHECK constraint referencing auto-increment column "${context.model.name}.${name}"`,
|
|
957
1818
|
hint: "mysql refuses this with error 3818 \u2014 enforce the rule in application code or on a non-generated column"
|
|
@@ -968,22 +1829,43 @@ function compareColumn(params) {
|
|
|
968
1829
|
const quoted = quoteColumn({ field, context });
|
|
969
1830
|
return context.dialect.name === "sqlite" && field.type === "Decimal" ? `CAST(${quoted} AS REAL)` : quoted;
|
|
970
1831
|
}
|
|
1832
|
+
function renderExpression(params) {
|
|
1833
|
+
const { expression, context } = params;
|
|
1834
|
+
if (expression.kind === "field") {
|
|
1835
|
+
const field = resolveField({ name: expression.name, context });
|
|
1836
|
+
return { text: compareColumn({ field, context }), field };
|
|
1837
|
+
}
|
|
1838
|
+
if (expression.kind === "jsonText") {
|
|
1839
|
+
const field = resolveField({ name: expression.field.name, context, nullTest: true });
|
|
1840
|
+
if (field.type !== "Json")
|
|
1841
|
+
refuse2({ context, rule: "predicate-type-mismatch", message: `jsonText requires a scalar Json field, not "${field.name}"` });
|
|
1842
|
+
if (!Array.isArray(expression.path) || Array.from(expression.path).some((segment) => typeof segment !== "string" || segment.includes("\x00"))) {
|
|
1843
|
+
refuse2({ context, rule: "predicate-json-path", message: "jsonText requires a string-only path without NUL bytes" });
|
|
1844
|
+
}
|
|
1845
|
+
const path = expression.path.map((value) => context.dialect.quoteStringLiteral({ value })).join(", ");
|
|
1846
|
+
return { text: `(${quoteColumn({ field, context })} #>> ARRAY[${path}]::text[])`, field: { ...field, type: "String" } };
|
|
1847
|
+
}
|
|
1848
|
+
const inner = renderExpression({ expression: expression.expression, context });
|
|
1849
|
+
if (inner.field.type !== "String")
|
|
1850
|
+
refuse2({ context, rule: "predicate-type-mismatch", message: "length requires a text expression" });
|
|
1851
|
+
return { text: `length(${inner.text})`, field: { ...inner.field, type: "Int" } };
|
|
1852
|
+
}
|
|
971
1853
|
function renderOperand(params) {
|
|
972
1854
|
const { operand, field, context } = params;
|
|
973
1855
|
if (operand.kind === "value")
|
|
974
1856
|
return renderLiteral({ value: operand, field, context });
|
|
975
|
-
const other =
|
|
1857
|
+
const other = renderExpression({ expression: operand, context });
|
|
976
1858
|
const leftType = fieldTypeIdentity({ type: field.type });
|
|
977
|
-
const rightType = fieldTypeIdentity({ type: other.type });
|
|
1859
|
+
const rightType = fieldTypeIdentity({ type: other.field.type });
|
|
978
1860
|
if (leftType !== rightType) {
|
|
979
1861
|
refuse2({
|
|
980
1862
|
context,
|
|
981
1863
|
rule: "predicate-type-mismatch",
|
|
982
|
-
message: `compares "${field.name}" (${leftType}) with "${other.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
|
|
983
|
-
meta: { field: field.name, other: other.name, fieldType: leftType, otherType: rightType }
|
|
1864
|
+
message: `compares "${field.name}" (${leftType}) with "${other.field.name}" (${rightType}) \u2014 both sides of a comparison need the same scalar type`,
|
|
1865
|
+
meta: { field: field.name, other: other.field.name, fieldType: leftType, otherType: rightType }
|
|
984
1866
|
});
|
|
985
1867
|
}
|
|
986
|
-
return
|
|
1868
|
+
return other.text;
|
|
987
1869
|
}
|
|
988
1870
|
function renderLiteral(params) {
|
|
989
1871
|
const { field, context } = params;
|
|
@@ -1121,23 +2003,23 @@ function describe(params) {
|
|
|
1121
2003
|
}
|
|
1122
2004
|
|
|
1123
2005
|
// packages/sql/src/exclusion.ts
|
|
1124
|
-
function
|
|
1125
|
-
throw new
|
|
2006
|
+
function invalid5(rule) {
|
|
2007
|
+
throw new VibeError12({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
1126
2008
|
}
|
|
1127
2009
|
function compileExclusion(params) {
|
|
1128
2010
|
const { exclusion: spec, model, dialect } = params;
|
|
1129
2011
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1130
|
-
throw
|
|
2012
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1131
2013
|
if (!spec.name || spec.name.includes("\x00"))
|
|
1132
|
-
|
|
2014
|
+
invalid5("name");
|
|
1133
2015
|
if (!["int8range", "tsrange", "tstzrange"].includes(spec.range.type))
|
|
1134
|
-
|
|
2016
|
+
invalid5("range-type");
|
|
1135
2017
|
if (!["[)", "[]", "()", "(]"].includes(spec.range.bounds))
|
|
1136
|
-
|
|
2018
|
+
invalid5("bounds");
|
|
1137
2019
|
const field = (name) => {
|
|
1138
2020
|
const value = model.fields.find((entry) => entry.name === name);
|
|
1139
2021
|
if (value === undefined || value.isList || value.isUnsupported)
|
|
1140
|
-
|
|
2022
|
+
invalid5("field");
|
|
1141
2023
|
return value;
|
|
1142
2024
|
};
|
|
1143
2025
|
const endpoint = (value) => {
|
|
@@ -1145,17 +2027,17 @@ function compileExclusion(params) {
|
|
|
1145
2027
|
return "NULL";
|
|
1146
2028
|
const infinity = "infinity" in value ? value.infinity : value.nullAs;
|
|
1147
2029
|
if (infinity !== undefined && (spec.range.type === "int8range" || !["infinity", "-infinity"].includes(infinity)))
|
|
1148
|
-
|
|
2030
|
+
invalid5("infinity");
|
|
1149
2031
|
const literal = infinity === undefined ? undefined : `${dialect.quoteStringLiteral({ value: infinity })}::${spec.range.type === "tsrange" ? "timestamp" : "timestamptz"}`;
|
|
1150
2032
|
if ("infinity" in value)
|
|
1151
2033
|
return literal;
|
|
1152
2034
|
const column = field(value.field);
|
|
1153
2035
|
if (fieldTypeIdentity2({ type: column.type }) !== (spec.range.type === "int8range" ? "BigInt" : "DateTime"))
|
|
1154
|
-
|
|
2036
|
+
invalid5("range-field-type");
|
|
1155
2037
|
if (column.type === "DateTime") {
|
|
1156
2038
|
const timezone = /timestamptz|timestamp.*with time zone/i.test(column.nativeType ?? "timestamptz");
|
|
1157
2039
|
if (timezone !== (spec.range.type === "tstzrange"))
|
|
1158
|
-
|
|
2040
|
+
invalid5("range-timezone");
|
|
1159
2041
|
}
|
|
1160
2042
|
const sql = dialect.quoteIdent({ name: column.dbName ?? column.name });
|
|
1161
2043
|
return literal === undefined ? sql : `COALESCE(${sql}, ${literal})`;
|
|
@@ -1163,15 +2045,15 @@ function compileExclusion(params) {
|
|
|
1163
2045
|
const keys = spec.keys.map((key) => {
|
|
1164
2046
|
if ("expression" in key) {
|
|
1165
2047
|
if (!key.expression.trim() || key.expression.includes("\x00") || key.fields.length === 0)
|
|
1166
|
-
|
|
2048
|
+
invalid5("expression");
|
|
1167
2049
|
key.fields.forEach(field);
|
|
1168
2050
|
return `(${key.expression}) WITH =`;
|
|
1169
2051
|
}
|
|
1170
2052
|
const column = field(key.field);
|
|
1171
2053
|
if (column.isOptional)
|
|
1172
|
-
|
|
2054
|
+
invalid5("nullable-key-requires-expression");
|
|
1173
2055
|
if (!isEnumType2(column.type) && !["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "DateTime"].includes(fieldTypeIdentity2({ type: column.type })))
|
|
1174
|
-
|
|
2056
|
+
invalid5("key-type");
|
|
1175
2057
|
return `${dialect.quoteIdent({ name: column.dbName ?? column.name })} WITH =`;
|
|
1176
2058
|
});
|
|
1177
2059
|
keys.push(`${spec.range.type}(${endpoint(spec.range.lower)}, ${endpoint(spec.range.upper)}, ${dialect.quoteStringLiteral({ value: spec.range.bounds })}) WITH &&`);
|
|
@@ -1180,13 +2062,13 @@ function compileExclusion(params) {
|
|
|
1180
2062
|
}
|
|
1181
2063
|
function renderExclusionSetup(params) {
|
|
1182
2064
|
if (!params.dialect.capabilities.exclusionConstraints)
|
|
1183
|
-
throw
|
|
2065
|
+
throw unsupportedCapability10({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
1184
2066
|
return params.exclusion.requiredExtensions.map((name) => `CREATE EXTENSION IF NOT EXISTS ${params.dialect.quoteIdent({ name })};`);
|
|
1185
2067
|
}
|
|
1186
2068
|
function renderExclusionDdl(params) {
|
|
1187
2069
|
const { dialect, table, exclusion, operation } = params;
|
|
1188
2070
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1189
|
-
throw
|
|
2071
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1190
2072
|
const target = `ALTER TABLE ${dialect.quoteIdent({ name: table })}`;
|
|
1191
2073
|
if (operation === "drop")
|
|
1192
2074
|
return [`${target} DROP CONSTRAINT ${dialect.quoteIdent({ name: exclusion.name })};`];
|
|
@@ -1195,14 +2077,14 @@ function renderExclusionDdl(params) {
|
|
|
1195
2077
|
function renderExclusionCatalog(params) {
|
|
1196
2078
|
const { dialect, probe } = params;
|
|
1197
2079
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1198
|
-
throw
|
|
2080
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1199
2081
|
const scope = probe === undefined ? `n.nspname = ${dialect.quoteStringLiteral({ value: params.namespace ?? "public" })}` : `t.oid = ${dialect.quoteStringLiteral({ value: probe })}::regclass`;
|
|
1200
2082
|
return `SELECT t.relname AS table_name, c.conname AS constraint_name, pg_catalog.pg_get_constraintdef(c.oid, true) AS definition, c.convalidated AS validated, i.indisvalid AS valid, i.indisready AS ready FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class t ON t.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace JOIN pg_catalog.pg_index i ON i.indexrelid = c.conindid WHERE c.contype = 'x' AND ${scope} ORDER BY t.relname, c.conname`;
|
|
1201
2083
|
}
|
|
1202
2084
|
function renderExclusionProbe(params) {
|
|
1203
2085
|
const { dialect, table, probe, definition } = params;
|
|
1204
2086
|
if (!dialect.capabilities.exclusionConstraints)
|
|
1205
|
-
throw
|
|
2087
|
+
throw unsupportedCapability10({ dialect: dialect.name, feature: "exclusionConstraints" });
|
|
1206
2088
|
return [`CREATE TEMP TABLE ${dialect.quoteIdent({ name: probe })} (LIKE ${dialect.quoteIdent({ name: params.namespace ?? "public" })}.${dialect.quoteIdent({ name: table })})`, `ALTER TABLE ${dialect.quoteIdent({ name: probe })} ADD CONSTRAINT ${dialect.quoteIdent({ name: `${probe}_x` })} ${definition}`];
|
|
1207
2089
|
}
|
|
1208
2090
|
// packages/sql/src/ast.ts
|
|
@@ -1245,7 +2127,7 @@ function isSqlDefaultCell(value) {
|
|
|
1245
2127
|
return value === SQL_DEFAULT_CELL;
|
|
1246
2128
|
}
|
|
1247
2129
|
function jsonParamCell(params) {
|
|
1248
|
-
return { __jsonParam: true, text: params.text };
|
|
2130
|
+
return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
|
|
1249
2131
|
}
|
|
1250
2132
|
function isJsonParamCell(value) {
|
|
1251
2133
|
return typeof value === "object" && value !== null && value.__jsonParam === true;
|
|
@@ -1256,9 +2138,32 @@ function excludedRef(params) {
|
|
|
1256
2138
|
function isExcludedRef(value) {
|
|
1257
2139
|
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
1258
2140
|
}
|
|
2141
|
+
function returningColumnName(column) {
|
|
2142
|
+
return typeof column === "string" ? column : column.name;
|
|
2143
|
+
}
|
|
1259
2144
|
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
2145
|
+
function stepColumnRef(params) {
|
|
2146
|
+
return { __stepColumn: true, step: params.step, column: params.column };
|
|
2147
|
+
}
|
|
2148
|
+
function isStepColumnRef(value) {
|
|
2149
|
+
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
2150
|
+
}
|
|
2151
|
+
var CHAIN_VETO_PREFIX = "__vibe_veto:";
|
|
2152
|
+
var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
2153
|
+
function chainVetoToken(params) {
|
|
2154
|
+
const { message, nonce } = params;
|
|
2155
|
+
if (nonce === "")
|
|
2156
|
+
return;
|
|
2157
|
+
for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
|
|
2158
|
+
const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
|
|
2159
|
+
const match = /^([a-z][a-z0-9_]*):/.exec(rest);
|
|
2160
|
+
if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
|
|
2161
|
+
return match[1];
|
|
2162
|
+
}
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
1260
2165
|
// packages/sql/src/locks.ts
|
|
1261
|
-
import { internalError, unsupportedCapability as
|
|
2166
|
+
import { internalError, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
1262
2167
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
1263
2168
|
strengths: Object.freeze({
|
|
1264
2169
|
update: "FOR UPDATE",
|
|
@@ -1278,7 +2183,7 @@ function renderRowLockClause(params) {
|
|
|
1278
2183
|
if (syntax === null) {
|
|
1279
2184
|
if (lock.strength === "update" && lock.wait === "wait")
|
|
1280
2185
|
return null;
|
|
1281
|
-
throw
|
|
2186
|
+
throw unsupportedCapability11({
|
|
1282
2187
|
dialect: dialect.name,
|
|
1283
2188
|
feature: `a ${describeRowLock({ lock })} locking read`,
|
|
1284
2189
|
hint: `${dialect.name} has no locking-read clause at all`
|
|
@@ -1286,7 +2191,7 @@ function renderRowLockClause(params) {
|
|
|
1286
2191
|
}
|
|
1287
2192
|
const strengthText = syntax.strengths[lock.strength];
|
|
1288
2193
|
if (strengthText === undefined) {
|
|
1289
|
-
throw
|
|
2194
|
+
throw unsupportedCapability11({
|
|
1290
2195
|
dialect: dialect.name,
|
|
1291
2196
|
feature: `the ${describeRowLock({ lock: { ...lock, wait: "wait" } })} row-lock strength`,
|
|
1292
2197
|
hint: `${dialect.name} can express: ${Object.keys(syntax.strengths).join(", ")}`
|
|
@@ -1294,7 +2199,7 @@ function renderRowLockClause(params) {
|
|
|
1294
2199
|
}
|
|
1295
2200
|
const waitText = syntax.wait[lock.wait];
|
|
1296
2201
|
if (waitText === undefined) {
|
|
1297
|
-
throw
|
|
2202
|
+
throw unsupportedCapability11({
|
|
1298
2203
|
dialect: dialect.name,
|
|
1299
2204
|
feature: `the "${lock.wait}" lock wait policy`,
|
|
1300
2205
|
hint: `${dialect.name} can express: ${Object.keys(syntax.wait).join(", ")}`
|
|
@@ -1313,7 +2218,7 @@ function renderAdvisoryLock(params) {
|
|
|
1313
2218
|
const { dialect, statement, collector } = params;
|
|
1314
2219
|
const functions = dialect.advisoryLockFunctions;
|
|
1315
2220
|
if (functions === null) {
|
|
1316
|
-
throw
|
|
2221
|
+
throw unsupportedCapability11({
|
|
1317
2222
|
dialect: dialect.name,
|
|
1318
2223
|
feature: "advisory locks",
|
|
1319
2224
|
hint: dialect.name === "mysql" ? "mysql's GET_LOCK is SESSION-scoped \u2014 it survives a rollback, so it cannot be released with the transaction" : `${dialect.name} has no advisory-lock functions`
|
|
@@ -1344,35 +2249,15 @@ function assertInt32(params) {
|
|
|
1344
2249
|
var JSON_TRANSPORT = Object.freeze({ chunkSize: 100 });
|
|
1345
2250
|
function projectionOutputName(params) {
|
|
1346
2251
|
const { column } = params;
|
|
1347
|
-
return column.transform === undefined ? column.alias ?? column.name : column.name;
|
|
1348
|
-
}
|
|
1349
|
-
// packages/sql/src/capabilities.ts
|
|
1350
|
-
import {
|
|
1351
|
-
DIALECT_CAPABILITIES,
|
|
1352
|
-
POSTGRES_CAPABILITIES,
|
|
1353
|
-
SQLITE_CAPABILITIES,
|
|
1354
|
-
MYSQL_CAPABILITIES
|
|
1355
|
-
} from "@vibeorm/schema";
|
|
1356
|
-
|
|
1357
|
-
// packages/sql/src/identifiers.ts
|
|
1358
|
-
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
1359
|
-
function assertNoNul(params) {
|
|
1360
|
-
if (params.name.includes("\x00")) {
|
|
1361
|
-
throw new VibeError7({
|
|
1362
|
-
code: "VIBE_VALIDATION",
|
|
1363
|
-
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
1364
|
-
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
return params.name;
|
|
2252
|
+
return column.transform === undefined || column.transform === "jsonText" ? column.alias ?? column.name : column.name;
|
|
1368
2253
|
}
|
|
1369
|
-
|
|
1370
2254
|
// packages/sql/src/dialects/postgres.ts
|
|
1371
2255
|
var postgresDialect = {
|
|
1372
2256
|
name: "postgres",
|
|
1373
2257
|
capabilities: POSTGRES_CAPABILITIES,
|
|
1374
2258
|
inArrayStrategy: "any",
|
|
1375
2259
|
tupleInStrategy: "rowValue",
|
|
2260
|
+
timeOfDayInStrategy: "in",
|
|
1376
2261
|
upsertForm: "onConflict",
|
|
1377
2262
|
conflictKeyEquality: {
|
|
1378
2263
|
foldingScalars: ["Decimal"],
|
|
@@ -1383,6 +2268,7 @@ var postgresDialect = {
|
|
|
1383
2268
|
doNothingForm: "onConflictDoNothing",
|
|
1384
2269
|
insensitiveLike: "ilike",
|
|
1385
2270
|
strictMatchStrategy: "native",
|
|
2271
|
+
greatestForm: "native",
|
|
1386
2272
|
orderByNullsMode: "native",
|
|
1387
2273
|
offsetRequiresLimit: null,
|
|
1388
2274
|
likeEscapeClause: null,
|
|
@@ -1397,6 +2283,7 @@ var postgresDialect = {
|
|
|
1397
2283
|
"array_ends_with"
|
|
1398
2284
|
],
|
|
1399
2285
|
jsonParamCast: "::text::jsonb",
|
|
2286
|
+
valueCastTypes: Object.freeze({ String: "TEXT", Boolean: "BOOLEAN", Int: "INTEGER", BigInt: "BIGINT", Float: "DOUBLE PRECISION", Decimal: "NUMERIC", DateTime: "TIMESTAMPTZ", Json: "JSONB", Bytes: "BYTEA" }),
|
|
1400
2287
|
lastInsertIdSql: null,
|
|
1401
2288
|
rowLockSyntax: {
|
|
1402
2289
|
strengths: Object.freeze({
|
|
@@ -1419,6 +2306,7 @@ var postgresDialect = {
|
|
|
1419
2306
|
keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
|
|
1420
2307
|
},
|
|
1421
2308
|
rowDefaultKeyword: true,
|
|
2309
|
+
statementChain: "dataModifyingCte",
|
|
1422
2310
|
paramBudget: 30000,
|
|
1423
2311
|
booleanLiteral: { true: "true", false: "false" },
|
|
1424
2312
|
quoteIdent({ name }) {
|
|
@@ -1430,69 +2318,22 @@ var postgresDialect = {
|
|
|
1430
2318
|
placeholder({ index }) {
|
|
1431
2319
|
return `$${index}`;
|
|
1432
2320
|
},
|
|
2321
|
+
jsonTextProjection({ expression }) {
|
|
2322
|
+
return `${expression}::text`;
|
|
2323
|
+
},
|
|
1433
2324
|
renderDbNow({ offsetSeconds, collector }) {
|
|
1434
2325
|
if (offsetSeconds === 0)
|
|
1435
2326
|
return "now()";
|
|
1436
2327
|
return `(now() + make_interval(secs => ${collector.add(offsetSeconds)}))`;
|
|
1437
2328
|
}
|
|
1438
2329
|
};
|
|
1439
|
-
// packages/sql/src/dialects/sqlite.ts
|
|
1440
|
-
var sqliteDialect = {
|
|
1441
|
-
name: "sqlite",
|
|
1442
|
-
capabilities: SQLITE_CAPABILITIES,
|
|
1443
|
-
inArrayStrategy: "expand",
|
|
1444
|
-
tupleInStrategy: "rowValue",
|
|
1445
|
-
upsertForm: "onConflict",
|
|
1446
|
-
conflictKeyEquality: {
|
|
1447
|
-
foldingScalars: [],
|
|
1448
|
-
textIsBinaryByDefault: true,
|
|
1449
|
-
duplicatesInOneStatement: "lastWriteWins"
|
|
1450
|
-
},
|
|
1451
|
-
excludedRowRef: { form: "keyword", name: "excluded" },
|
|
1452
|
-
doNothingForm: "onConflictDoNothing",
|
|
1453
|
-
insensitiveLike: "lower",
|
|
1454
|
-
strictMatchStrategy: "glob",
|
|
1455
|
-
orderByNullsMode: "native",
|
|
1456
|
-
offsetRequiresLimit: { limitText: "-1" },
|
|
1457
|
-
likeEscapeClause: " ESCAPE '\\'",
|
|
1458
|
-
jsonPathMode: "sqliteExtract",
|
|
1459
|
-
supportedJsonOps: [
|
|
1460
|
-
"equals",
|
|
1461
|
-
"string_contains",
|
|
1462
|
-
"string_starts_with",
|
|
1463
|
-
"string_ends_with",
|
|
1464
|
-
"array_contains",
|
|
1465
|
-
"array_starts_with",
|
|
1466
|
-
"array_ends_with"
|
|
1467
|
-
],
|
|
1468
|
-
jsonParamCast: "",
|
|
1469
|
-
lastInsertIdSql: null,
|
|
1470
|
-
rowLockSyntax: null,
|
|
1471
|
-
advisoryLockFunctions: null,
|
|
1472
|
-
rowDefaultKeyword: false,
|
|
1473
|
-
paramBudget: 900,
|
|
1474
|
-
booleanLiteral: { true: "1", false: "0" },
|
|
1475
|
-
quoteIdent({ name }) {
|
|
1476
|
-
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
1477
|
-
},
|
|
1478
|
-
quoteStringLiteral({ value }) {
|
|
1479
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
1480
|
-
},
|
|
1481
|
-
placeholder() {
|
|
1482
|
-
return "?";
|
|
1483
|
-
},
|
|
1484
|
-
renderDbNow({ offsetSeconds, collector }) {
|
|
1485
|
-
if (offsetSeconds === 0)
|
|
1486
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1487
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
1488
|
-
}
|
|
1489
|
-
};
|
|
1490
2330
|
// packages/sql/src/dialects/mysql.ts
|
|
1491
2331
|
var mysqlDialect = {
|
|
1492
2332
|
name: "mysql",
|
|
1493
2333
|
capabilities: MYSQL_CAPABILITIES,
|
|
1494
2334
|
inArrayStrategy: "expand",
|
|
1495
2335
|
tupleInStrategy: "rowValue",
|
|
2336
|
+
timeOfDayInStrategy: "equalities",
|
|
1496
2337
|
upsertForm: "onDuplicateKey",
|
|
1497
2338
|
conflictKeyEquality: {
|
|
1498
2339
|
foldingScalars: ["Decimal"],
|
|
@@ -1504,6 +2345,7 @@ var mysqlDialect = {
|
|
|
1504
2345
|
doNothingForm: "insertIgnore",
|
|
1505
2346
|
insensitiveLike: "lower",
|
|
1506
2347
|
strictMatchStrategy: "binary",
|
|
2348
|
+
greatestForm: "wrapped-frame",
|
|
1507
2349
|
orderByNullsMode: "emulate",
|
|
1508
2350
|
offsetRequiresLimit: { limitText: "18446744073709551615" },
|
|
1509
2351
|
likeEscapeClause: null,
|
|
@@ -1518,6 +2360,7 @@ var mysqlDialect = {
|
|
|
1518
2360
|
"array_ends_with"
|
|
1519
2361
|
],
|
|
1520
2362
|
jsonParamCast: "",
|
|
2363
|
+
valueCastTypes: Object.freeze({ String: "CHAR", Boolean: "UNSIGNED", Int: "SIGNED", BigInt: "SIGNED", Float: "DOUBLE", Decimal: "DECIMAL(65, 30)", DateTime: "DATETIME(3)", Json: "JSON", Bytes: "BINARY" }),
|
|
1521
2364
|
lastInsertIdSql: "SELECT LAST_INSERT_ID() AS `id`",
|
|
1522
2365
|
rowLockSyntax: {
|
|
1523
2366
|
strengths: Object.freeze({ update: "FOR UPDATE", share: "FOR SHARE" }),
|
|
@@ -1525,6 +2368,7 @@ var mysqlDialect = {
|
|
|
1525
2368
|
},
|
|
1526
2369
|
advisoryLockFunctions: null,
|
|
1527
2370
|
rowDefaultKeyword: true,
|
|
2371
|
+
statementChain: null,
|
|
1528
2372
|
paramBudget: 1e4,
|
|
1529
2373
|
booleanLiteral: { true: "1", false: "0" },
|
|
1530
2374
|
quoteIdent({ name }) {
|
|
@@ -1536,6 +2380,9 @@ var mysqlDialect = {
|
|
|
1536
2380
|
placeholder() {
|
|
1537
2381
|
return "?";
|
|
1538
2382
|
},
|
|
2383
|
+
jsonTextProjection({ expression }) {
|
|
2384
|
+
return `CAST(${expression} AS CHAR)`;
|
|
2385
|
+
},
|
|
1539
2386
|
renderDbNow({ offsetSeconds, collector }) {
|
|
1540
2387
|
if (offsetSeconds === 0)
|
|
1541
2388
|
return "NOW(6)";
|
|
@@ -1551,85 +2398,8 @@ var SQL_DIALECTS = {
|
|
|
1551
2398
|
function getSqlDialect(params) {
|
|
1552
2399
|
return SQL_DIALECTS[params.dialect];
|
|
1553
2400
|
}
|
|
1554
|
-
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
1555
|
-
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
1556
|
-
function tableDefinitionParts(params) {
|
|
1557
|
-
const sql = params.sql;
|
|
1558
|
-
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
1559
|
-
throw unavailable();
|
|
1560
|
-
const parts = [];
|
|
1561
|
-
let start = -1;
|
|
1562
|
-
let depth = 0;
|
|
1563
|
-
let quote;
|
|
1564
|
-
for (let index = 0;index < sql.length; index++) {
|
|
1565
|
-
const char = sql[index];
|
|
1566
|
-
if (quote !== undefined) {
|
|
1567
|
-
if (char === quote) {
|
|
1568
|
-
if (quote !== "]" && sql[index + 1] === quote)
|
|
1569
|
-
index++;
|
|
1570
|
-
else
|
|
1571
|
-
quote = undefined;
|
|
1572
|
-
}
|
|
1573
|
-
continue;
|
|
1574
|
-
}
|
|
1575
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
1576
|
-
quote = char;
|
|
1577
|
-
continue;
|
|
1578
|
-
}
|
|
1579
|
-
if (char === "[") {
|
|
1580
|
-
quote = "]";
|
|
1581
|
-
continue;
|
|
1582
|
-
}
|
|
1583
|
-
if (char === "-" && sql[index + 1] === "-") {
|
|
1584
|
-
const end = sql.indexOf(`
|
|
1585
|
-
`, index + 2);
|
|
1586
|
-
if (end < 0)
|
|
1587
|
-
break;
|
|
1588
|
-
index = end;
|
|
1589
|
-
continue;
|
|
1590
|
-
}
|
|
1591
|
-
if (char === "/" && sql[index + 1] === "*") {
|
|
1592
|
-
const end = sql.indexOf("*/", index + 2);
|
|
1593
|
-
if (end < 0)
|
|
1594
|
-
break;
|
|
1595
|
-
index = end + 1;
|
|
1596
|
-
continue;
|
|
1597
|
-
}
|
|
1598
|
-
if (char === "(") {
|
|
1599
|
-
if (depth === 0)
|
|
1600
|
-
start = index + 1;
|
|
1601
|
-
depth++;
|
|
1602
|
-
} else if (char === ")") {
|
|
1603
|
-
depth--;
|
|
1604
|
-
if (depth === 0 && start >= 0) {
|
|
1605
|
-
parts.push(sql.slice(start, index).trim());
|
|
1606
|
-
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
1607
|
-
}
|
|
1608
|
-
} else if (char === "," && depth === 1) {
|
|
1609
|
-
parts.push(sql.slice(start, index).trim());
|
|
1610
|
-
start = index + 1;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
throw unavailable();
|
|
1614
|
-
}
|
|
1615
|
-
function unavailable() {
|
|
1616
|
-
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
1617
|
-
}
|
|
1618
|
-
function sqliteColumnClauses(params) {
|
|
1619
|
-
const columns = [];
|
|
1620
|
-
for (const part of tableDefinitionParts(params).clauses) {
|
|
1621
|
-
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
1622
|
-
if (token === null)
|
|
1623
|
-
throw unavailable();
|
|
1624
|
-
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
1625
|
-
continue;
|
|
1626
|
-
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
1627
|
-
columns.push({ name, sql: part });
|
|
1628
|
-
}
|
|
1629
|
-
return columns;
|
|
1630
|
-
}
|
|
1631
2401
|
// packages/sql/src/dialects/diagnostics.ts
|
|
1632
|
-
import { unsupportedCapability as
|
|
2402
|
+
import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
1633
2403
|
var QUERIES = Object.freeze({
|
|
1634
2404
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
1635
2405
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -1637,7 +2407,7 @@ var QUERIES = Object.freeze({
|
|
|
1637
2407
|
function renderDiagnostic(params) {
|
|
1638
2408
|
const query = QUERIES[params.query];
|
|
1639
2409
|
if (params.dialect.name !== query.dialect) {
|
|
1640
|
-
throw
|
|
2410
|
+
throw unsupportedCapability12({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
1641
2411
|
}
|
|
1642
2412
|
return { text: query.text, values: [], returning: "none" };
|
|
1643
2413
|
}
|
|
@@ -1646,7 +2416,7 @@ function isFixedDiagnostic(params) {
|
|
|
1646
2416
|
}
|
|
1647
2417
|
|
|
1648
2418
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
1649
|
-
import { VibeError as
|
|
2419
|
+
import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability13 } from "@vibeorm/schema";
|
|
1650
2420
|
function renderPostgresExplain(params) {
|
|
1651
2421
|
const { dialect, query, options } = params;
|
|
1652
2422
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -1681,7 +2451,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
1681
2451
|
});
|
|
1682
2452
|
function requirePostgres(params) {
|
|
1683
2453
|
if (params.dialect.name !== "postgres") {
|
|
1684
|
-
throw
|
|
2454
|
+
throw unsupportedCapability13({
|
|
1685
2455
|
dialect: params.dialect.name,
|
|
1686
2456
|
feature: params.feature,
|
|
1687
2457
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -1699,7 +2469,7 @@ function renderPostgresWorkload(params) {
|
|
|
1699
2469
|
if (query === "statementSnapshot") {
|
|
1700
2470
|
const bounded = limit ?? 20;
|
|
1701
2471
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
1702
|
-
throw new
|
|
2472
|
+
throw new VibeError13({
|
|
1703
2473
|
code: "VIBE_CONFIG",
|
|
1704
2474
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
1705
2475
|
meta: { limit }
|
|
@@ -1718,7 +2488,7 @@ function renderPostgresWorkload(params) {
|
|
|
1718
2488
|
function requireArg(params) {
|
|
1719
2489
|
const { value, name, query } = params;
|
|
1720
2490
|
if (value === undefined || value.length === 0) {
|
|
1721
|
-
throw new
|
|
2491
|
+
throw new VibeError13({
|
|
1722
2492
|
code: "VIBE_CONFIG",
|
|
1723
2493
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
1724
2494
|
meta: { query, argument: name }
|
|
@@ -1728,10 +2498,10 @@ function requireArg(params) {
|
|
|
1728
2498
|
}
|
|
1729
2499
|
|
|
1730
2500
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
1731
|
-
import { unsupportedCapability as
|
|
2501
|
+
import { unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
1732
2502
|
|
|
1733
2503
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
1734
|
-
import { RlsContextError, VibeError as
|
|
2504
|
+
import { RlsContextError, VibeError as VibeError14, assertValidSchemaIR, unsupportedCapability as unsupportedCapability14, validateRlsContextIR } from "@vibeorm/schema";
|
|
1735
2505
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
1736
2506
|
var COLUMN_TYPES = {
|
|
1737
2507
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -1744,18 +2514,18 @@ var COLUMN_TYPES = {
|
|
|
1744
2514
|
};
|
|
1745
2515
|
function requireRls(params) {
|
|
1746
2516
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
1747
|
-
throw
|
|
2517
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
1748
2518
|
}
|
|
1749
2519
|
}
|
|
1750
2520
|
function identifier2(params) {
|
|
1751
2521
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
1752
|
-
throw new
|
|
2522
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
1753
2523
|
}
|
|
1754
2524
|
return params.dialect.quoteIdent({ name: params.name });
|
|
1755
2525
|
}
|
|
1756
2526
|
function tableRef(params) {
|
|
1757
2527
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
1758
|
-
throw
|
|
2528
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
1759
2529
|
}
|
|
1760
2530
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
1761
2531
|
}
|
|
@@ -1796,14 +2566,14 @@ function renderRlsColumnType(params) {
|
|
|
1796
2566
|
if (native !== undefined)
|
|
1797
2567
|
return native;
|
|
1798
2568
|
}
|
|
1799
|
-
throw
|
|
2569
|
+
throw unsupportedCapability14({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
1800
2570
|
}
|
|
1801
2571
|
function protectedModel(params) {
|
|
1802
2572
|
requireRls(params);
|
|
1803
2573
|
assertValidSchemaIR({ schema: params.schema });
|
|
1804
2574
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
1805
2575
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
1806
|
-
throw new
|
|
2576
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
1807
2577
|
}
|
|
1808
2578
|
return model;
|
|
1809
2579
|
}
|
|
@@ -1836,7 +2606,7 @@ function predicate(params) {
|
|
|
1836
2606
|
}).join(" AND ");
|
|
1837
2607
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
1838
2608
|
}
|
|
1839
|
-
throw new
|
|
2609
|
+
throw new VibeError14({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
1840
2610
|
}
|
|
1841
2611
|
function renderRlsPolicy(params) {
|
|
1842
2612
|
const model = protectedModel(params);
|
|
@@ -1870,7 +2640,7 @@ function renderRlsContext(params) {
|
|
|
1870
2640
|
function renderRlsRole(params) {
|
|
1871
2641
|
requireRls(params);
|
|
1872
2642
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
1873
|
-
throw new
|
|
2643
|
+
throw new VibeError14({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
1874
2644
|
}
|
|
1875
2645
|
const collector = new ParamCollector(params.dialect);
|
|
1876
2646
|
return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
|
|
@@ -2022,7 +2792,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
2022
2792
|
});
|
|
2023
2793
|
function requireRls2(params) {
|
|
2024
2794
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
2025
|
-
throw
|
|
2795
|
+
throw unsupportedCapability15({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
2026
2796
|
}
|
|
2027
2797
|
}
|
|
2028
2798
|
function renderRlsReadinessQuery(params) {
|
|
@@ -2054,7 +2824,7 @@ function renderRlsPolicyPath(params) {
|
|
|
2054
2824
|
}
|
|
2055
2825
|
|
|
2056
2826
|
// packages/sql/src/render.ts
|
|
2057
|
-
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as
|
|
2827
|
+
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError2, unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2058
2828
|
|
|
2059
2829
|
// packages/sql/src/like.ts
|
|
2060
2830
|
function escapeLikePattern(params) {
|
|
@@ -2108,6 +2878,8 @@ function renderStatement(params) {
|
|
|
2108
2878
|
return renderUpdate({ dialect, statement, collector });
|
|
2109
2879
|
case "delete":
|
|
2110
2880
|
return renderDelete({ dialect, statement, collector });
|
|
2881
|
+
case "chain":
|
|
2882
|
+
return renderChain({ dialect, statement, collector });
|
|
2111
2883
|
case "rlsReadiness":
|
|
2112
2884
|
return renderRlsReadinessQuery({ dialect, ...statement });
|
|
2113
2885
|
case "rlsProbeTable":
|
|
@@ -2141,8 +2913,29 @@ function quoteCol(dialect, column) {
|
|
|
2141
2913
|
}
|
|
2142
2914
|
return `CAST(${ref} AS REAL)`;
|
|
2143
2915
|
}
|
|
2916
|
+
if (column.castJsonb !== undefined) {
|
|
2917
|
+
if (dialect.name !== "postgres") {
|
|
2918
|
+
throw internalError2({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
2919
|
+
}
|
|
2920
|
+
return `${ref}::${column.castJsonb}`;
|
|
2921
|
+
}
|
|
2144
2922
|
return ref;
|
|
2145
2923
|
}
|
|
2924
|
+
function typedOrderRef(params) {
|
|
2925
|
+
const { statement, column } = params;
|
|
2926
|
+
if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined)
|
|
2927
|
+
return column;
|
|
2928
|
+
const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
|
|
2929
|
+
return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
|
|
2930
|
+
}
|
|
2931
|
+
function jsonCellCast(dialect, cell) {
|
|
2932
|
+
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
2933
|
+
return dialect.jsonParamCast;
|
|
2934
|
+
if (dialect.name !== "postgres") {
|
|
2935
|
+
throw internalError2({ message: "JsonParamCell storage json is a postgres construct" });
|
|
2936
|
+
}
|
|
2937
|
+
return "::text::json";
|
|
2938
|
+
}
|
|
2146
2939
|
function quoteProjection(dialect, column) {
|
|
2147
2940
|
if (column.transform === undefined) {
|
|
2148
2941
|
const base = quoteCol(dialect, column);
|
|
@@ -2150,8 +2943,13 @@ function quoteProjection(dialect, column) {
|
|
|
2150
2943
|
return base;
|
|
2151
2944
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
2152
2945
|
}
|
|
2946
|
+
if (column.transform === "jsonText") {
|
|
2947
|
+
const ref = quoteCol(dialect, column);
|
|
2948
|
+
const alias = dialect.quoteIdent({ name: column.alias ?? column.name });
|
|
2949
|
+
return `${dialect.jsonTextProjection({ expression: ref })} AS ${alias}`;
|
|
2950
|
+
}
|
|
2153
2951
|
if (dialect.name !== "postgres") {
|
|
2154
|
-
throw internalError2({ message: "ColRef.transform is a postgres
|
|
2952
|
+
throw internalError2({ message: "ColRef.transform is a postgres construct" });
|
|
2155
2953
|
}
|
|
2156
2954
|
const ref = quoteCol(dialect, column);
|
|
2157
2955
|
const alias = dialect.quoteIdent({ name: column.name });
|
|
@@ -2159,6 +2957,10 @@ function quoteProjection(dialect, column) {
|
|
|
2159
2957
|
return `${ref}::text AS ${alias}`;
|
|
2160
2958
|
return `encode(${ref}, 'base64') AS ${alias}`;
|
|
2161
2959
|
}
|
|
2960
|
+
function renderProjection(params) {
|
|
2961
|
+
const { dialect, columns } = params;
|
|
2962
|
+
return columns.map((column) => quoteProjection(dialect, column)).join(", ");
|
|
2963
|
+
}
|
|
2162
2964
|
function renderTable(dialect, table) {
|
|
2163
2965
|
if (table.namespace !== undefined)
|
|
2164
2966
|
assertSqlNamespace2({ namespace: table.namespace, dialect: dialect.name });
|
|
@@ -2202,7 +3004,7 @@ function renderAggRef(dialect, agg) {
|
|
|
2202
3004
|
function assertScalarArrays(dialect, feature) {
|
|
2203
3005
|
if (dialect.capabilities.scalarArrays)
|
|
2204
3006
|
return;
|
|
2205
|
-
throw
|
|
3007
|
+
throw unsupportedCapability16({
|
|
2206
3008
|
dialect: dialect.name,
|
|
2207
3009
|
feature,
|
|
2208
3010
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -2219,7 +3021,8 @@ function renderExpr(params) {
|
|
|
2219
3021
|
if (isDbNowMarker(expr.value)) {
|
|
2220
3022
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
2221
3023
|
}
|
|
2222
|
-
|
|
3024
|
+
const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
|
|
3025
|
+
return `${compared.column} ${op} ${compared.operand}`;
|
|
2223
3026
|
}
|
|
2224
3027
|
case "cmpCol": {
|
|
2225
3028
|
const op = COMPARISON_SQL2[expr.op];
|
|
@@ -2235,6 +3038,10 @@ function renderExpr(params) {
|
|
|
2235
3038
|
return expr.negate === true ? "1 = 1" : "1 = 0";
|
|
2236
3039
|
}
|
|
2237
3040
|
const column = quoteCol(dialect, expr.column);
|
|
3041
|
+
if (expr.timeOfDay === true && dialect.timeOfDayInStrategy === "equalities") {
|
|
3042
|
+
const equalities = collector.addMany(expr.values).map((placeholder) => `${column} = ${placeholder}`);
|
|
3043
|
+
return `${expr.negate === true ? "NOT " : ""}(${equalities.join(" OR ")})`;
|
|
3044
|
+
}
|
|
2238
3045
|
if (dialect.inArrayStrategy === "any") {
|
|
2239
3046
|
const base = `${column} = ANY(${collector.add(Array.isArray(expr.values) ? [...expr.values] : expr.values)})`;
|
|
2240
3047
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
@@ -2328,16 +3135,21 @@ function renderExpr(params) {
|
|
|
2328
3135
|
}
|
|
2329
3136
|
case "arrayHas": {
|
|
2330
3137
|
assertScalarArrays(dialect, 'the scalar-list filter "has"');
|
|
2331
|
-
const
|
|
3138
|
+
const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
|
|
3139
|
+
const base = `${compared.operand} = ANY(${compared.column})`;
|
|
2332
3140
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
2333
3141
|
}
|
|
2334
3142
|
case "arrayContainsAll": {
|
|
2335
3143
|
assertScalarArrays(dialect, 'the scalar-list filter "hasEvery"');
|
|
2336
|
-
|
|
3144
|
+
const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
|
|
3145
|
+
const compared = listComparison({ dialect, column: expr.column, value: values, collector });
|
|
3146
|
+
return `${compared.column} @> ${compared.operand}`;
|
|
2337
3147
|
}
|
|
2338
3148
|
case "arrayOverlaps": {
|
|
2339
3149
|
assertScalarArrays(dialect, 'the scalar-list filter "hasSome"');
|
|
2340
|
-
|
|
3150
|
+
const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
|
|
3151
|
+
const compared = listComparison({ dialect, column: expr.column, value: values, collector });
|
|
3152
|
+
return `${compared.column} && ${compared.operand}`;
|
|
2341
3153
|
}
|
|
2342
3154
|
case "arrayIsEmpty": {
|
|
2343
3155
|
assertScalarArrays(dialect, 'the scalar-list filter "isEmpty"');
|
|
@@ -2385,6 +3197,16 @@ function jsonPathString(path) {
|
|
|
2385
3197
|
}
|
|
2386
3198
|
return out;
|
|
2387
3199
|
}
|
|
3200
|
+
function listComparison(params) {
|
|
3201
|
+
const { dialect, column, value, collector } = params;
|
|
3202
|
+
if (isJsonListParameter(value) && value.storage === "json") {
|
|
3203
|
+
return {
|
|
3204
|
+
column: `${quoteCol(dialect, column)}::jsonb[]`,
|
|
3205
|
+
operand: collector.add(jsonListParameter({ value: value.value, storage: "jsonb" }))
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
return { column: quoteCol(dialect, column), operand: collector.add(value) };
|
|
3209
|
+
}
|
|
2388
3210
|
function jsonStringPattern(params) {
|
|
2389
3211
|
const { expr } = params;
|
|
2390
3212
|
if (typeof expr.value !== "string") {
|
|
@@ -2412,7 +3234,7 @@ function renderJsonLike(params) {
|
|
|
2412
3234
|
function renderJsonFilter(params) {
|
|
2413
3235
|
const { dialect, expr, collector } = params;
|
|
2414
3236
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
2415
|
-
throw
|
|
3237
|
+
throw unsupportedCapability16({
|
|
2416
3238
|
dialect: dialect.name,
|
|
2417
3239
|
feature: `the json filter "${expr.op}"`,
|
|
2418
3240
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -2422,19 +3244,22 @@ function renderJsonFilter(params) {
|
|
|
2422
3244
|
if (dialect.jsonPathMode === "operators") {
|
|
2423
3245
|
const column = quoteCol(dialect, expr.column);
|
|
2424
3246
|
const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
|
|
3247
|
+
const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
|
|
2425
3248
|
switch (expr.op) {
|
|
2426
3249
|
case "equals":
|
|
2427
|
-
return
|
|
3250
|
+
return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2428
3251
|
case "string_contains":
|
|
2429
3252
|
case "string_starts_with":
|
|
2430
|
-
case "string_ends_with":
|
|
2431
|
-
|
|
3253
|
+
case "string_ends_with": {
|
|
3254
|
+
const target = expr.storage === "json" ? `(${extracted} #>> '{}')` : `(${column} #>> ${pathParam})`;
|
|
3255
|
+
return renderJsonLike({ dialect, target, expr, collector });
|
|
3256
|
+
}
|
|
2432
3257
|
case "array_contains":
|
|
2433
|
-
return
|
|
3258
|
+
return `${extracted} @> ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2434
3259
|
case "array_starts_with":
|
|
2435
|
-
return
|
|
3260
|
+
return `${extracted} -> 0 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2436
3261
|
case "array_ends_with":
|
|
2437
|
-
return
|
|
3262
|
+
return `${extracted} -> -1 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2438
3263
|
}
|
|
2439
3264
|
}
|
|
2440
3265
|
if (dialect.jsonPathMode === "sqliteExtract") {
|
|
@@ -2476,7 +3301,7 @@ function renderJsonFilter(params) {
|
|
|
2476
3301
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
2477
3302
|
const tests = elements.map((element) => {
|
|
2478
3303
|
if (element === null || typeof element === "object" && element !== null) {
|
|
2479
|
-
throw
|
|
3304
|
+
throw unsupportedCapability16({
|
|
2480
3305
|
dialect: dialect.name,
|
|
2481
3306
|
feature: "json array_contains with object/array/null elements",
|
|
2482
3307
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -2578,7 +3403,14 @@ function renderLateralJoin(params) {
|
|
|
2578
3403
|
const terms = join.select.orderBy;
|
|
2579
3404
|
const columnTerms = terms.filter((term) => ("column" in term));
|
|
2580
3405
|
if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
|
|
2581
|
-
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
3406
|
+
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
3407
|
+
dialect,
|
|
3408
|
+
term: {
|
|
3409
|
+
...term,
|
|
3410
|
+
column: { table: "__sub", name: term.column.name, ...term.column.castJsonb === undefined ? {} : { castJsonb: term.column.castJsonb } }
|
|
3411
|
+
},
|
|
3412
|
+
collector
|
|
3413
|
+
}));
|
|
2582
3414
|
aggOrder = ` ORDER BY ${rendered.join(", ")}`;
|
|
2583
3415
|
}
|
|
2584
3416
|
}
|
|
@@ -2587,6 +3419,15 @@ function renderLateralJoin(params) {
|
|
|
2587
3419
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
2588
3420
|
return `LEFT JOIN LATERAL (SELECT ${projection} FROM (${inner.text}) AS ${subAlias}) AS ${alias} ON true`;
|
|
2589
3421
|
}
|
|
3422
|
+
function renderCountSubquery(params) {
|
|
3423
|
+
const { dialect, subquery, collector } = params;
|
|
3424
|
+
const select = subquery.select;
|
|
3425
|
+
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) {
|
|
3426
|
+
throw internalError2({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
3427
|
+
}
|
|
3428
|
+
const inner = renderSelect({ dialect, statement: select, collector });
|
|
3429
|
+
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
3430
|
+
}
|
|
2590
3431
|
function applyReturning(params) {
|
|
2591
3432
|
const { dialect, requested } = params;
|
|
2592
3433
|
if (requested === undefined || requested.length === 0) {
|
|
@@ -2595,9 +3436,69 @@ function applyReturning(params) {
|
|
|
2595
3436
|
if (!dialect.capabilities.returning) {
|
|
2596
3437
|
return { suffix: "", status: "omitted" };
|
|
2597
3438
|
}
|
|
2598
|
-
const columns = requested.map((
|
|
3439
|
+
const columns = requested.map((column) => {
|
|
3440
|
+
if (typeof column === "string")
|
|
3441
|
+
return dialect.quoteIdent({ name: column });
|
|
3442
|
+
if ("transform" in column) {
|
|
3443
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
3444
|
+
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
3445
|
+
}
|
|
3446
|
+
if (dialect.name !== "postgres") {
|
|
3447
|
+
throw internalError2({ message: "a castText RETURNING column is a postgres construct" });
|
|
3448
|
+
}
|
|
3449
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
3450
|
+
return `${name}::text AS ${name}`;
|
|
3451
|
+
}).join(", ");
|
|
2599
3452
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
2600
3453
|
}
|
|
3454
|
+
function renderKeyTableJoin(params) {
|
|
3455
|
+
const { dialect, join, collector } = params;
|
|
3456
|
+
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
3457
|
+
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
3458
|
+
if (!admitted) {
|
|
3459
|
+
throw unsupportedCapability16({
|
|
3460
|
+
dialect: dialect.name,
|
|
3461
|
+
feature: "relation key table",
|
|
3462
|
+
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
3466
|
+
throw internalError2({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
3467
|
+
}
|
|
3468
|
+
if (join.rows.length === 0)
|
|
3469
|
+
throw internalError2({ message: "key table needs at least one key row" });
|
|
3470
|
+
for (const member of join.members) {
|
|
3471
|
+
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
3472
|
+
throw internalError2({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
const alias = dialect.quoteIdent({ name: join.alias });
|
|
3476
|
+
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
3477
|
+
const rows = join.rows.map((row, ordinal) => {
|
|
3478
|
+
if (row.length !== join.members.length) {
|
|
3479
|
+
throw internalError2({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
3480
|
+
}
|
|
3481
|
+
const cells = row.map((value, index) => {
|
|
3482
|
+
const placeholder = collector.add(value);
|
|
3483
|
+
const member = join.members[index];
|
|
3484
|
+
switch (member.kind) {
|
|
3485
|
+
case "integer":
|
|
3486
|
+
return dialect.name === "mysql" ? `CAST(${placeholder} AS SIGNED)` : `CAST(${placeholder} AS bigint)`;
|
|
3487
|
+
case "decimal":
|
|
3488
|
+
return `CAST(${placeholder} AS DECIMAL(65,${member.scale}))`;
|
|
3489
|
+
case "citext":
|
|
3490
|
+
return `CAST(${placeholder} AS citext)`;
|
|
3491
|
+
case "text":
|
|
3492
|
+
return `CAST(${placeholder} AS text)`;
|
|
3493
|
+
default:
|
|
3494
|
+
return placeholder;
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3497
|
+
return dialect.name === "mysql" ? `ROW(${cells.join(", ")}, ${ordinal})` : `(${cells.join(", ")}, ${ordinal})`;
|
|
3498
|
+
});
|
|
3499
|
+
const on = join.columns.map((column, index) => `${quoteCol(dialect, column)} = ${alias}.${memberNames[index]}`).join(" AND ");
|
|
3500
|
+
return `INNER JOIN (VALUES ${rows.join(", ")}) AS ${alias} (${[...memberNames, dialect.quoteIdent({ name: join.ordinalAlias })].join(", ")}) ON ${on}`;
|
|
3501
|
+
}
|
|
2601
3502
|
function renderSelect(params) {
|
|
2602
3503
|
const { dialect, statement, collector } = params;
|
|
2603
3504
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
@@ -2615,6 +3516,11 @@ function renderSelect(params) {
|
|
|
2615
3516
|
columnList.push(`${renderAggRef(dialect, selection.agg)} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
2616
3517
|
}
|
|
2617
3518
|
}
|
|
3519
|
+
if (statement.countSubqueries !== undefined) {
|
|
3520
|
+
for (const subquery of statement.countSubqueries) {
|
|
3521
|
+
columnList.push(renderCountSubquery({ dialect, subquery, collector }));
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
2618
3524
|
if (statement.rowNumber !== undefined) {
|
|
2619
3525
|
const rowNumber = statement.rowNumber;
|
|
2620
3526
|
if (rowNumber.partitionBy.length === 0) {
|
|
@@ -2627,13 +3533,13 @@ function renderSelect(params) {
|
|
|
2627
3533
|
let distinctPrefix = "";
|
|
2628
3534
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
2629
3535
|
if (!dialect.capabilities.distinctOn) {
|
|
2630
|
-
throw
|
|
3536
|
+
throw unsupportedCapability16({
|
|
2631
3537
|
dialect: dialect.name,
|
|
2632
3538
|
feature: "SELECT DISTINCT ON",
|
|
2633
3539
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
2634
3540
|
});
|
|
2635
3541
|
}
|
|
2636
|
-
distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, column)).join(", ")}) `;
|
|
3542
|
+
distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, typedOrderRef({ statement, column }))).join(", ")}) `;
|
|
2637
3543
|
}
|
|
2638
3544
|
const columns = columnList.length === 0 ? "*" : columnList.join(", ");
|
|
2639
3545
|
let from;
|
|
@@ -2656,9 +3562,12 @@ function renderSelect(params) {
|
|
|
2656
3562
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
2657
3563
|
}
|
|
2658
3564
|
}
|
|
3565
|
+
if (statement.keyTable !== undefined) {
|
|
3566
|
+
text += ` ${renderKeyTableJoin({ dialect, join: statement.keyTable, collector })}`;
|
|
3567
|
+
}
|
|
2659
3568
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
2660
3569
|
if (!dialect.capabilities.lateralJoin) {
|
|
2661
|
-
throw
|
|
3570
|
+
throw unsupportedCapability16({
|
|
2662
3571
|
dialect: dialect.name,
|
|
2663
3572
|
feature: "LATERAL JOIN relation loading",
|
|
2664
3573
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -2678,7 +3587,7 @@ function renderSelect(params) {
|
|
|
2678
3587
|
text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
|
|
2679
3588
|
}
|
|
2680
3589
|
if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
|
|
2681
|
-
const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term, collector }));
|
|
3590
|
+
const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term: "column" in term ? { ...term, column: typedOrderRef({ statement, column: term.column }) } : term, collector }));
|
|
2682
3591
|
text += ` ORDER BY ${terms.join(", ")}`;
|
|
2683
3592
|
}
|
|
2684
3593
|
if (statement.limit !== undefined) {
|
|
@@ -2720,7 +3629,7 @@ function renderInsert(params) {
|
|
|
2720
3629
|
const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
2721
3630
|
const rowsSql = statement.rows.map((row) => `(${row.map((value) => {
|
|
2722
3631
|
if (isJsonParamCell(value))
|
|
2723
|
-
return `${collector.add(value.text)}${dialect
|
|
3632
|
+
return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
2724
3633
|
if (isDbNowMarker(value)) {
|
|
2725
3634
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
2726
3635
|
}
|
|
@@ -2743,7 +3652,7 @@ function renderInsert(params) {
|
|
|
2743
3652
|
let target = "";
|
|
2744
3653
|
if (onConflict.constraint !== undefined) {
|
|
2745
3654
|
if (dialect.name !== "postgres") {
|
|
2746
|
-
throw
|
|
3655
|
+
throw unsupportedCapability16({
|
|
2747
3656
|
dialect: dialect.name,
|
|
2748
3657
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
2749
3658
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -2758,7 +3667,7 @@ function renderInsert(params) {
|
|
|
2758
3667
|
}
|
|
2759
3668
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
2760
3669
|
} else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
2761
|
-
throw
|
|
3670
|
+
throw unsupportedCapability16({
|
|
2762
3671
|
dialect: dialect.name,
|
|
2763
3672
|
feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
|
|
2764
3673
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -2776,7 +3685,7 @@ function renderInsert(params) {
|
|
|
2776
3685
|
throw internalError2({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
2777
3686
|
}
|
|
2778
3687
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
2779
|
-
throw
|
|
3688
|
+
throw unsupportedCapability16({
|
|
2780
3689
|
dialect: dialect.name,
|
|
2781
3690
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
2782
3691
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
@@ -2792,14 +3701,14 @@ function renderInsert(params) {
|
|
|
2792
3701
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
2793
3702
|
} else {
|
|
2794
3703
|
if (onConflict.constraint !== undefined) {
|
|
2795
|
-
throw
|
|
3704
|
+
throw unsupportedCapability16({
|
|
2796
3705
|
dialect: dialect.name,
|
|
2797
3706
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
2798
3707
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
2799
3708
|
});
|
|
2800
3709
|
}
|
|
2801
3710
|
if (onConflict.conflictWhere !== undefined) {
|
|
2802
|
-
throw
|
|
3711
|
+
throw unsupportedCapability16({
|
|
2803
3712
|
dialect: dialect.name,
|
|
2804
3713
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
2805
3714
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -2852,7 +3761,7 @@ function renderAssignments(params) {
|
|
|
2852
3761
|
return `${column} = ${renderIncomingRowColumn({ dialect, column: value.column })}`;
|
|
2853
3762
|
}
|
|
2854
3763
|
if (isJsonParamCell(value)) {
|
|
2855
|
-
return `${column} = ${collector.add(value.text)}${dialect
|
|
3764
|
+
return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
2856
3765
|
}
|
|
2857
3766
|
if (isSetOperation(value)) {
|
|
2858
3767
|
const operand = collector.add(value.value);
|
|
@@ -2881,8 +3790,174 @@ function renderDelete(params) {
|
|
|
2881
3790
|
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
2882
3791
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
2883
3792
|
}
|
|
3793
|
+
var CHAIN_ROWS_ALIAS = "__vibe_rows";
|
|
3794
|
+
var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
3795
|
+
function renderChain(params) {
|
|
3796
|
+
const { dialect, statement, collector } = params;
|
|
3797
|
+
if (dialect.statementChain === null) {
|
|
3798
|
+
throw unsupportedCapability16({
|
|
3799
|
+
dialect: dialect.name,
|
|
3800
|
+
feature: "a statement chain (data-modifying WITH steps)",
|
|
3801
|
+
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
3802
|
+
});
|
|
3803
|
+
}
|
|
3804
|
+
if (statement.steps.length === 0)
|
|
3805
|
+
throw internalError2({ message: "a statement chain needs at least one step" });
|
|
3806
|
+
const defined = new Set;
|
|
3807
|
+
const steps = [];
|
|
3808
|
+
for (const step of statement.steps) {
|
|
3809
|
+
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
3810
|
+
throw internalError2({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
3811
|
+
}
|
|
3812
|
+
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
3813
|
+
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
3814
|
+
defined.add(step.name);
|
|
3815
|
+
}
|
|
3816
|
+
const result = statement.result;
|
|
3817
|
+
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
3818
|
+
throw internalError2({ message: "a chain result needs at least one source step and one column" });
|
|
3819
|
+
}
|
|
3820
|
+
for (const name of result.rowsFrom) {
|
|
3821
|
+
if (!defined.has(name))
|
|
3822
|
+
throw internalError2({ message: `chain result reads undefined step "${name}"` });
|
|
3823
|
+
}
|
|
3824
|
+
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3825
|
+
const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
|
|
3826
|
+
let select = union;
|
|
3827
|
+
if (result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
3828
|
+
const extra = [];
|
|
3829
|
+
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
3830
|
+
if (alias !== undefined && result.columns.includes(alias)) {
|
|
3831
|
+
throw internalError2({ message: `chain result alias "${alias}" collides with a result column` });
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
3835
|
+
const projected = result.columns.map((name) => `${rows}.${dialect.quoteIdent({ name })}`);
|
|
3836
|
+
if (result.verdict !== undefined) {
|
|
3837
|
+
extra.push(`${renderExpr({ dialect, expr: result.verdict.expr, collector })} AS ${dialect.quoteIdent({ name: result.verdict.alias })}`);
|
|
3838
|
+
}
|
|
3839
|
+
if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
3840
|
+
extra.push(renderTallyColumn({ dialect, result, defined, collector }));
|
|
3841
|
+
}
|
|
3842
|
+
select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
|
|
3843
|
+
}
|
|
3844
|
+
return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
|
|
3845
|
+
}
|
|
3846
|
+
function renderChainStep(params) {
|
|
3847
|
+
const { dialect, statement, collector } = params;
|
|
3848
|
+
switch (statement.kind) {
|
|
3849
|
+
case "select":
|
|
3850
|
+
return renderSelect({ dialect, statement, collector }).text;
|
|
3851
|
+
case "insert":
|
|
3852
|
+
return renderInsert({ dialect, statement, collector }).text;
|
|
3853
|
+
case "insertSelect":
|
|
3854
|
+
return renderInsertSelect({ dialect, statement, collector, earlier: params.earlier });
|
|
3855
|
+
case "update":
|
|
3856
|
+
return renderUpdate({ dialect, statement, collector }).text;
|
|
3857
|
+
case "delete":
|
|
3858
|
+
return renderDelete({ dialect, statement, collector }).text;
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
function renderSelectCell(params) {
|
|
3862
|
+
const { dialect, value, collector } = params;
|
|
3863
|
+
if (isJsonParamCell(value))
|
|
3864
|
+
return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
3865
|
+
if (isDbNowMarker(value))
|
|
3866
|
+
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
3867
|
+
if (isSqlDefaultCell(value)) {
|
|
3868
|
+
throw internalError2({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
3869
|
+
}
|
|
3870
|
+
return collector.add(value);
|
|
3871
|
+
}
|
|
3872
|
+
function renderRowSource(params) {
|
|
3873
|
+
const { dialect, source, collector } = params;
|
|
3874
|
+
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
3875
|
+
throw internalError2({ message: "a row source needs at least one column and one row" });
|
|
3876
|
+
}
|
|
3877
|
+
const typedBy = renderTable(dialect, source.typedBy);
|
|
3878
|
+
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
3879
|
+
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
3880
|
+
for (const row of source.rows) {
|
|
3881
|
+
if (row.length !== source.columns.length) {
|
|
3882
|
+
throw internalError2({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
3883
|
+
}
|
|
3884
|
+
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
3885
|
+
}
|
|
3886
|
+
const alias = dialect.quoteIdent({ name: source.alias });
|
|
3887
|
+
const columnList = columns.join(", ");
|
|
3888
|
+
return `(SELECT ${columnList} FROM (VALUES ${rows.join(", ")}) AS ${alias} (${columnList}) OFFSET 1) AS ${alias}`;
|
|
3889
|
+
}
|
|
3890
|
+
function renderTallyColumn(params) {
|
|
3891
|
+
const { dialect, result, defined, collector } = params;
|
|
3892
|
+
const tally = result.tally;
|
|
3893
|
+
if (tally === undefined)
|
|
3894
|
+
throw internalError2({ message: "a chain veto needs a tally column to guard" });
|
|
3895
|
+
if (tally.steps.length === 0)
|
|
3896
|
+
throw internalError2({ message: "a chain tally needs at least one step" });
|
|
3897
|
+
const step = (name) => {
|
|
3898
|
+
if (!defined.has(name))
|
|
3899
|
+
throw internalError2({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
3900
|
+
return dialect.quoteIdent({ name });
|
|
3901
|
+
};
|
|
3902
|
+
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
3903
|
+
const sum = tally.steps.map(count).join(" + ");
|
|
3904
|
+
const vetoes = result.veto ?? [];
|
|
3905
|
+
if (vetoes.length === 0)
|
|
3906
|
+
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
3907
|
+
const arms = vetoes.map((veto) => {
|
|
3908
|
+
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
3909
|
+
throw internalError2({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
3910
|
+
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
3911
|
+
throw internalError2({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
3912
|
+
const minRows = veto.minRows ?? 1;
|
|
3913
|
+
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
3914
|
+
throw internalError2({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
3915
|
+
const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
|
|
3916
|
+
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
3917
|
+
});
|
|
3918
|
+
return `CASE ${arms.join(" ")} ELSE ${sum} END AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
3919
|
+
}
|
|
3920
|
+
function renderInsertSelect(params) {
|
|
3921
|
+
const { dialect, statement, collector } = params;
|
|
3922
|
+
if (statement.columns.length === 0)
|
|
3923
|
+
throw internalError2({ message: "insert requires at least one column" });
|
|
3924
|
+
if (statement.cells.length !== statement.columns.length) {
|
|
3925
|
+
throw internalError2({
|
|
3926
|
+
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
const from = statement.from ?? [];
|
|
3930
|
+
for (const name of from) {
|
|
3931
|
+
if (!params.earlier.has(name))
|
|
3932
|
+
throw internalError2({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
3933
|
+
}
|
|
3934
|
+
const source = statement.source;
|
|
3935
|
+
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
3936
|
+
throw internalError2({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
3937
|
+
}
|
|
3938
|
+
const cells = statement.cells.map((value) => {
|
|
3939
|
+
if (isStepColumnRef(value)) {
|
|
3940
|
+
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
3941
|
+
throw internalError2({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
3942
|
+
}
|
|
3943
|
+
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
3944
|
+
}
|
|
3945
|
+
return renderSelectCell({ dialect, value, collector });
|
|
3946
|
+
});
|
|
3947
|
+
const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3948
|
+
let text = `INSERT INTO ${renderTable(dialect, statement.table)} (${columnList}) SELECT ${cells.join(", ")}`;
|
|
3949
|
+
const relations = from.map((name) => dialect.quoteIdent({ name }));
|
|
3950
|
+
if (source !== undefined)
|
|
3951
|
+
relations.push(renderRowSource({ dialect, source, collector }));
|
|
3952
|
+
if (relations.length > 0)
|
|
3953
|
+
text += ` FROM ${relations.join(" CROSS JOIN ")}`;
|
|
3954
|
+
if (statement.where !== undefined) {
|
|
3955
|
+
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
3956
|
+
}
|
|
3957
|
+
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
3958
|
+
}
|
|
2884
3959
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
2885
|
-
import { unsupportedCapability as
|
|
3960
|
+
import { unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
2886
3961
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
2887
3962
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
2888
3963
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -2909,13 +3984,13 @@ function parsePostgresTypeName(params) {
|
|
|
2909
3984
|
}
|
|
2910
3985
|
function renderPostgresTypeName(params) {
|
|
2911
3986
|
if (params.dialect.name !== "postgres")
|
|
2912
|
-
throw
|
|
3987
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
2913
3988
|
const { dialect, type } = params;
|
|
2914
3989
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
2915
3990
|
}
|
|
2916
3991
|
function renderPostgresTypeIdentity(params) {
|
|
2917
3992
|
if (params.dialect.name !== "postgres")
|
|
2918
|
-
throw
|
|
3993
|
+
throw unsupportedCapability17({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
2919
3994
|
const collector = new ParamCollector(params.dialect);
|
|
2920
3995
|
const { source } = params;
|
|
2921
3996
|
let text;
|
|
@@ -2928,11 +4003,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
2928
4003
|
return { text, values: collector.values, returning: "none" };
|
|
2929
4004
|
}
|
|
2930
4005
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
2931
|
-
import { unsupportedCapability as
|
|
4006
|
+
import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
2932
4007
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
2933
4008
|
const { dialect, table, column } = params;
|
|
2934
4009
|
if (dialect.name !== "postgres")
|
|
2935
|
-
throw
|
|
4010
|
+
throw unsupportedCapability18({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
2936
4011
|
const collector = new ParamCollector(dialect);
|
|
2937
4012
|
const namespace = collector.add(table.namespace);
|
|
2938
4013
|
const name = collector.add(table.name);
|
|
@@ -2956,28 +4031,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
2956
4031
|
};
|
|
2957
4032
|
}
|
|
2958
4033
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
2959
|
-
import { unsupportedCapability as
|
|
4034
|
+
import { unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
2960
4035
|
function renderConstraintOwnedIndexDrop(params) {
|
|
2961
4036
|
if (params.dialect.name !== "postgres")
|
|
2962
|
-
throw
|
|
4037
|
+
throw unsupportedCapability19({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
2963
4038
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
2964
4039
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
2965
4040
|
}
|
|
2966
4041
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
2967
|
-
import { unsupportedCapability as
|
|
4042
|
+
import { unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
2968
4043
|
function renderMysqlCheckProbe(params) {
|
|
2969
4044
|
if (params.dialect.name !== "mysql")
|
|
2970
|
-
throw
|
|
4045
|
+
throw unsupportedCapability20({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
2971
4046
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2972
4047
|
const probe = q(params.probe);
|
|
2973
4048
|
switch (params.operation) {
|
|
2974
4049
|
case "clone":
|
|
2975
4050
|
if (params.table === undefined)
|
|
2976
|
-
throw
|
|
4051
|
+
throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
2977
4052
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
2978
4053
|
case "add": {
|
|
2979
4054
|
if (params.expression === undefined || params.definition === undefined)
|
|
2980
|
-
throw
|
|
4055
|
+
throw unsupportedCapability20({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
2981
4056
|
const parts = tableDefinitionParts({ sql: params.definition });
|
|
2982
4057
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
2983
4058
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -3023,31 +4098,31 @@ function sqliteTableConstraints(params) {
|
|
|
3023
4098
|
return result;
|
|
3024
4099
|
}
|
|
3025
4100
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
3026
|
-
import { unsupportedCapability as
|
|
4101
|
+
import { unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
3027
4102
|
function renderMigrationVisibility(params) {
|
|
3028
4103
|
if (params.dialect.name !== "mysql")
|
|
3029
|
-
throw
|
|
4104
|
+
throw unsupportedCapability21({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
3030
4105
|
const collector = new ParamCollector(params.dialect);
|
|
3031
4106
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
3032
4107
|
return { text: `SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.USER_PRIVILEGES WHERE GRANTEE = ${grantee} UNION SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE = ${grantee} AND ${collector.add(params.namespace)} LIKE TABLE_SCHEMA`, values: collector.values, returning: "none" };
|
|
3033
4108
|
}
|
|
3034
4109
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
3035
|
-
import { unsupportedCapability as
|
|
4110
|
+
import { unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
3036
4111
|
function renderSqliteColumnInventory(params) {
|
|
3037
4112
|
if (params.dialect.name !== "sqlite")
|
|
3038
|
-
throw
|
|
4113
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
3039
4114
|
const collector = new ParamCollector(params.dialect);
|
|
3040
4115
|
return { text: `SELECT name, hidden, pk, dflt_value AS definition FROM pragma_table_xinfo(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY cid`, values: collector.values, returning: "none" };
|
|
3041
4116
|
}
|
|
3042
4117
|
function renderSqliteForeignKeyInventory(params) {
|
|
3043
4118
|
if (params.dialect.name !== "sqlite")
|
|
3044
|
-
throw
|
|
4119
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
3045
4120
|
const collector = new ParamCollector(params.dialect);
|
|
3046
4121
|
return { text: `SELECT id, seq, "from", "to", "table", on_update, on_delete, match FROM pragma_foreign_key_list(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY id, seq`, values: collector.values, returning: "none" };
|
|
3047
4122
|
}
|
|
3048
4123
|
function renderSqliteRebuildObjects(params) {
|
|
3049
4124
|
if (params.dialect.name !== "sqlite")
|
|
3050
|
-
throw
|
|
4125
|
+
throw unsupportedCapability22({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
3051
4126
|
return { text: "SELECT name, tbl_name, type, sql FROM main.sqlite_schema WHERE type IN ('index', 'trigger') AND sql IS NOT NULL ORDER BY type, name", values: [], returning: "none" };
|
|
3052
4127
|
}
|
|
3053
4128
|
function renderMigrationInventory(params) {
|
|
@@ -3086,7 +4161,7 @@ ORDER BY kind, parent, name`, values: collector.values, returning: "none" };
|
|
|
3086
4161
|
return { text: `SELECT type AS kind, name, tbl_name AS parent, sql AS definition FROM ${schema}.sqlite_schema WHERE name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND name <> '_vibe_migrations' ORDER BY type, tbl_name, name`, values: [], returning: "none" };
|
|
3087
4162
|
}
|
|
3088
4163
|
// packages/sql/src/dialects/migration-objects.ts
|
|
3089
|
-
import { internalError as internalError3, unsupportedCapability as
|
|
4164
|
+
import { internalError as internalError3, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
3090
4165
|
function renderMigrationObjectQuery(params) {
|
|
3091
4166
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
3092
4167
|
}
|
|
@@ -3094,7 +4169,7 @@ function objectQuery(params) {
|
|
|
3094
4169
|
const { dialect, identity: object, collector } = params;
|
|
3095
4170
|
if (object.kind === "namespace") {
|
|
3096
4171
|
if (dialect.name !== "postgres")
|
|
3097
|
-
throw
|
|
4172
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3098
4173
|
return { text: `SELECT n.nspname::text AS "name" FROM pg_catalog.pg_namespace n WHERE n.nspname = ${collector.add(object.name)}`, values: collector.values, returning: "none" };
|
|
3099
4174
|
}
|
|
3100
4175
|
const physical = "table" in object ? object.table : object.name;
|
|
@@ -3158,7 +4233,7 @@ function objectQuery(params) {
|
|
|
3158
4233
|
break;
|
|
3159
4234
|
case "constraint":
|
|
3160
4235
|
if (object.constraintType === "exclusion")
|
|
3161
|
-
throw
|
|
4236
|
+
throw unsupportedCapability23({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
3162
4237
|
text = `SELECT tc.CONSTRAINT_TYPE AS type, tc.ENFORCED AS enforced, cc.CHECK_CLAUSE AS definition, rc.UPDATE_RULE AS onUpdate, rc.DELETE_RULE AS onDelete, rc.MATCH_OPTION AS matchOption FROM information_schema.TABLE_CONSTRAINTS tc LEFT JOIN information_schema.CHECK_CONSTRAINTS cc ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS rc ON rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND rc.TABLE_NAME = tc.TABLE_NAME AND rc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME WHERE tc.TABLE_SCHEMA = ${namespace} AND tc.TABLE_NAME = ${name} AND tc.CONSTRAINT_NAME = ${collector.add(object.name)}`;
|
|
3163
4238
|
break;
|
|
3164
4239
|
case "index":
|
|
@@ -3168,15 +4243,15 @@ function objectQuery(params) {
|
|
|
3168
4243
|
text = `SELECT TABLE_TYPE AS type, ENGINE AS engine, TABLE_COLLATION AS collation FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${namespace} AND TABLE_NAME = ${name}`;
|
|
3169
4244
|
break;
|
|
3170
4245
|
default:
|
|
3171
|
-
throw
|
|
4246
|
+
throw unsupportedCapability23({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
3172
4247
|
}
|
|
3173
4248
|
} else {
|
|
3174
4249
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
3175
|
-
throw
|
|
4250
|
+
throw unsupportedCapability23({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
3176
4251
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
3177
4252
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
3178
4253
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
3179
|
-
throw
|
|
4254
|
+
throw unsupportedCapability23({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
3180
4255
|
const sqliteParams = new ParamCollector(dialect);
|
|
3181
4256
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
3182
4257
|
text = `SELECT x.type AS type, x."notnull" AS "notNull", x.pk AS primaryKeyPosition, x.hidden AS generated, x.dflt_value AS definition, (SELECT sql FROM ${catalog} WHERE name = ${sqliteParams.add(physical.name)} AND type = 'table') AS tableDefinition FROM pragma_table_xinfo(${sqliteParams.add(physical.name)}, ${sqliteParams.add(physical.namespace)}) x WHERE x.name = ${sqliteParams.add(object.column)}`;
|
|
@@ -3206,7 +4281,7 @@ function renderMigrationDependencies(params) {
|
|
|
3206
4281
|
}
|
|
3207
4282
|
function dependencyQuery(params) {
|
|
3208
4283
|
if (params.dialect.name !== "postgres")
|
|
3209
|
-
throw
|
|
4284
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
3210
4285
|
const object = params.identity;
|
|
3211
4286
|
const collector = params.collector;
|
|
3212
4287
|
if (object.kind === "namespace") {
|
|
@@ -3263,7 +4338,7 @@ function inspectionReferences(params) {
|
|
|
3263
4338
|
function renderMigrationObjectBatch(params) {
|
|
3264
4339
|
const { dialect } = params;
|
|
3265
4340
|
if (dialect.name !== "postgres")
|
|
3266
|
-
throw
|
|
4341
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
3267
4342
|
const groups = new Map;
|
|
3268
4343
|
for (const [position, identity] of params.identities.entries()) {
|
|
3269
4344
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -3296,16 +4371,16 @@ function renderMigrationNamespace(params) {
|
|
|
3296
4371
|
}
|
|
3297
4372
|
function renderCheckProbe(params) {
|
|
3298
4373
|
if (params.dialect.name !== "postgres")
|
|
3299
|
-
throw
|
|
4374
|
+
throw unsupportedCapability23({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
3300
4375
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
3301
4376
|
return { text: `ALTER TABLE ${q(params.table.namespace)}.${q(params.table.name)} ADD CONSTRAINT ${q(params.name)} CHECK (${params.expression}) NOT VALID`, values: [], returning: "none" };
|
|
3302
4377
|
}
|
|
3303
4378
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
3304
|
-
import { VibeError as
|
|
4379
|
+
import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3305
4380
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
3306
4381
|
function requirePostgres2(params) {
|
|
3307
4382
|
if (params.dialect.name !== "postgres") {
|
|
3308
|
-
throw
|
|
4383
|
+
throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.feature });
|
|
3309
4384
|
}
|
|
3310
4385
|
}
|
|
3311
4386
|
function renderMigrationSessionRead(params) {
|
|
@@ -3320,7 +4395,7 @@ function renderMigrationSessionSet(params) {
|
|
|
3320
4395
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
3321
4396
|
const { milliseconds, setting } = params;
|
|
3322
4397
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
3323
|
-
throw new
|
|
4398
|
+
throw new VibeError15({
|
|
3324
4399
|
code: "VIBE_MIGRATION",
|
|
3325
4400
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
3326
4401
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -3410,6 +4485,8 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
3410
4485
|
export {
|
|
3411
4486
|
ADVISORY_KEY_BOUNDS,
|
|
3412
4487
|
ADVISORY_LOCK_RESULT_COLUMN,
|
|
4488
|
+
CHAIN_VETO_PREFIX,
|
|
4489
|
+
CHAIN_VETO_TOKEN_PATTERN,
|
|
3413
4490
|
DIALECT_CAPABILITIES,
|
|
3414
4491
|
DIALECT_CODECS,
|
|
3415
4492
|
JSON_TRANSPORT,
|
|
@@ -3431,37 +4508,65 @@ export {
|
|
|
3431
4508
|
SQLITE_SESSION_SQL,
|
|
3432
4509
|
SQL_DEFAULT_CELL,
|
|
3433
4510
|
SQL_DIALECTS,
|
|
4511
|
+
assertSqlParamBuilding,
|
|
4512
|
+
chainVetoToken,
|
|
3434
4513
|
compileExclusion,
|
|
4514
|
+
compileReusableSqlCommand,
|
|
4515
|
+
compileReusableSqlQuery,
|
|
3435
4516
|
compileSql,
|
|
4517
|
+
compileSqlCommand,
|
|
4518
|
+
compileSqlQuery,
|
|
3436
4519
|
containsPattern,
|
|
4520
|
+
createReusableSqlCommand,
|
|
4521
|
+
createReusableSqlQuery,
|
|
4522
|
+
createSqlCommand,
|
|
4523
|
+
createSqlInput,
|
|
4524
|
+
createSqlInputScope,
|
|
4525
|
+
createSqlQuery,
|
|
3437
4526
|
dbNowMarker,
|
|
3438
4527
|
decode,
|
|
3439
4528
|
decodeIsNoop,
|
|
3440
4529
|
describeRowLock,
|
|
4530
|
+
encode,
|
|
3441
4531
|
endsWithPattern,
|
|
3442
4532
|
enumMembershipCodec,
|
|
3443
4533
|
escapeGlobPattern,
|
|
3444
4534
|
escapeLikePattern,
|
|
3445
4535
|
excludedRef,
|
|
4536
|
+
float32Codec,
|
|
3446
4537
|
getSqlDialect,
|
|
3447
4538
|
isDbNowMarker,
|
|
3448
4539
|
isExcludedRef,
|
|
3449
4540
|
isFixedDiagnostic,
|
|
4541
|
+
isJsonListParameter,
|
|
3450
4542
|
isJsonParamCell,
|
|
3451
4543
|
isListSetOperation,
|
|
3452
4544
|
isNativeEnumArrayParameter,
|
|
4545
|
+
isReusableSqlCommand,
|
|
4546
|
+
isReusableSqlQuery,
|
|
3453
4547
|
isSetOperation,
|
|
4548
|
+
isSqlBindingCollector,
|
|
4549
|
+
isSqlCommand,
|
|
3454
4550
|
isSqlDefaultCell,
|
|
3455
4551
|
isSqlFragment,
|
|
4552
|
+
isSqlParam,
|
|
4553
|
+
isSqlQuery,
|
|
4554
|
+
isStepColumnRef,
|
|
4555
|
+
jsonListParameter,
|
|
3456
4556
|
jsonParamCell,
|
|
3457
4557
|
mapConflictColumns,
|
|
3458
4558
|
mysqlDialect,
|
|
3459
4559
|
nativeEnumArrayParameter,
|
|
3460
4560
|
parseDateTimeText,
|
|
4561
|
+
parseJsonText,
|
|
3461
4562
|
parsePgArrayLiteral,
|
|
4563
|
+
parsePgArrayLiteralNested,
|
|
3462
4564
|
parsePostgresTypeName,
|
|
4565
|
+
postgresArrayElementFromText,
|
|
3463
4566
|
postgresDialect,
|
|
3464
4567
|
projectionOutputName,
|
|
4568
|
+
recordSqlBinding,
|
|
4569
|
+
recordSqlBindingCheck,
|
|
3465
4570
|
renderAdvisoryLock,
|
|
3466
4571
|
renderCheckProbe,
|
|
3467
4572
|
renderColumnStateQuery,
|
|
@@ -3501,6 +4606,7 @@ export {
|
|
|
3501
4606
|
renderPostgresTypeName,
|
|
3502
4607
|
renderPostgresWorkload,
|
|
3503
4608
|
renderPredicate,
|
|
4609
|
+
renderProjection,
|
|
3504
4610
|
renderRelationStateQuery,
|
|
3505
4611
|
renderReplayCoverageGaps,
|
|
3506
4612
|
renderRlsColumnType,
|
|
@@ -3521,17 +4627,28 @@ export {
|
|
|
3521
4627
|
renderSqlFragment,
|
|
3522
4628
|
renderSqliteColumnInventory,
|
|
3523
4629
|
renderSqliteForeignKeyInventory,
|
|
4630
|
+
renderSqliteFtsUpdateTrigger,
|
|
3524
4631
|
renderSqliteRebuildObjects,
|
|
3525
4632
|
renderStatement,
|
|
4633
|
+
resultScalarContract,
|
|
4634
|
+
returningColumnName,
|
|
4635
|
+
sealSqlInputScope,
|
|
4636
|
+
shortestFloat32,
|
|
4637
|
+
snapshotSqlValue,
|
|
3526
4638
|
sql,
|
|
4639
|
+
sqlInputContract,
|
|
4640
|
+
sqlParamContract,
|
|
3527
4641
|
sqlUsesIncoming,
|
|
3528
4642
|
sqliteColumnClauses,
|
|
3529
4643
|
sqliteDialect,
|
|
4644
|
+
sqliteFtsUpdateUpgrade,
|
|
3530
4645
|
sqliteTableConstraints,
|
|
3531
4646
|
startsWithPattern,
|
|
4647
|
+
stepColumnRef,
|
|
3532
4648
|
strictMatchPattern,
|
|
3533
4649
|
tableDefinitionParts,
|
|
4650
|
+
timeOfDayCodec,
|
|
3534
4651
|
toPgArrayLiteral
|
|
3535
4652
|
};
|
|
3536
4653
|
|
|
3537
|
-
//# debugId=
|
|
4654
|
+
//# debugId=B8D70B32D9F5E99064756E2164756E21
|