@dbsp/adapter-pgsql 1.10.2 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -2
- package/dist/index.js +1060 -256
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -94,8 +94,373 @@ var init_naming_plugin = __esm({
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
// src/db-type.ts
|
|
98
|
+
function splitDbType(input) {
|
|
99
|
+
const trimmed = input.trim();
|
|
100
|
+
let rest = trimmed;
|
|
101
|
+
let isArray = false;
|
|
102
|
+
if (rest.endsWith("[]")) {
|
|
103
|
+
isArray = true;
|
|
104
|
+
rest = rest.slice(0, -2).trimEnd();
|
|
105
|
+
}
|
|
106
|
+
const lowerRest = rest.toLowerCase();
|
|
107
|
+
for (const suffix of TEMPORAL_TZ_SUFFIXES) {
|
|
108
|
+
if (!lowerRest.endsWith(suffix)) continue;
|
|
109
|
+
const beforeSuffix = rest.slice(0, -suffix.length).trimEnd();
|
|
110
|
+
const temporal = splitTrailingModifier(beforeSuffix);
|
|
111
|
+
const temporalBase = normalizeBase(temporal.base).toLowerCase();
|
|
112
|
+
if (temporalBase === "timestamp" || temporalBase === "time") {
|
|
113
|
+
const base2 = normalizeBase(`${temporal.base}${suffix}`);
|
|
114
|
+
return {
|
|
115
|
+
base: base2,
|
|
116
|
+
modifier: temporal.modifier,
|
|
117
|
+
isArray,
|
|
118
|
+
schemaQualified: isSchemaQualified(base2)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const split = splitTrailingModifier(rest);
|
|
123
|
+
const base = normalizeBase(split.base);
|
|
124
|
+
return {
|
|
125
|
+
base,
|
|
126
|
+
modifier: split.modifier,
|
|
127
|
+
isArray,
|
|
128
|
+
schemaQualified: isSchemaQualified(base)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function splitTrailingModifier(input) {
|
|
132
|
+
const match = input.match(/^(.*)\(([^()]*)\)$/);
|
|
133
|
+
if (!match) return { base: input.trim(), modifier: void 0 };
|
|
134
|
+
return {
|
|
135
|
+
base: (match[1] ?? "").trimEnd(),
|
|
136
|
+
modifier: (match[2] ?? "").trim()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function normalizeBase(base) {
|
|
140
|
+
const trimmed = base.trim();
|
|
141
|
+
return trimmed.includes('"') ? trimmed : trimmed.replace(/\s+/g, " ");
|
|
142
|
+
}
|
|
143
|
+
function isSchemaQualified(base) {
|
|
144
|
+
return QUALIFIED_IDENTIFIER_RE.test(base);
|
|
145
|
+
}
|
|
146
|
+
function canonicalizeDbType(type) {
|
|
147
|
+
const split = splitDbType(type);
|
|
148
|
+
const base = normalizeBase(split.base);
|
|
149
|
+
const canonicalBase = base.includes('"') ? base : TYPE_ALIASES.get(base.toLowerCase()) ?? base.toLowerCase();
|
|
150
|
+
const modifier = split.modifier === void 0 ? "" : `(${split.modifier.replace(/\s*,\s*/g, ",").trim()})`;
|
|
151
|
+
const arraySuffix = split.isArray ? "[]" : "";
|
|
152
|
+
return `${canonicalBase}${modifier}${arraySuffix}`;
|
|
153
|
+
}
|
|
154
|
+
function dbTypeCastTarget(originalDbType) {
|
|
155
|
+
const split = splitDbType(originalDbType);
|
|
156
|
+
const base = normalizeBase(split.base).toLowerCase();
|
|
157
|
+
const hasModifier = split.modifier !== void 0;
|
|
158
|
+
const arraySuffix = split.isArray ? "[]" : "";
|
|
159
|
+
if (base === "varchar" || base === "character varying") {
|
|
160
|
+
return hasModifier ? `varchar${arraySuffix}` : originalDbType;
|
|
161
|
+
}
|
|
162
|
+
if (base === "char" || base === "character" || base === "bpchar") {
|
|
163
|
+
return `text${arraySuffix}`;
|
|
164
|
+
}
|
|
165
|
+
if (base === "bit" || base === "bit varying" || base === "varbit") {
|
|
166
|
+
return hasModifier ? `bit varying${arraySuffix}` : originalDbType;
|
|
167
|
+
}
|
|
168
|
+
if (base === "numeric" || base === "decimal") {
|
|
169
|
+
return hasModifier ? `numeric${arraySuffix}` : originalDbType;
|
|
170
|
+
}
|
|
171
|
+
if (base === "timestamp with time zone" || base === "timestamptz") {
|
|
172
|
+
return hasModifier ? `timestamp with time zone${arraySuffix}` : originalDbType;
|
|
173
|
+
}
|
|
174
|
+
if (base === "timestamp without time zone") {
|
|
175
|
+
return hasModifier ? `timestamp without time zone${arraySuffix}` : originalDbType;
|
|
176
|
+
}
|
|
177
|
+
if (base === "timestamp") {
|
|
178
|
+
return hasModifier ? `timestamp without time zone${arraySuffix}` : originalDbType;
|
|
179
|
+
}
|
|
180
|
+
if (base === "time with time zone" || base === "timetz") {
|
|
181
|
+
return hasModifier ? `time with time zone${arraySuffix}` : originalDbType;
|
|
182
|
+
}
|
|
183
|
+
if (base === "time without time zone") {
|
|
184
|
+
return hasModifier ? `time without time zone${arraySuffix}` : originalDbType;
|
|
185
|
+
}
|
|
186
|
+
if (base === "time") {
|
|
187
|
+
return hasModifier ? `time${arraySuffix}` : originalDbType;
|
|
188
|
+
}
|
|
189
|
+
if (INTERVAL_BASE_RE.test(base)) {
|
|
190
|
+
return hasModifier ? `${base}${arraySuffix}` : originalDbType;
|
|
191
|
+
}
|
|
192
|
+
return originalDbType;
|
|
193
|
+
}
|
|
194
|
+
function dbTypesEqual(a, b) {
|
|
195
|
+
return canonicalizeDbType(a) === canonicalizeDbType(b);
|
|
196
|
+
}
|
|
197
|
+
function isPgBuiltInTypeName(type) {
|
|
198
|
+
const split = splitDbType(type);
|
|
199
|
+
const base = normalizeBase(split.base);
|
|
200
|
+
if (base.includes('"') || split.schemaQualified) return false;
|
|
201
|
+
const baseLower = base.toLowerCase();
|
|
202
|
+
return MULTIWORD_BASE_TYPES.has(baseLower) || INTERVAL_BASE_RE.test(base) || BUILTIN_BASE_TYPES.has(baseLower);
|
|
203
|
+
}
|
|
204
|
+
function quoteTypeIdentifier(rawIdentifier) {
|
|
205
|
+
if (SAFE_LOWERCASE_IDENTIFIER_RE.test(rawIdentifier)) return rawIdentifier;
|
|
206
|
+
return `"${rawIdentifier.replace(/"/g, '""')}"`;
|
|
207
|
+
}
|
|
208
|
+
function unquoteIdentifier(id) {
|
|
209
|
+
const t = id.trim();
|
|
210
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
211
|
+
return t.slice(1, -1).replace(/""/g, '"');
|
|
212
|
+
}
|
|
213
|
+
return t;
|
|
214
|
+
}
|
|
215
|
+
function enumReferenceKind(originalDbType, typname) {
|
|
216
|
+
const split = splitDbType(originalDbType);
|
|
217
|
+
if (split.schemaQualified) return null;
|
|
218
|
+
if (unquoteIdentifier(normalizeBase(split.base)) !== typname) return null;
|
|
219
|
+
return split.isArray ? "array" : "scalar";
|
|
220
|
+
}
|
|
221
|
+
function validateDbType(type) {
|
|
222
|
+
if (typeof type !== "string") {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Unsafe database type name: expected a string, received ${typeof type}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (!isStructurallyValidDbType(type)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Unsafe database type name: "${type}". Must be a structurally valid PostgreSQL type name.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return type;
|
|
233
|
+
}
|
|
234
|
+
function isStructurallyValidDbType(type) {
|
|
235
|
+
const trimmed = type.trim();
|
|
236
|
+
if (trimmed.length === 0 || trimmed !== type) return false;
|
|
237
|
+
if (/[;]|--|\/\*|\*\//.test(trimmed)) return false;
|
|
238
|
+
if (trimmed.endsWith("[][]")) return false;
|
|
239
|
+
if (hasForbiddenTypeKeyword(trimmed)) return false;
|
|
240
|
+
const split = splitDbType(trimmed);
|
|
241
|
+
const base = split.base;
|
|
242
|
+
const baseLower = base.toLowerCase();
|
|
243
|
+
if (split.modifier !== void 0 && !isSafeModifier(split.modifier, baseLower)) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
if (MULTIWORD_BASE_TYPES.has(baseLower)) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (INTERVAL_BASE_RE.test(base)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return IDENTIFIER_RE.test(base) || split.schemaQualified;
|
|
253
|
+
}
|
|
254
|
+
function isSafeModifier(modifier, baseLower) {
|
|
255
|
+
const structurallySafe = modifier.length > 0 && !/[();]|--|\/\*|\*\//.test(modifier) && !MODIFIER_OPERATOR_RE.test(modifier) && MODIFIER_CONTENT_RE.test(modifier) && !hasForbiddenTypeKeyword(modifier);
|
|
256
|
+
if (!structurallySafe) return false;
|
|
257
|
+
if (PRECISION_SCALE_BUILTINS.has(baseLower)) {
|
|
258
|
+
return /^\d+(\s*,\s*-?\d+)?$/.test(modifier);
|
|
259
|
+
}
|
|
260
|
+
if (SINGLE_INT_MODIFIER_BUILTINS.has(baseLower) || INTERVAL_BASE_RE.test(baseLower)) {
|
|
261
|
+
return /^\d+$/.test(modifier.trim());
|
|
262
|
+
}
|
|
263
|
+
if (BUILTIN_BASE_TYPES.has(baseLower) || MULTIWORD_BASE_TYPES.has(baseLower)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
function hasForbiddenTypeKeyword(type) {
|
|
269
|
+
const withoutQuotedIdentifiers = type.replace(QUOTED_IDENTIFIER_RE, '""');
|
|
270
|
+
return /\bnot\s+null\b|\b(default|references|check|primary|unique|constraint|select|insert|update|delete|drop|alter|create|truncate|grant|revoke|copy)\b/i.test(
|
|
271
|
+
withoutQuotedIdentifiers
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
var TEMPORAL_TZ_SUFFIXES, SAFE_LOWERCASE_IDENTIFIER_RE, MULTIWORD_BASE_TYPES, TYPE_ALIASES, BUILTIN_BASE_TYPES, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER, IDENTIFIER_RE, QUALIFIED_IDENTIFIER_RE, QUOTED_IDENTIFIER_RE, INTERVAL_FIELD, INTERVAL_BASE_RE, MODIFIER_CONTENT_RE, MODIFIER_OPERATOR_RE, SINGLE_INT_MODIFIER_BUILTINS, PRECISION_SCALE_BUILTINS;
|
|
275
|
+
var init_db_type = __esm({
|
|
276
|
+
"src/db-type.ts"() {
|
|
277
|
+
"use strict";
|
|
278
|
+
TEMPORAL_TZ_SUFFIXES = [" without time zone", " with time zone"];
|
|
279
|
+
SAFE_LOWERCASE_IDENTIFIER_RE = /^[a-z_][a-z0-9_]*$/;
|
|
280
|
+
MULTIWORD_BASE_TYPES = /* @__PURE__ */ new Set([
|
|
281
|
+
"timestamp with time zone",
|
|
282
|
+
"timestamp without time zone",
|
|
283
|
+
"time with time zone",
|
|
284
|
+
"time without time zone",
|
|
285
|
+
"double precision",
|
|
286
|
+
"character varying",
|
|
287
|
+
"bit varying"
|
|
288
|
+
]);
|
|
289
|
+
TYPE_ALIASES = /* @__PURE__ */ new Map([
|
|
290
|
+
["character varying", "varchar"],
|
|
291
|
+
["character", "char"],
|
|
292
|
+
["bpchar", "char"],
|
|
293
|
+
["bit varying", "varbit"],
|
|
294
|
+
["timestamp with time zone", "timestamptz"],
|
|
295
|
+
["timestamp without time zone", "timestamp"],
|
|
296
|
+
["time with time zone", "timetz"],
|
|
297
|
+
["time without time zone", "time"],
|
|
298
|
+
["int", "integer"],
|
|
299
|
+
["int4", "integer"],
|
|
300
|
+
["int8", "bigint"],
|
|
301
|
+
["int2", "smallint"],
|
|
302
|
+
["float", "double precision"],
|
|
303
|
+
["float8", "double precision"],
|
|
304
|
+
["float4", "real"],
|
|
305
|
+
["bool", "boolean"],
|
|
306
|
+
["decimal", "numeric"],
|
|
307
|
+
["serial", "integer"],
|
|
308
|
+
["serial4", "integer"],
|
|
309
|
+
["bigserial", "bigint"],
|
|
310
|
+
["serial8", "bigint"],
|
|
311
|
+
["smallserial", "smallint"],
|
|
312
|
+
["serial2", "smallint"]
|
|
313
|
+
]);
|
|
314
|
+
BUILTIN_BASE_TYPES = /* @__PURE__ */ new Set([
|
|
315
|
+
...TYPE_ALIASES.keys(),
|
|
316
|
+
...TYPE_ALIASES.values(),
|
|
317
|
+
"bigint",
|
|
318
|
+
"bit",
|
|
319
|
+
"boolean",
|
|
320
|
+
"box",
|
|
321
|
+
"bytea",
|
|
322
|
+
"char",
|
|
323
|
+
"cidr",
|
|
324
|
+
"circle",
|
|
325
|
+
"date",
|
|
326
|
+
"float",
|
|
327
|
+
"inet",
|
|
328
|
+
"integer",
|
|
329
|
+
"interval",
|
|
330
|
+
"json",
|
|
331
|
+
"jsonb",
|
|
332
|
+
"line",
|
|
333
|
+
"lseg",
|
|
334
|
+
"macaddr",
|
|
335
|
+
"macaddr8",
|
|
336
|
+
"money",
|
|
337
|
+
"numeric",
|
|
338
|
+
"path",
|
|
339
|
+
"pg_lsn",
|
|
340
|
+
"point",
|
|
341
|
+
"polygon",
|
|
342
|
+
"real",
|
|
343
|
+
"text",
|
|
344
|
+
"time",
|
|
345
|
+
"timestamp",
|
|
346
|
+
"timetz",
|
|
347
|
+
"timestamptz",
|
|
348
|
+
"tsquery",
|
|
349
|
+
"tsvector",
|
|
350
|
+
"txid_snapshot",
|
|
351
|
+
"uuid",
|
|
352
|
+
"varbit",
|
|
353
|
+
"varchar",
|
|
354
|
+
"xml",
|
|
355
|
+
"jsonpath",
|
|
356
|
+
"name",
|
|
357
|
+
"oid",
|
|
358
|
+
"regclass",
|
|
359
|
+
"regtype",
|
|
360
|
+
"regrole",
|
|
361
|
+
"regnamespace",
|
|
362
|
+
"regproc",
|
|
363
|
+
"regprocedure",
|
|
364
|
+
"xid",
|
|
365
|
+
"xid8",
|
|
366
|
+
"cid",
|
|
367
|
+
"tid",
|
|
368
|
+
"daterange",
|
|
369
|
+
"int4range",
|
|
370
|
+
"int8range",
|
|
371
|
+
"numrange",
|
|
372
|
+
"tsrange",
|
|
373
|
+
"tstzrange",
|
|
374
|
+
"datemultirange",
|
|
375
|
+
"int4multirange",
|
|
376
|
+
"int8multirange",
|
|
377
|
+
"nummultirange",
|
|
378
|
+
"tsmultirange",
|
|
379
|
+
"tstzmultirange"
|
|
380
|
+
]);
|
|
381
|
+
UNQUOTED_IDENTIFIER = "[A-Za-z_][A-Za-z0-9_]*";
|
|
382
|
+
QUOTED_IDENTIFIER = '"(?:[^"]|"")+"';
|
|
383
|
+
IDENTIFIER_RE = new RegExp(
|
|
384
|
+
`^(?:${UNQUOTED_IDENTIFIER}|${QUOTED_IDENTIFIER})$`
|
|
385
|
+
);
|
|
386
|
+
QUALIFIED_IDENTIFIER_RE = new RegExp(
|
|
387
|
+
`^(?:${UNQUOTED_IDENTIFIER}|${QUOTED_IDENTIFIER})\\.(?:${UNQUOTED_IDENTIFIER}|${QUOTED_IDENTIFIER})$`
|
|
388
|
+
);
|
|
389
|
+
QUOTED_IDENTIFIER_RE = new RegExp(QUOTED_IDENTIFIER, "g");
|
|
390
|
+
INTERVAL_FIELD = "(?:year|month|day|hour|minute|second)";
|
|
391
|
+
INTERVAL_BASE_RE = new RegExp(
|
|
392
|
+
`^interval(?: ${INTERVAL_FIELD}(?: to ${INTERVAL_FIELD})?)?$`,
|
|
393
|
+
"i"
|
|
394
|
+
);
|
|
395
|
+
MODIFIER_CONTENT_RE = /^[A-Za-z0-9_,.\- ]+$/;
|
|
396
|
+
MODIFIER_OPERATOR_RE = /[=<>+*/%|&!@#^~]/;
|
|
397
|
+
SINGLE_INT_MODIFIER_BUILTINS = /* @__PURE__ */ new Set([
|
|
398
|
+
"varchar",
|
|
399
|
+
"character varying",
|
|
400
|
+
"char",
|
|
401
|
+
"character",
|
|
402
|
+
"bpchar",
|
|
403
|
+
// float(p): PostgreSQL treats float(1..24) as real, float(25..53) as double
|
|
404
|
+
// precision — a single-integer mantissa precision.
|
|
405
|
+
"float",
|
|
406
|
+
"bit",
|
|
407
|
+
"bit varying",
|
|
408
|
+
"varbit",
|
|
409
|
+
"timestamp",
|
|
410
|
+
"timestamptz",
|
|
411
|
+
"timestamp with time zone",
|
|
412
|
+
"timestamp without time zone",
|
|
413
|
+
"time",
|
|
414
|
+
"timetz",
|
|
415
|
+
"time with time zone",
|
|
416
|
+
"time without time zone",
|
|
417
|
+
"interval"
|
|
418
|
+
]);
|
|
419
|
+
PRECISION_SCALE_BUILTINS = /* @__PURE__ */ new Set(["numeric", "decimal"]);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
97
423
|
// src/validate.ts
|
|
424
|
+
function assertString(value, context) {
|
|
425
|
+
if (typeof value !== "string") {
|
|
426
|
+
throw new Error(`${context}: expected a string, received ${typeof value}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function assertNumericLiteral(value, context) {
|
|
430
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`${context}: expected a finite number or numeric string, received ${typeof value}`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const literal4 = typeof value === "number" ? String(value) : value;
|
|
436
|
+
if (typeof value === "number" && !Number.isFinite(value) || !SAFE_DECIMAL_LITERAL.test(literal4)) {
|
|
437
|
+
throw new Error(`${context}: unsafe numeric literal "${literal4}"`);
|
|
438
|
+
}
|
|
439
|
+
return literal4;
|
|
440
|
+
}
|
|
441
|
+
function formatStorageParameterValue(value, context) {
|
|
442
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`${context}: expected a string or number, received ${typeof value}`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (typeof value === "number") {
|
|
448
|
+
return assertNumericLiteral(value, context);
|
|
449
|
+
}
|
|
450
|
+
if (value.includes("\0")) {
|
|
451
|
+
throw new Error(`${context}: contains null byte`);
|
|
452
|
+
}
|
|
453
|
+
if (SAFE_DECIMAL_LITERAL.test(value) || SAFE_STORAGE_BOOLEAN_LITERAL.test(value) || SAFE_SIMPLE_IDENTIFIER_VALUE.test(value) && !isReservedKeyword(value) && value.toLowerCase() !== "null") {
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
457
|
+
}
|
|
98
458
|
function validateIdentifier(value, type) {
|
|
459
|
+
if (typeof value !== "string") {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Invalid ${type} identifier: expected a string, received ${typeof value}`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
99
464
|
if (!value || value.length === 0) {
|
|
100
465
|
throw new InvalidIdentifierError(value, type, "cannot be empty");
|
|
101
466
|
}
|
|
@@ -171,7 +536,12 @@ function validateIdentifiers(identifiers) {
|
|
|
171
536
|
function sanitizeForDisplay(value) {
|
|
172
537
|
return value.replace(/[\x00-\x1f\x7f]/g, "?").slice(0, 100);
|
|
173
538
|
}
|
|
539
|
+
function sanitizeCommentText(value) {
|
|
540
|
+
assertString(value, "SQL comment text");
|
|
541
|
+
return value.replace(/[\x00-\x1f\x7f]/g, "");
|
|
542
|
+
}
|
|
174
543
|
function validateExtensionName(name, context = "extension") {
|
|
544
|
+
assertString(name, `Invalid ${context} identifier`);
|
|
175
545
|
if (!name || name.length === 0) {
|
|
176
546
|
throw new InvalidIdentifierError(name, context, "cannot be empty");
|
|
177
547
|
}
|
|
@@ -257,6 +627,7 @@ function validateExtensionName(name, context = "extension") {
|
|
|
257
627
|
}
|
|
258
628
|
}
|
|
259
629
|
function validateCollationName(name, context = "collation") {
|
|
630
|
+
assertString(name, `Invalid ${context} identifier`);
|
|
260
631
|
if (!name || name.length === 0) {
|
|
261
632
|
throw new InvalidIdentifierError(name, context, "cannot be empty");
|
|
262
633
|
}
|
|
@@ -349,24 +720,113 @@ function validateCollationName(name, context = "collation") {
|
|
|
349
720
|
}
|
|
350
721
|
}
|
|
351
722
|
function validateSqlExpression(sql, context) {
|
|
723
|
+
assertString(sql, `Unsafe SQL expression in ${context}`);
|
|
352
724
|
if (/[;]|--|\/\*|\*\/|\$\$|\\/.test(sql)) {
|
|
353
725
|
throw new Error(
|
|
354
726
|
`Unsafe SQL expression in ${context}: contains forbidden characters (;, --, /*, */, "$$" (dollar-quoted strings), \\). Value: "${sql}"`
|
|
355
727
|
);
|
|
356
728
|
}
|
|
357
729
|
}
|
|
358
|
-
function
|
|
359
|
-
if (
|
|
730
|
+
function validateCheckExpression(sql, context) {
|
|
731
|
+
if (typeof sql !== "string") {
|
|
360
732
|
throw new Error(
|
|
361
|
-
`Unsafe
|
|
733
|
+
`Unsafe SQL expression in ${context}: expected a string, received ${typeof sql}`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
let i = 0;
|
|
737
|
+
let singleQuoted = false;
|
|
738
|
+
let dollarQuoteDelimiter;
|
|
739
|
+
while (i < sql.length) {
|
|
740
|
+
if (singleQuoted) {
|
|
741
|
+
if (sql[i] === "'") {
|
|
742
|
+
if (sql[i + 1] === "'") {
|
|
743
|
+
i += 2;
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
singleQuoted = false;
|
|
747
|
+
}
|
|
748
|
+
i++;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (dollarQuoteDelimiter) {
|
|
752
|
+
if (sql.startsWith(dollarQuoteDelimiter, i)) {
|
|
753
|
+
i += dollarQuoteDelimiter.length;
|
|
754
|
+
dollarQuoteDelimiter = void 0;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
i++;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (sql[i] === "'") {
|
|
761
|
+
singleQuoted = true;
|
|
762
|
+
i++;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const dollarDelimiter = readDollarQuoteDelimiter(sql, i);
|
|
766
|
+
if (dollarDelimiter) {
|
|
767
|
+
dollarQuoteDelimiter = dollarDelimiter;
|
|
768
|
+
i += dollarDelimiter.length;
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
const forbiddenToken = forbiddenCheckTokenAt(sql, i);
|
|
772
|
+
if (forbiddenToken) {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`Unsafe SQL expression in ${context}: contains forbidden token "${forbiddenToken}" outside string literal. Value: "${sql}"`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
i++;
|
|
778
|
+
}
|
|
779
|
+
if (singleQuoted) {
|
|
780
|
+
throw new Error(
|
|
781
|
+
`Unsafe SQL expression in ${context}: unterminated single-quoted string literal. Value: "${sql}"`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
if (dollarQuoteDelimiter) {
|
|
785
|
+
throw new Error(
|
|
786
|
+
`Unsafe SQL expression in ${context}: unterminated dollar-quoted string literal ${dollarQuoteDelimiter}. Value: "${sql}"`
|
|
362
787
|
);
|
|
363
788
|
}
|
|
364
|
-
return type;
|
|
365
789
|
}
|
|
366
|
-
|
|
790
|
+
function forbiddenCheckTokenAt(sql, index) {
|
|
791
|
+
const ch = sql[index];
|
|
792
|
+
if (ch === ";" || ch === "\\") return ch;
|
|
793
|
+
const pair = sql.slice(index, index + 2);
|
|
794
|
+
if (pair === "--" || pair === "/*" || pair === "*/") return pair;
|
|
795
|
+
return void 0;
|
|
796
|
+
}
|
|
797
|
+
function readDollarQuoteDelimiter(sql, index) {
|
|
798
|
+
if (sql[index] !== "$") return void 0;
|
|
799
|
+
const previous = index > 0 ? sql[index - 1] : void 0;
|
|
800
|
+
if (previous && isIdentifierContinuationBeforeDollarQuote(previous)) {
|
|
801
|
+
return void 0;
|
|
802
|
+
}
|
|
803
|
+
if (sql[index + 1] === "$") return "$$";
|
|
804
|
+
if (!isDollarQuoteTagStart(sql[index + 1])) return void 0;
|
|
805
|
+
let cursor = index + 2;
|
|
806
|
+
while (isDollarQuoteTagContinuation(sql[cursor])) {
|
|
807
|
+
cursor++;
|
|
808
|
+
}
|
|
809
|
+
if (sql[cursor] !== "$") return void 0;
|
|
810
|
+
return sql.slice(index, cursor + 1);
|
|
811
|
+
}
|
|
812
|
+
function isIdentifierContinuationBeforeDollarQuote(ch) {
|
|
813
|
+
return /[A-Za-z0-9_$]/.test(ch) || (ch.codePointAt(0) ?? 0) > 127;
|
|
814
|
+
}
|
|
815
|
+
function isDollarQuoteTagStart(ch) {
|
|
816
|
+
return ch !== void 0 && /[A-Za-z_]/.test(ch);
|
|
817
|
+
}
|
|
818
|
+
function isDollarQuoteTagContinuation(ch) {
|
|
819
|
+
return ch !== void 0 && /[A-Za-z0-9_]/.test(ch);
|
|
820
|
+
}
|
|
821
|
+
function validateDbTypeName(type) {
|
|
822
|
+
assertString(type, "Unsafe database type name");
|
|
823
|
+
return validateDbType(type);
|
|
824
|
+
}
|
|
825
|
+
var SQL_RESERVED_KEYWORDS, InvalidIdentifierError, SAFE_DECIMAL_LITERAL, SAFE_STORAGE_BOOLEAN_LITERAL, SAFE_SIMPLE_IDENTIFIER_VALUE;
|
|
367
826
|
var init_validate = __esm({
|
|
368
827
|
"src/validate.ts"() {
|
|
369
828
|
"use strict";
|
|
829
|
+
init_db_type();
|
|
370
830
|
SQL_RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
371
831
|
"all",
|
|
372
832
|
"analyse",
|
|
@@ -482,7 +942,9 @@ var init_validate = __esm({
|
|
|
482
942
|
identifierType;
|
|
483
943
|
reason;
|
|
484
944
|
};
|
|
485
|
-
|
|
945
|
+
SAFE_DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)$/;
|
|
946
|
+
SAFE_STORAGE_BOOLEAN_LITERAL = /^(?:true|false|on|off)$/i;
|
|
947
|
+
SAFE_SIMPLE_IDENTIFIER_VALUE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
486
948
|
}
|
|
487
949
|
});
|
|
488
950
|
|
|
@@ -1126,7 +1588,7 @@ var init_param_value = __esm({
|
|
|
1126
1588
|
});
|
|
1127
1589
|
|
|
1128
1590
|
// src/handlers/expression/case-value.ts
|
|
1129
|
-
function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler) {
|
|
1591
|
+
function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler, expressionHandler) {
|
|
1130
1592
|
if (value === null || value === void 0) {
|
|
1131
1593
|
return nullConstNode();
|
|
1132
1594
|
}
|
|
@@ -1160,7 +1622,8 @@ function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler
|
|
|
1160
1622
|
schema,
|
|
1161
1623
|
naming,
|
|
1162
1624
|
state,
|
|
1163
|
-
nestedCaseHandler
|
|
1625
|
+
nestedCaseHandler,
|
|
1626
|
+
expressionHandler
|
|
1164
1627
|
);
|
|
1165
1628
|
const right = resolveCaseValue(
|
|
1166
1629
|
expr.right,
|
|
@@ -1168,7 +1631,8 @@ function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler
|
|
|
1168
1631
|
schema,
|
|
1169
1632
|
naming,
|
|
1170
1633
|
state,
|
|
1171
|
-
nestedCaseHandler
|
|
1634
|
+
nestedCaseHandler,
|
|
1635
|
+
expressionHandler
|
|
1172
1636
|
);
|
|
1173
1637
|
return {
|
|
1174
1638
|
A_Expr: {
|
|
@@ -1186,15 +1650,29 @@ function resolveCaseValue(value, alias, schema, naming, state, nestedCaseHandler
|
|
|
1186
1650
|
}
|
|
1187
1651
|
// falls through
|
|
1188
1652
|
default: {
|
|
1653
|
+
if (expressionHandler && typeof expr.kind === "string" && EXPRESSION_HANDLER_KINDS.has(expr.kind)) {
|
|
1654
|
+
return expressionHandler(expr);
|
|
1655
|
+
}
|
|
1189
1656
|
return bindParameter(value, state);
|
|
1190
1657
|
}
|
|
1191
1658
|
}
|
|
1192
1659
|
}
|
|
1660
|
+
var EXPRESSION_HANDLER_KINDS;
|
|
1193
1661
|
var init_case_value = __esm({
|
|
1194
1662
|
"src/handlers/expression/case-value.ts"() {
|
|
1195
1663
|
"use strict";
|
|
1196
1664
|
init_ast_helpers();
|
|
1197
1665
|
init_param_value();
|
|
1666
|
+
EXPRESSION_HANDLER_KINDS = /* @__PURE__ */ new Set([
|
|
1667
|
+
"customOp",
|
|
1668
|
+
"customFn",
|
|
1669
|
+
"ref",
|
|
1670
|
+
"cast",
|
|
1671
|
+
"unary",
|
|
1672
|
+
"array",
|
|
1673
|
+
"subquery",
|
|
1674
|
+
"relationColumn"
|
|
1675
|
+
]);
|
|
1198
1676
|
}
|
|
1199
1677
|
});
|
|
1200
1678
|
|
|
@@ -2752,7 +3230,7 @@ var init_raw_expression_parser = __esm({
|
|
|
2752
3230
|
});
|
|
2753
3231
|
|
|
2754
3232
|
// src/compiler-utils.ts
|
|
2755
|
-
import { InvalidOperationError
|
|
3233
|
+
import { InvalidOperationError } from "@dbsp/core";
|
|
2756
3234
|
function inferPgArrayType(columnName, columnTypes, sampleValue) {
|
|
2757
3235
|
if (columnTypes?.[columnName]) {
|
|
2758
3236
|
const pgBase = mapToPgBaseType(columnTypes[columnName]);
|
|
@@ -2770,7 +3248,16 @@ function inferPgArrayType(columnName, columnTypes, sampleValue) {
|
|
|
2770
3248
|
return "text[]";
|
|
2771
3249
|
}
|
|
2772
3250
|
function mapToPgBaseType(pgType) {
|
|
2773
|
-
const
|
|
3251
|
+
const trimmedPgType = pgType.trim();
|
|
3252
|
+
try {
|
|
3253
|
+
validateDbType(trimmedPgType);
|
|
3254
|
+
} catch {
|
|
3255
|
+
throw new Error(
|
|
3256
|
+
`batchValues: invalid type name '${pgType}'. Must be a structurally valid PostgreSQL type name.`
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
const elementType = stripArraySuffix(trimmedPgType);
|
|
3260
|
+
const normalized = elementType.toUpperCase().replace(/\(.*\)/, "").trim();
|
|
2774
3261
|
switch (normalized) {
|
|
2775
3262
|
// ColumnType aliases (lowercase ColumnType values from ModelIR)
|
|
2776
3263
|
case "STRING":
|
|
@@ -2800,9 +3287,10 @@ function mapToPgBaseType(pgType) {
|
|
|
2800
3287
|
case "DOUBLE PRECISION":
|
|
2801
3288
|
case "FLOAT8":
|
|
2802
3289
|
case "FLOAT":
|
|
3290
|
+
return "float8";
|
|
2803
3291
|
case "NUMERIC":
|
|
2804
3292
|
case "DECIMAL":
|
|
2805
|
-
return "
|
|
3293
|
+
return "numeric";
|
|
2806
3294
|
case "TEXT":
|
|
2807
3295
|
case "VARCHAR":
|
|
2808
3296
|
case "CHAR":
|
|
@@ -2822,11 +3310,8 @@ function mapToPgBaseType(pgType) {
|
|
|
2822
3310
|
return "timestamptz";
|
|
2823
3311
|
case "DATE":
|
|
2824
3312
|
return "date";
|
|
2825
|
-
default:
|
|
2826
|
-
|
|
2827
|
-
validateTypeName2(customType);
|
|
2828
|
-
return customType;
|
|
2829
|
-
}
|
|
3313
|
+
default:
|
|
3314
|
+
return elementType;
|
|
2830
3315
|
}
|
|
2831
3316
|
}
|
|
2832
3317
|
function stripArraySuffix(pgArrayType) {
|
|
@@ -2875,6 +3360,7 @@ function parseRawExpression(sqlFragment) {
|
|
|
2875
3360
|
var init_compiler_utils = __esm({
|
|
2876
3361
|
"src/compiler-utils.ts"() {
|
|
2877
3362
|
"use strict";
|
|
3363
|
+
init_db_type();
|
|
2878
3364
|
init_raw_expression_parser();
|
|
2879
3365
|
}
|
|
2880
3366
|
});
|
|
@@ -2891,7 +3377,6 @@ var init_param_intent = __esm({
|
|
|
2891
3377
|
});
|
|
2892
3378
|
|
|
2893
3379
|
// src/handlers/where/utils.ts
|
|
2894
|
-
import { validateTypeName as validateTypeName3 } from "@dbsp/core";
|
|
2895
3380
|
import { isFieldRef, isParamIntent as isParamIntent3 } from "@dbsp/types";
|
|
2896
3381
|
function buildColumnRef(column, ctx) {
|
|
2897
3382
|
if (column.includes(".")) {
|
|
@@ -2955,8 +3440,8 @@ function resolveColumnPgType(columnName, ctx) {
|
|
|
2955
3440
|
if (!column) return void 0;
|
|
2956
3441
|
if (column.originalDbType) {
|
|
2957
3442
|
const typeName = column.originalDbType.trim();
|
|
2958
|
-
|
|
2959
|
-
return typeName;
|
|
3443
|
+
validateDbType(typeName);
|
|
3444
|
+
return dbTypeCastTarget(typeName);
|
|
2960
3445
|
}
|
|
2961
3446
|
return void 0;
|
|
2962
3447
|
}
|
|
@@ -2964,6 +3449,7 @@ var init_utils = __esm({
|
|
|
2964
3449
|
"src/handlers/where/utils.ts"() {
|
|
2965
3450
|
"use strict";
|
|
2966
3451
|
init_ast_helpers();
|
|
3452
|
+
init_db_type();
|
|
2967
3453
|
init_param_intent();
|
|
2968
3454
|
init_param_ref();
|
|
2969
3455
|
init_types();
|
|
@@ -6659,7 +7145,21 @@ var init_arithmetic = __esm({
|
|
|
6659
7145
|
// src/handlers/expression/case.ts
|
|
6660
7146
|
function resolveCaseValue2(value, ctx, state) {
|
|
6661
7147
|
const alias = ctx.currentAlias ?? ctx.rootTable;
|
|
6662
|
-
return resolveCaseValue(
|
|
7148
|
+
return resolveCaseValue(
|
|
7149
|
+
value,
|
|
7150
|
+
alias,
|
|
7151
|
+
void 0,
|
|
7152
|
+
ctx.naming,
|
|
7153
|
+
state,
|
|
7154
|
+
void 0,
|
|
7155
|
+
// Handler-level path: renders every expression kind via the shared
|
|
7156
|
+
// expression compiler. It does NOT apply the customFn FILTER modifier
|
|
7157
|
+
// (agg_filter) — that requires compiler-private compileFilterCondition,
|
|
7158
|
+
// which cannot be reached here without a circular dependency. The primary
|
|
7159
|
+
// DX caseWhen path (PlanCompiler.compileCustomExpressionNode) filter-patches
|
|
7160
|
+
// correctly; use it for filtered aggregates as CASE branches.
|
|
7161
|
+
(expr) => compileExpressionIntent(expr, ctx, state)
|
|
7162
|
+
);
|
|
6663
7163
|
}
|
|
6664
7164
|
var caseHandler, simpleCaseHandler;
|
|
6665
7165
|
var init_case = __esm({
|
|
@@ -6668,6 +7168,7 @@ var init_case = __esm({
|
|
|
6668
7168
|
init_ast_helpers();
|
|
6669
7169
|
init_param_intent();
|
|
6670
7170
|
init_case_value();
|
|
7171
|
+
init_custom();
|
|
6671
7172
|
init_param_value();
|
|
6672
7173
|
caseHandler = {
|
|
6673
7174
|
types: ["case", "CASE", "caseWhen"],
|
|
@@ -9366,6 +9867,24 @@ function renumberNode(value, offset) {
|
|
|
9366
9867
|
}
|
|
9367
9868
|
return result;
|
|
9368
9869
|
}
|
|
9870
|
+
function maxParamRefNumber(value) {
|
|
9871
|
+
if (value === null || typeof value !== "object") return 0;
|
|
9872
|
+
if (Array.isArray(value)) {
|
|
9873
|
+
let max2 = 0;
|
|
9874
|
+
for (const item of value) max2 = Math.max(max2, maxParamRefNumber(item));
|
|
9875
|
+
return max2;
|
|
9876
|
+
}
|
|
9877
|
+
const obj = value;
|
|
9878
|
+
if ("ParamRef" in obj && obj.ParamRef !== null && typeof obj.ParamRef === "object") {
|
|
9879
|
+
const n = obj.ParamRef.number;
|
|
9880
|
+
return typeof n === "number" ? n : 0;
|
|
9881
|
+
}
|
|
9882
|
+
let max = 0;
|
|
9883
|
+
for (const key of Object.keys(obj)) {
|
|
9884
|
+
max = Math.max(max, maxParamRefNumber(obj[key]));
|
|
9885
|
+
}
|
|
9886
|
+
return max;
|
|
9887
|
+
}
|
|
9369
9888
|
var includeHandlersInitialized = false;
|
|
9370
9889
|
function ensureIncludeHandlersRegistered() {
|
|
9371
9890
|
if (includeHandlersInitialized) return;
|
|
@@ -10400,7 +10919,7 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
10400
10919
|
}
|
|
10401
10920
|
case "selectExpression": {
|
|
10402
10921
|
if (decision.expressionType === "case") {
|
|
10403
|
-
const caseNode = this.compileCaseExpression(decision);
|
|
10922
|
+
const caseNode = this.compileCaseExpression(decision, plan);
|
|
10404
10923
|
const alias = decision.alias;
|
|
10405
10924
|
targetList.push({
|
|
10406
10925
|
ResTarget: {
|
|
@@ -10413,34 +10932,7 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
10413
10932
|
}
|
|
10414
10933
|
case "selectCustomExpression": {
|
|
10415
10934
|
const exprIntent = decision.expressionIntent;
|
|
10416
|
-
const
|
|
10417
|
-
const ctx = {
|
|
10418
|
-
...this.createHandlerContext(plan, plan.rootTable),
|
|
10419
|
-
compileSubquery(query, paramOffset) {
|
|
10420
|
-
return outerThis.compileExpressionSubquery(query, paramOffset);
|
|
10421
|
-
}
|
|
10422
|
-
};
|
|
10423
|
-
const state = this.createHandlerState();
|
|
10424
|
-
const node = compileExpressionIntent(exprIntent, ctx, state);
|
|
10425
|
-
if (exprIntent.kind === "customFn" && exprIntent.filter) {
|
|
10426
|
-
const filterIntent = exprIntent.filter;
|
|
10427
|
-
const filterDecision = convertWhereCondition(
|
|
10428
|
-
filterIntent,
|
|
10429
|
-
plan.rootTable
|
|
10430
|
-
);
|
|
10431
|
-
if (filterDecision) {
|
|
10432
|
-
const filterNode = compileFilterCondition(
|
|
10433
|
-
filterDecision,
|
|
10434
|
-
createWhereDispatcher(),
|
|
10435
|
-
ctx,
|
|
10436
|
-
state
|
|
10437
|
-
);
|
|
10438
|
-
if (filterNode && "FuncCall" in node) {
|
|
10439
|
-
node.FuncCall.agg_filter = filterNode;
|
|
10440
|
-
}
|
|
10441
|
-
}
|
|
10442
|
-
}
|
|
10443
|
-
this.state.paramIndex = state.paramIndex;
|
|
10935
|
+
const node = this.compileCustomExpressionNode(exprIntent, plan);
|
|
10444
10936
|
const alias = decision.alias || decision.column || void 0;
|
|
10445
10937
|
targetList.push({
|
|
10446
10938
|
ResTarget: {
|
|
@@ -10707,14 +11199,28 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
10707
11199
|
*/
|
|
10708
11200
|
compileJoinDecision(decision, plan, from) {
|
|
10709
11201
|
if (isPrecompiledJoinDecision(decision)) {
|
|
10710
|
-
|
|
11202
|
+
const isBatchValues = isBatchValuesJoinDecision(decision);
|
|
11203
|
+
if (isBatchValues) {
|
|
10711
11204
|
for (const p of decision.batchValuesParams) {
|
|
10712
11205
|
this.state.parameters.push(p);
|
|
10713
11206
|
}
|
|
10714
11207
|
this.state.paramIndex = this.state.parameters.length;
|
|
10715
11208
|
}
|
|
10716
11209
|
const jRarg = decision.joinRarg;
|
|
10717
|
-
|
|
11210
|
+
let jOn = decision.joinOnNode;
|
|
11211
|
+
if (!isBatchValues && decision.joinOnParams?.length) {
|
|
11212
|
+
if (maxParamRefNumber(decision.joinOnNode) > decision.joinOnParams.length) {
|
|
11213
|
+
throw new Error(
|
|
11214
|
+
"Table-mode JOIN ON clauses support only inline param() values; externally pre-assigned or bound parameters in an ON clause are not supported."
|
|
11215
|
+
);
|
|
11216
|
+
}
|
|
11217
|
+
const offset = this.state.parameters.length;
|
|
11218
|
+
jOn = renumberParamRefsInAst(decision.joinOnNode, offset);
|
|
11219
|
+
for (const p of decision.joinOnParams) {
|
|
11220
|
+
this.state.parameters.push(p);
|
|
11221
|
+
}
|
|
11222
|
+
this.state.paramIndex = this.state.parameters.length;
|
|
11223
|
+
}
|
|
10718
11224
|
from[0] = decision.joinType === "left" ? leftJoin(from[0], jRarg, jOn) : innerJoin(from[0], jRarg, jOn);
|
|
10719
11225
|
} else {
|
|
10720
11226
|
from[0] = this.compileJoin(decision, plan, from[0]);
|
|
@@ -11008,7 +11514,7 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
11008
11514
|
// --------------------------------------------------------------------------
|
|
11009
11515
|
// CASE Expression Compilation
|
|
11010
11516
|
// --------------------------------------------------------------------------
|
|
11011
|
-
compileCaseExpression(decision) {
|
|
11517
|
+
compileCaseExpression(decision, plan) {
|
|
11012
11518
|
const conditions = decision.conditions;
|
|
11013
11519
|
const elseValue = decision.value;
|
|
11014
11520
|
if (!conditions || conditions.length === 0) {
|
|
@@ -11016,7 +11522,7 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
11016
11522
|
}
|
|
11017
11523
|
const args = conditions.map((cond) => {
|
|
11018
11524
|
const whenExpr = this.dispatchWhere(cond.when);
|
|
11019
|
-
const thenResult = this.compileCaseValue(cond.then);
|
|
11525
|
+
const thenResult = this.compileCaseValue(cond.then, plan);
|
|
11020
11526
|
return {
|
|
11021
11527
|
CaseWhen: {
|
|
11022
11528
|
expr: whenExpr,
|
|
@@ -11026,7 +11532,7 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
11026
11532
|
});
|
|
11027
11533
|
let defresult;
|
|
11028
11534
|
if (elseValue !== void 0) {
|
|
11029
|
-
defresult = this.compileCaseValue(elseValue);
|
|
11535
|
+
defresult = this.compileCaseValue(elseValue, plan);
|
|
11030
11536
|
}
|
|
11031
11537
|
return {
|
|
11032
11538
|
CaseExpr: {
|
|
@@ -11039,26 +11545,66 @@ var PlanCompiler = class _PlanCompiler {
|
|
|
11039
11545
|
* Compile a CASE THEN/ELSE value based on its ExpressionIntent kind.
|
|
11040
11546
|
* Delegates to shared resolveCaseValue with nested CASE support.
|
|
11041
11547
|
*/
|
|
11042
|
-
compileCaseValue(value) {
|
|
11548
|
+
compileCaseValue(value, plan) {
|
|
11043
11549
|
return resolveCaseValue(
|
|
11044
11550
|
value,
|
|
11045
11551
|
this.currentRootTable,
|
|
11046
11552
|
void 0,
|
|
11047
11553
|
this.naming,
|
|
11048
11554
|
this.state,
|
|
11049
|
-
(expr) =>
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11555
|
+
(expr) => {
|
|
11556
|
+
const nestedDecision = {
|
|
11557
|
+
type: "selectExpression",
|
|
11558
|
+
expressionType: "case",
|
|
11559
|
+
conditions: expr.when.map((wc) => ({
|
|
11560
|
+
when: wc.condition,
|
|
11561
|
+
// biome-ignore lint/suspicious/noThenProperty: intentional
|
|
11562
|
+
then: wc.result
|
|
11563
|
+
})),
|
|
11564
|
+
value: expr.else,
|
|
11565
|
+
table: this.currentRootTable
|
|
11566
|
+
};
|
|
11567
|
+
return this.compileCaseExpression(nestedDecision, plan);
|
|
11568
|
+
},
|
|
11569
|
+
(expr) => this.compileCustomExpressionNode(
|
|
11570
|
+
expr,
|
|
11571
|
+
plan
|
|
11572
|
+
)
|
|
11060
11573
|
);
|
|
11061
11574
|
}
|
|
11575
|
+
/**
|
|
11576
|
+
* Compile a custom ExpressionIntent (customFn, customOp, ref, cast, unary,
|
|
11577
|
+
* array, function, subquery, …) to an AST node, applying the customFn FILTER
|
|
11578
|
+
* clause. Shared by the `selectCustomExpression` target path and CASE
|
|
11579
|
+
* THEN/ELSE values so both render the full expression surface identically
|
|
11580
|
+
* (every expression kind + FILTER), rather than one path silently binding
|
|
11581
|
+
* expressions as parameters or dropping FILTER.
|
|
11582
|
+
*/
|
|
11583
|
+
compileCustomExpressionNode(exprIntent, plan) {
|
|
11584
|
+
const ctx = this.createHandlerContext(plan, plan.rootTable);
|
|
11585
|
+
const state = this.createHandlerState();
|
|
11586
|
+
const node = compileExpressionIntent(exprIntent, ctx, state);
|
|
11587
|
+
if (exprIntent.kind === "customFn" && exprIntent.filter) {
|
|
11588
|
+
const filterIntent = exprIntent.filter;
|
|
11589
|
+
const filterDecision = convertWhereCondition(
|
|
11590
|
+
filterIntent,
|
|
11591
|
+
plan.rootTable
|
|
11592
|
+
);
|
|
11593
|
+
if (filterDecision) {
|
|
11594
|
+
const filterNode = compileFilterCondition(
|
|
11595
|
+
filterDecision,
|
|
11596
|
+
createWhereDispatcher(),
|
|
11597
|
+
ctx,
|
|
11598
|
+
state
|
|
11599
|
+
);
|
|
11600
|
+
if (filterNode && "FuncCall" in node) {
|
|
11601
|
+
node.FuncCall.agg_filter = filterNode;
|
|
11602
|
+
}
|
|
11603
|
+
}
|
|
11604
|
+
}
|
|
11605
|
+
this.state.paramIndex = state.paramIndex;
|
|
11606
|
+
return node;
|
|
11607
|
+
}
|
|
11062
11608
|
// --------------------------------------------------------------------------
|
|
11063
11609
|
// INSERT Compilation
|
|
11064
11610
|
// --------------------------------------------------------------------------
|
|
@@ -11261,6 +11807,9 @@ function compilePlan(plan, options) {
|
|
|
11261
11807
|
init_naming_plugin();
|
|
11262
11808
|
init_validate();
|
|
11263
11809
|
|
|
11810
|
+
// src/ddl/phases/comments.ts
|
|
11811
|
+
init_validate();
|
|
11812
|
+
|
|
11264
11813
|
// src/ddl/phases/types.ts
|
|
11265
11814
|
function sup(caps, flag) {
|
|
11266
11815
|
return !caps || flag === true;
|
|
@@ -11281,6 +11830,7 @@ var VALID_INDEX_METHODS = /* @__PURE__ */ new Set([
|
|
|
11281
11830
|
"bloom"
|
|
11282
11831
|
]);
|
|
11283
11832
|
function validateIndexMethod(method, context = "index method") {
|
|
11833
|
+
assertString(method, `Invalid ${context}`);
|
|
11284
11834
|
if (!VALID_INDEX_METHODS.has(method)) {
|
|
11285
11835
|
throw new Error(
|
|
11286
11836
|
`Invalid ${context}: "${method}". Must be one of: ${[...VALID_INDEX_METHODS].join(", ")}`
|
|
@@ -11288,6 +11838,7 @@ function validateIndexMethod(method, context = "index method") {
|
|
|
11288
11838
|
}
|
|
11289
11839
|
}
|
|
11290
11840
|
function validateEnumLabel(value, context = "enum label") {
|
|
11841
|
+
assertString(value, `Invalid ${context}`);
|
|
11291
11842
|
if (/\x00/.test(value)) {
|
|
11292
11843
|
throw new Error(
|
|
11293
11844
|
`Invalid ${context}: contains NUL byte (\\x00) which would be silently truncated by PostgreSQL`
|
|
@@ -11312,6 +11863,7 @@ function quoteCollation(name) {
|
|
|
11312
11863
|
return `"${name}"`;
|
|
11313
11864
|
}
|
|
11314
11865
|
function quoteRoleName(name) {
|
|
11866
|
+
assertString(name, "quoteRoleName: role name");
|
|
11315
11867
|
if (!name) {
|
|
11316
11868
|
throw new Error("quoteRoleName: role name must not be empty");
|
|
11317
11869
|
}
|
|
@@ -11378,17 +11930,21 @@ function generateCommentsPhase(ctx) {
|
|
|
11378
11930
|
}
|
|
11379
11931
|
const statements = [];
|
|
11380
11932
|
for (const table of tables) {
|
|
11381
|
-
|
|
11933
|
+
const tableComment = table.comment;
|
|
11934
|
+
if (tableComment) {
|
|
11935
|
+
assertString(tableComment, "table comment");
|
|
11382
11936
|
const qualifiedTable = qualifyTableIdent(table.name, schemaName, naming);
|
|
11383
11937
|
statements.push(
|
|
11384
|
-
`COMMENT ON TABLE ${qualifiedTable} IS '${
|
|
11938
|
+
`COMMENT ON TABLE ${qualifiedTable} IS '${tableComment.replace(/'/g, "''")}';`
|
|
11385
11939
|
);
|
|
11386
11940
|
}
|
|
11387
11941
|
for (const col of table.columns) {
|
|
11388
|
-
|
|
11942
|
+
const columnComment = col.comment;
|
|
11943
|
+
if (columnComment) {
|
|
11944
|
+
assertString(columnComment, "column comment");
|
|
11389
11945
|
const qualifiedTable = qualifyTableIdent(table.name, schemaName, naming);
|
|
11390
11946
|
statements.push(
|
|
11391
|
-
`COMMENT ON COLUMN ${qualifiedTable}.${quoteIdent2(naming.toDatabase(col.name))} IS '${
|
|
11947
|
+
`COMMENT ON COLUMN ${qualifiedTable}.${quoteIdent2(naming.toDatabase(col.name))} IS '${columnComment.replace(/'/g, "''")}';`
|
|
11392
11948
|
);
|
|
11393
11949
|
}
|
|
11394
11950
|
}
|
|
@@ -11413,9 +11969,10 @@ function generateConstraintsPhase(ctx) {
|
|
|
11413
11969
|
for (const check of table.checkConstraints ?? []) {
|
|
11414
11970
|
const qualifiedTable = qualifyTableIdent(table.name, schemaName, naming);
|
|
11415
11971
|
const constraintName = quoteIdent2(check.name);
|
|
11416
|
-
|
|
11972
|
+
const expression = check.expression;
|
|
11973
|
+
validateCheckExpression(expression, "check constraint expression");
|
|
11417
11974
|
statements.push(
|
|
11418
|
-
`ALTER TABLE ${qualifiedTable} ADD CONSTRAINT ${constraintName} ${
|
|
11975
|
+
`ALTER TABLE ${qualifiedTable} ADD CONSTRAINT ${constraintName} ${expression};`
|
|
11419
11976
|
);
|
|
11420
11977
|
}
|
|
11421
11978
|
}
|
|
@@ -11525,16 +12082,37 @@ function generateRlsPhase(ctx) {
|
|
|
11525
12082
|
init_validate();
|
|
11526
12083
|
|
|
11527
12084
|
// src/ddl/type-mapping.ts
|
|
12085
|
+
init_db_type();
|
|
11528
12086
|
init_validate();
|
|
11529
12087
|
function mapColumnType(col) {
|
|
11530
12088
|
if (col.originalDbType) {
|
|
11531
|
-
|
|
12089
|
+
const originalDbType = validateDbTypeName(col.originalDbType);
|
|
12090
|
+
return isPgBuiltInTypeName(originalDbType) ? uppercaseOutsideQuotedIdentifiers(originalDbType) : originalDbType;
|
|
11532
12091
|
}
|
|
11533
12092
|
if (col.autoIncrement) {
|
|
11534
12093
|
return col.type === "bigint" ? "BIGSERIAL" : "SERIAL";
|
|
11535
12094
|
}
|
|
11536
12095
|
return mapBaseType(col.type);
|
|
11537
12096
|
}
|
|
12097
|
+
function uppercaseOutsideQuotedIdentifiers(type) {
|
|
12098
|
+
let result = "";
|
|
12099
|
+
let inQuotedIdentifier = false;
|
|
12100
|
+
for (let i = 0; i < type.length; i++) {
|
|
12101
|
+
const char = type[i];
|
|
12102
|
+
if (char === '"') {
|
|
12103
|
+
result += char;
|
|
12104
|
+
if (inQuotedIdentifier && type[i + 1] === '"') {
|
|
12105
|
+
result += type[i + 1];
|
|
12106
|
+
i++;
|
|
12107
|
+
continue;
|
|
12108
|
+
}
|
|
12109
|
+
inQuotedIdentifier = !inQuotedIdentifier;
|
|
12110
|
+
continue;
|
|
12111
|
+
}
|
|
12112
|
+
result += inQuotedIdentifier ? char : char.toUpperCase();
|
|
12113
|
+
}
|
|
12114
|
+
return result;
|
|
12115
|
+
}
|
|
11538
12116
|
function mapBaseType(type) {
|
|
11539
12117
|
switch (type) {
|
|
11540
12118
|
case "string":
|
|
@@ -11598,12 +12176,29 @@ function mapOnDeleteAction(action) {
|
|
|
11598
12176
|
function qualifyTable(table, schemaName) {
|
|
11599
12177
|
return schemaName ? `${quoteIdent2(schemaName, "schema")}.${quoteIdent2(table, "table")}` : quoteIdent2(table, "table");
|
|
11600
12178
|
}
|
|
12179
|
+
function buildDoBlock(body) {
|
|
12180
|
+
const delimiter = chooseDoBlockDelimiter(body);
|
|
12181
|
+
return `DO ${delimiter} ${body} ${delimiter};`;
|
|
12182
|
+
}
|
|
12183
|
+
function chooseDoBlockDelimiter(body) {
|
|
12184
|
+
if (!body.includes("$$")) return "$$";
|
|
12185
|
+
let suffix = 0;
|
|
12186
|
+
let delimiter = "$dbsp_check$";
|
|
12187
|
+
while (body.includes(delimiter)) {
|
|
12188
|
+
suffix++;
|
|
12189
|
+
delimiter = `$dbsp_check_${suffix}$`;
|
|
12190
|
+
}
|
|
12191
|
+
return delimiter;
|
|
12192
|
+
}
|
|
11601
12193
|
function pkName(table) {
|
|
11602
12194
|
return `pk_${table}`;
|
|
11603
12195
|
}
|
|
11604
12196
|
function fkName(table, columns) {
|
|
11605
12197
|
return `fk_${table}_${columns.join("_")}`;
|
|
11606
12198
|
}
|
|
12199
|
+
function uniqueName(table, column) {
|
|
12200
|
+
return `${table}_${column}_key`;
|
|
12201
|
+
}
|
|
11607
12202
|
function idxName(table, columns, customName) {
|
|
11608
12203
|
return customName ?? `idx_${table}_${columns.join("_")}`;
|
|
11609
12204
|
}
|
|
@@ -11658,11 +12253,22 @@ function buildPolicySQL(tableName, policy, schemaName) {
|
|
|
11658
12253
|
}
|
|
11659
12254
|
function buildSequenceClause(verb, seqName, seq, includeCycleNoCycle = false) {
|
|
11660
12255
|
const parts = [`${verb} ${seqName}`];
|
|
11661
|
-
if (seq.startWith !== void 0)
|
|
12256
|
+
if (seq.startWith !== void 0)
|
|
12257
|
+
parts.push(
|
|
12258
|
+
`START WITH ${assertNumericLiteral(seq.startWith, "sequence START WITH")}`
|
|
12259
|
+
);
|
|
11662
12260
|
if (seq.incrementBy !== void 0)
|
|
11663
|
-
parts.push(
|
|
11664
|
-
|
|
11665
|
-
|
|
12261
|
+
parts.push(
|
|
12262
|
+
`INCREMENT BY ${assertNumericLiteral(seq.incrementBy, "sequence INCREMENT BY")}`
|
|
12263
|
+
);
|
|
12264
|
+
if (seq.minValue !== void 0)
|
|
12265
|
+
parts.push(
|
|
12266
|
+
`MINVALUE ${assertNumericLiteral(seq.minValue, "sequence MINVALUE")}`
|
|
12267
|
+
);
|
|
12268
|
+
if (seq.maxValue !== void 0)
|
|
12269
|
+
parts.push(
|
|
12270
|
+
`MAXVALUE ${assertNumericLiteral(seq.maxValue, "sequence MAXVALUE")}`
|
|
12271
|
+
);
|
|
11666
12272
|
if (includeCycleNoCycle) {
|
|
11667
12273
|
if (seq.cycle !== void 0) parts.push(seq.cycle ? "CYCLE" : "NO CYCLE");
|
|
11668
12274
|
} else {
|
|
@@ -11672,7 +12278,7 @@ function buildSequenceClause(verb, seqName, seq, includeCycleNoCycle = false) {
|
|
|
11672
12278
|
}
|
|
11673
12279
|
function failSafeUnknownDownChange(kind) {
|
|
11674
12280
|
return {
|
|
11675
|
-
sql: `-- WARNING: Cannot reverse unsupported SchemaChange kind "${kind}"`,
|
|
12281
|
+
sql: `-- WARNING: Cannot reverse unsupported SchemaChange kind "${sanitizeCommentText(kind)}"`,
|
|
11676
12282
|
destructive: true
|
|
11677
12283
|
};
|
|
11678
12284
|
}
|
|
@@ -11734,7 +12340,7 @@ function generateMigrationSQL(diff, options) {
|
|
|
11734
12340
|
[],
|
|
11735
12341
|
// 8: alter column
|
|
11736
12342
|
[],
|
|
11737
|
-
// 9: add PK
|
|
12343
|
+
// 9: add PK / column UNIQUE constraint
|
|
11738
12344
|
[],
|
|
11739
12345
|
// 10: add FK
|
|
11740
12346
|
[],
|
|
@@ -11821,6 +12427,7 @@ function getPhase(kind) {
|
|
|
11821
12427
|
case "alter_column_collation":
|
|
11822
12428
|
case "alter_column_identity":
|
|
11823
12429
|
return 8;
|
|
12430
|
+
case "alter_column_unique":
|
|
11824
12431
|
case "add_primary_key":
|
|
11825
12432
|
return 9;
|
|
11826
12433
|
case "add_foreign_key":
|
|
@@ -11857,7 +12464,7 @@ function upAddColumn(change, schemaName) {
|
|
|
11857
12464
|
}
|
|
11858
12465
|
function upAlterColumnType(change, schemaName) {
|
|
11859
12466
|
const col = change.meta?.column;
|
|
11860
|
-
const toType = col ? mapColumnType(col) :
|
|
12467
|
+
const toType = col ? mapColumnType(col) : validateDbTypeName(change.meta?.toType);
|
|
11861
12468
|
return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} TYPE ${toType};`;
|
|
11862
12469
|
}
|
|
11863
12470
|
function upAlterColumnNullable(change, schemaName) {
|
|
@@ -11872,6 +12479,34 @@ function upAlterColumnDefault(change, schemaName) {
|
|
|
11872
12479
|
}
|
|
11873
12480
|
return `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} SET DEFAULT ${formatDefault(def)};`;
|
|
11874
12481
|
}
|
|
12482
|
+
function addColumnUniqueSQL(table, column, schemaName, constraintName) {
|
|
12483
|
+
const name = constraintName ?? uniqueName(table, column);
|
|
12484
|
+
if (constraintName !== void 0) validateIdentifier(constraintName, "alias");
|
|
12485
|
+
const quotedConstraintName = quoteIdent2(name, "alias");
|
|
12486
|
+
return `ALTER TABLE ${qualifyTable(table, schemaName)} ADD CONSTRAINT ${quotedConstraintName} UNIQUE (${quoteIdent2(column, "alias")});`;
|
|
12487
|
+
}
|
|
12488
|
+
function dropColumnUniqueSQL(table, column, schemaName, constraintName) {
|
|
12489
|
+
const name = constraintName ?? uniqueName(table, column);
|
|
12490
|
+
if (constraintName !== void 0) validateIdentifier(constraintName, "alias");
|
|
12491
|
+
const quotedConstraintName = quoteIdent2(name, "alias");
|
|
12492
|
+
return `ALTER TABLE ${qualifyTable(table, schemaName)} DROP CONSTRAINT IF EXISTS ${quotedConstraintName};`;
|
|
12493
|
+
}
|
|
12494
|
+
function upAlterColumnUnique(change, schemaName) {
|
|
12495
|
+
const unique = change.meta?.unique;
|
|
12496
|
+
if (unique === void 0 || !change.column) return void 0;
|
|
12497
|
+
const constraintName = change.meta?.constraintName;
|
|
12498
|
+
return unique ? addColumnUniqueSQL(
|
|
12499
|
+
change.table,
|
|
12500
|
+
change.column,
|
|
12501
|
+
schemaName,
|
|
12502
|
+
constraintName
|
|
12503
|
+
) : dropColumnUniqueSQL(
|
|
12504
|
+
change.table,
|
|
12505
|
+
change.column,
|
|
12506
|
+
schemaName,
|
|
12507
|
+
constraintName
|
|
12508
|
+
);
|
|
12509
|
+
}
|
|
11875
12510
|
function upAddPrimaryKey(change, schemaName) {
|
|
11876
12511
|
const columns = change.meta?.columns;
|
|
11877
12512
|
const pkCols = columns.map((n) => quoteIdent2(n, "alias")).join(", ");
|
|
@@ -11900,8 +12535,9 @@ function upCreateIndex(change, schemaName) {
|
|
|
11900
12535
|
"alias"
|
|
11901
12536
|
);
|
|
11902
12537
|
const unique = idx.unique ? "UNIQUE " : "";
|
|
11903
|
-
|
|
11904
|
-
|
|
12538
|
+
const indexMethod = idx.method;
|
|
12539
|
+
if (indexMethod) validateIndexMethod(indexMethod, "index method");
|
|
12540
|
+
const method = indexMethod ? ` USING ${indexMethod}` : "";
|
|
11905
12541
|
const colParts = [
|
|
11906
12542
|
...(idx.expressions ?? []).map((expr) => {
|
|
11907
12543
|
validateSqlExpression(expr, "index expression");
|
|
@@ -11918,10 +12554,11 @@ function upCreateIndex(change, schemaName) {
|
|
|
11918
12554
|
const nullsNotDistinct = idx.unique && idx.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
|
|
11919
12555
|
const withParams = idx.with && Object.keys(idx.with).length > 0 ? ` WITH (${Object.entries(idx.with).map(([k, v]) => {
|
|
11920
12556
|
validateIdentifier(k, "alias");
|
|
11921
|
-
return `${k} = ${v}`;
|
|
12557
|
+
return `${k} = ${formatStorageParameterValue(v, `index WITH parameter "${k}"`)}`;
|
|
11922
12558
|
}).join(", ")})` : "";
|
|
11923
|
-
|
|
11924
|
-
|
|
12559
|
+
const whereExpr = idx.where;
|
|
12560
|
+
if (whereExpr) validateSqlExpression(whereExpr, "index WHERE predicate");
|
|
12561
|
+
const where = whereExpr ? ` WHERE ${whereExpr}` : "";
|
|
11925
12562
|
return `CREATE ${unique}INDEX IF NOT EXISTS ${indexName} ON ${qualifyTable(change.table, schemaName)}${method} (${cols})${include}${nullsNotDistinct}${withParams}${where};`;
|
|
11926
12563
|
}
|
|
11927
12564
|
function upDropIndex(change, schemaName) {
|
|
@@ -11937,12 +12574,12 @@ function upDropIndex(change, schemaName) {
|
|
|
11937
12574
|
function upAddCheckConstraint(change, schemaName) {
|
|
11938
12575
|
const check = change.meta?.check;
|
|
11939
12576
|
if (!check) return void 0;
|
|
12577
|
+
const expression = check.expression;
|
|
11940
12578
|
const notValid = check.notValid ? " NOT VALID" : "";
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
"
|
|
12579
|
+
validateCheckExpression(expression, "migration check constraint expression");
|
|
12580
|
+
return buildDoBlock(
|
|
12581
|
+
"BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + expression + notValid + "; EXCEPTION WHEN duplicate_object THEN NULL; END"
|
|
11944
12582
|
);
|
|
11945
|
-
return "DO $$ BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + check.expression + notValid + "; EXCEPTION WHEN duplicate_object THEN NULL; END $$;";
|
|
11946
12583
|
}
|
|
11947
12584
|
function upValidateConstraint(change, schemaName) {
|
|
11948
12585
|
const fk = change.meta?.fk;
|
|
@@ -11978,9 +12615,12 @@ function upDropEnum(change, schemaName) {
|
|
|
11978
12615
|
if (!enumDef) return void 0;
|
|
11979
12616
|
const enumName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(enumDef.name, "alias")}` : quoteIdent2(enumDef.name, "alias");
|
|
11980
12617
|
const refs = change.meta?.referencingColumns;
|
|
11981
|
-
const alterStatements = refs && refs.length > 0 ? refs.map(
|
|
11982
|
-
|
|
11983
|
-
|
|
12618
|
+
const alterStatements = refs && refs.length > 0 ? refs.map((ref) => {
|
|
12619
|
+
const column = quoteIdent2(ref.column, "alias");
|
|
12620
|
+
const targetType = ref.isArray ? "text[]" : "text";
|
|
12621
|
+
const using = ref.isArray ? ` USING ${column}::text[]` : "";
|
|
12622
|
+
return `ALTER TABLE ${qualifyTable(ref.table, schemaName)} ALTER COLUMN ${column} TYPE ${targetType}${using};`;
|
|
12623
|
+
}) : [];
|
|
11984
12624
|
return [...alterStatements, `DROP TYPE IF EXISTS ${enumName} CASCADE;`].join(
|
|
11985
12625
|
"\n"
|
|
11986
12626
|
);
|
|
@@ -12011,6 +12651,7 @@ function upAlterColumnIdentity(change, schemaName) {
|
|
|
12011
12651
|
function upAddComment(change, schemaName) {
|
|
12012
12652
|
const comment = change.meta?.comment;
|
|
12013
12653
|
const target = change.meta?.target;
|
|
12654
|
+
assertString(comment, "migration comment");
|
|
12014
12655
|
const escaped = comment.replace(/'/g, "''");
|
|
12015
12656
|
if (target === "table") {
|
|
12016
12657
|
return `COMMENT ON TABLE ${qualifyTable(change.table, schemaName)} IS '${escaped}';`;
|
|
@@ -12038,6 +12679,8 @@ function changeToUpSQL(change, schemaName) {
|
|
|
12038
12679
|
return upAlterColumnNullable(change, schemaName);
|
|
12039
12680
|
case "alter_column_default":
|
|
12040
12681
|
return upAlterColumnDefault(change, schemaName);
|
|
12682
|
+
case "alter_column_unique":
|
|
12683
|
+
return upAlterColumnUnique(change, schemaName);
|
|
12041
12684
|
case "add_primary_key":
|
|
12042
12685
|
return upAddPrimaryKey(change, schemaName);
|
|
12043
12686
|
case "drop_primary_key":
|
|
@@ -12135,7 +12778,7 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12135
12778
|
};
|
|
12136
12779
|
case "drop_table":
|
|
12137
12780
|
return {
|
|
12138
|
-
sql: `-- WARNING: Cannot reverse drop_table "${change.table}" -- table data was lost`,
|
|
12781
|
+
sql: `-- WARNING: Cannot reverse drop_table "${sanitizeCommentText(change.table)}" -- table data was lost`,
|
|
12139
12782
|
destructive: true
|
|
12140
12783
|
};
|
|
12141
12784
|
case "add_column":
|
|
@@ -12145,19 +12788,20 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12145
12788
|
};
|
|
12146
12789
|
case "drop_column":
|
|
12147
12790
|
return {
|
|
12148
|
-
sql: `-- WARNING: Cannot reverse drop_column "${change.table}"."${change.column}" -- column data was lost`,
|
|
12791
|
+
sql: `-- WARNING: Cannot reverse drop_column "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- column data was lost`,
|
|
12149
12792
|
destructive: true
|
|
12150
12793
|
};
|
|
12151
12794
|
case "alter_column_type": {
|
|
12152
12795
|
const fromType = change.meta?.fromType;
|
|
12153
|
-
if (
|
|
12796
|
+
if (fromType == null) {
|
|
12154
12797
|
return {
|
|
12155
|
-
sql: `-- WARNING: Cannot reverse alter_column_type "${change.table}"."${change.column}" -- missing migration metadata`,
|
|
12798
|
+
sql: `-- WARNING: Cannot reverse alter_column_type "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- missing migration metadata`,
|
|
12156
12799
|
destructive: true
|
|
12157
12800
|
};
|
|
12158
12801
|
}
|
|
12802
|
+
const safeFromType = validateDbTypeName(fromType);
|
|
12159
12803
|
return {
|
|
12160
|
-
sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} TYPE ${
|
|
12804
|
+
sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} ALTER COLUMN ${quoteIdent2(change.column, "alias")} TYPE ${safeFromType};`,
|
|
12161
12805
|
destructive: true
|
|
12162
12806
|
};
|
|
12163
12807
|
}
|
|
@@ -12165,7 +12809,7 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12165
12809
|
const oldNullable = change.meta?.oldNullable;
|
|
12166
12810
|
if (oldNullable === void 0) {
|
|
12167
12811
|
return {
|
|
12168
|
-
sql: `-- WARNING: Cannot reverse alter_column_nullable "${change.table}"."${change.column}" -- missing migration metadata`,
|
|
12812
|
+
sql: `-- WARNING: Cannot reverse alter_column_nullable "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- missing migration metadata`,
|
|
12169
12813
|
destructive: true
|
|
12170
12814
|
};
|
|
12171
12815
|
}
|
|
@@ -12180,7 +12824,7 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12180
12824
|
const oldDefault = change.meta?.oldDefault;
|
|
12181
12825
|
if (oldDefault === void 0) {
|
|
12182
12826
|
return {
|
|
12183
|
-
sql: `-- WARNING: Cannot reverse alter_column_default "${change.table}"."${change.column}" -- missing migration metadata`,
|
|
12827
|
+
sql: `-- WARNING: Cannot reverse alter_column_default "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- missing migration metadata`,
|
|
12184
12828
|
destructive: true
|
|
12185
12829
|
};
|
|
12186
12830
|
}
|
|
@@ -12197,6 +12841,30 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12197
12841
|
destructive: false
|
|
12198
12842
|
};
|
|
12199
12843
|
}
|
|
12844
|
+
case "alter_column_unique": {
|
|
12845
|
+
const unique = change.meta?.unique;
|
|
12846
|
+
if (unique === void 0 || !change.column) {
|
|
12847
|
+
return {
|
|
12848
|
+
sql: `-- WARNING: Cannot reverse alter_column_unique "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- missing migration metadata`,
|
|
12849
|
+
destructive: true
|
|
12850
|
+
};
|
|
12851
|
+
}
|
|
12852
|
+
const constraintName = change.meta?.constraintName;
|
|
12853
|
+
return {
|
|
12854
|
+
sql: unique ? dropColumnUniqueSQL(
|
|
12855
|
+
change.table,
|
|
12856
|
+
change.column,
|
|
12857
|
+
schemaName,
|
|
12858
|
+
constraintName
|
|
12859
|
+
) : addColumnUniqueSQL(
|
|
12860
|
+
change.table,
|
|
12861
|
+
change.column,
|
|
12862
|
+
schemaName,
|
|
12863
|
+
constraintName
|
|
12864
|
+
),
|
|
12865
|
+
destructive: unique === true
|
|
12866
|
+
};
|
|
12867
|
+
}
|
|
12200
12868
|
case "add_primary_key": {
|
|
12201
12869
|
return {
|
|
12202
12870
|
sql: `ALTER TABLE ${qualifyTable(change.table, schemaName)} DROP CONSTRAINT IF EXISTS ${quoteIdent2(pkName(change.table), "alias")} CASCADE;`,
|
|
@@ -12214,7 +12882,7 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12214
12882
|
};
|
|
12215
12883
|
}
|
|
12216
12884
|
return {
|
|
12217
|
-
sql: `-- WARNING: Cannot reverse drop_primary_key "${change.table}" -- columns unknown`,
|
|
12885
|
+
sql: `-- WARNING: Cannot reverse drop_primary_key "${sanitizeCommentText(change.table)}" -- columns unknown`,
|
|
12218
12886
|
destructive: true
|
|
12219
12887
|
};
|
|
12220
12888
|
}
|
|
@@ -12234,7 +12902,7 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12234
12902
|
const fk = change.meta?.fk;
|
|
12235
12903
|
if (!fk) {
|
|
12236
12904
|
return {
|
|
12237
|
-
sql: `-- WARNING: Cannot reverse drop_foreign_key "${change.table}" -- FK definition was lost`,
|
|
12905
|
+
sql: `-- WARNING: Cannot reverse drop_foreign_key "${sanitizeCommentText(change.table)}" -- FK definition was lost`,
|
|
12238
12906
|
destructive: true
|
|
12239
12907
|
};
|
|
12240
12908
|
}
|
|
@@ -12249,7 +12917,7 @@ function changeToDownSQL(change, schemaName) {
|
|
|
12249
12917
|
const fk = change.meta?.fk;
|
|
12250
12918
|
if (!oldFk || !fk) {
|
|
12251
12919
|
return {
|
|
12252
|
-
sql: `-- WARNING: Cannot reverse alter_foreign_key "${change.table}" -- missing migration metadata`,
|
|
12920
|
+
sql: `-- WARNING: Cannot reverse alter_foreign_key "${sanitizeCommentText(change.table)}" -- missing migration metadata`,
|
|
12253
12921
|
destructive: true
|
|
12254
12922
|
};
|
|
12255
12923
|
}
|
|
@@ -12279,7 +12947,7 @@ ${add}`, destructive: false };
|
|
|
12279
12947
|
const idx = change.meta?.index;
|
|
12280
12948
|
if (!idx) {
|
|
12281
12949
|
return {
|
|
12282
|
-
sql: `-- WARNING: Cannot reverse drop_index "${change.table}" -- index definition was lost`,
|
|
12950
|
+
sql: `-- WARNING: Cannot reverse drop_index "${sanitizeCommentText(change.table)}" -- index definition was lost`,
|
|
12283
12951
|
destructive: true
|
|
12284
12952
|
};
|
|
12285
12953
|
}
|
|
@@ -12300,12 +12968,12 @@ ${add}`, destructive: false };
|
|
|
12300
12968
|
case "drop_check_constraint": {
|
|
12301
12969
|
const check = change.meta?.check;
|
|
12302
12970
|
if (!check) return { sql: void 0, destructive: true };
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
"migration check constraint (down)"
|
|
12306
|
-
);
|
|
12971
|
+
const expression = check.expression;
|
|
12972
|
+
validateCheckExpression(expression, "migration check constraint (down)");
|
|
12307
12973
|
return {
|
|
12308
|
-
sql:
|
|
12974
|
+
sql: buildDoBlock(
|
|
12975
|
+
"BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + expression + "; EXCEPTION WHEN duplicate_object THEN NULL; END"
|
|
12976
|
+
),
|
|
12309
12977
|
// Allowlisted: re-adds the dropped CHECK constraint from metadata.
|
|
12310
12978
|
destructive: false
|
|
12311
12979
|
};
|
|
@@ -12323,7 +12991,10 @@ ${add}`, destructive: false };
|
|
|
12323
12991
|
const enumDef = change.meta?.enum;
|
|
12324
12992
|
if (!enumDef) return { sql: void 0, destructive: true };
|
|
12325
12993
|
const enumName = schemaName ? `${quoteIdent2(schemaName, "alias")}.${quoteIdent2(enumDef.name, "alias")}` : quoteIdent2(enumDef.name, "alias");
|
|
12326
|
-
const values = enumDef.values.map((v) =>
|
|
12994
|
+
const values = enumDef.values.map((v) => {
|
|
12995
|
+
validateEnumLabel(v, "enum value");
|
|
12996
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
12997
|
+
}).join(", ");
|
|
12327
12998
|
return {
|
|
12328
12999
|
sql: `CREATE TYPE ${enumName} AS ENUM (${values});`,
|
|
12329
13000
|
// Allowlisted: re-creates the dropped enum type from metadata.
|
|
@@ -12337,7 +13008,7 @@ ${add}`, destructive: false };
|
|
|
12337
13008
|
};
|
|
12338
13009
|
case "alter_column_collation": {
|
|
12339
13010
|
return {
|
|
12340
|
-
sql: `-- WARNING: Cannot reverse alter_column_collation "${change.table}"."${change.column}" -- previous collation unknown`,
|
|
13011
|
+
sql: `-- WARNING: Cannot reverse alter_column_collation "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- previous collation unknown`,
|
|
12341
13012
|
destructive: true
|
|
12342
13013
|
};
|
|
12343
13014
|
}
|
|
@@ -12363,7 +13034,7 @@ ${add}`, destructive: false };
|
|
|
12363
13034
|
}
|
|
12364
13035
|
if (!prevIdentity && !col.identity) {
|
|
12365
13036
|
return {
|
|
12366
|
-
sql: `-- WARNING: Cannot reverse alter_column_identity "${change.table}"."${change.column}" -- missing previous identity metadata`,
|
|
13037
|
+
sql: `-- WARNING: Cannot reverse alter_column_identity "${sanitizeCommentText(change.table)}"."${sanitizeCommentText(change.column)}" -- missing previous identity metadata`,
|
|
12367
13038
|
destructive: true
|
|
12368
13039
|
};
|
|
12369
13040
|
}
|
|
@@ -12390,7 +13061,8 @@ ${add}`, destructive: false };
|
|
|
12390
13061
|
case "drop_comment": {
|
|
12391
13062
|
const target = change.meta?.target;
|
|
12392
13063
|
const comment = change.meta?.comment;
|
|
12393
|
-
if (
|
|
13064
|
+
if (comment !== void 0) {
|
|
13065
|
+
assertString(comment, "migration comment");
|
|
12394
13066
|
const escaped = comment.replace(/'/g, "''");
|
|
12395
13067
|
if (target === "table") {
|
|
12396
13068
|
return {
|
|
@@ -12407,8 +13079,9 @@ ${add}`, destructive: false };
|
|
|
12407
13079
|
};
|
|
12408
13080
|
}
|
|
12409
13081
|
}
|
|
13082
|
+
const columnTarget2 = change.column === void 0 ? "" : `."${sanitizeCommentText(change.column)}"`;
|
|
12410
13083
|
return {
|
|
12411
|
-
sql:
|
|
13084
|
+
sql: '-- WARNING: Cannot reverse drop_comment "' + sanitizeCommentText(change.table) + '"' + columnTarget2 + " -- comment text was lost",
|
|
12412
13085
|
destructive: true
|
|
12413
13086
|
};
|
|
12414
13087
|
}
|
|
@@ -12442,7 +13115,7 @@ ${add}`, destructive: false };
|
|
|
12442
13115
|
const prevSeq = change.meta?.previousSequence;
|
|
12443
13116
|
if (!prevSeq) {
|
|
12444
13117
|
return {
|
|
12445
|
-
sql: `-- WARNING: Cannot reverse alter_sequence "${change.table}" -- missing migration metadata`,
|
|
13118
|
+
sql: `-- WARNING: Cannot reverse alter_sequence "${sanitizeCommentText(change.table)}" -- missing migration metadata`,
|
|
12446
13119
|
destructive: true
|
|
12447
13120
|
};
|
|
12448
13121
|
}
|
|
@@ -12465,7 +13138,7 @@ ${add}`, destructive: false };
|
|
|
12465
13138
|
}
|
|
12466
13139
|
case "validate_constraint":
|
|
12467
13140
|
return {
|
|
12468
|
-
sql: `-- VALIDATE CONSTRAINT cannot be reversed in PostgreSQL (table: "${change.table}")`,
|
|
13141
|
+
sql: `-- VALIDATE CONSTRAINT cannot be reversed in PostgreSQL (table: "${sanitizeCommentText(change.table)}")`,
|
|
12469
13142
|
destructive: true
|
|
12470
13143
|
};
|
|
12471
13144
|
case "enable_rls":
|
|
@@ -12506,7 +13179,6 @@ function generateDownSQL(diff, options) {
|
|
|
12506
13179
|
function generateDownMigrationSQL(diff, options) {
|
|
12507
13180
|
const schemaName = options?.schemaName;
|
|
12508
13181
|
const includeDestructive = options?.includeDestructive ?? true;
|
|
12509
|
-
const changes = includeDestructive ? diff.changes : diff.changes.filter((c) => !c.destructive);
|
|
12510
13182
|
const phases = [
|
|
12511
13183
|
[],
|
|
12512
13184
|
// 0: drop FK, drop CHECK
|
|
@@ -12527,7 +13199,7 @@ function generateDownMigrationSQL(diff, options) {
|
|
|
12527
13199
|
[],
|
|
12528
13200
|
// 8: alter column
|
|
12529
13201
|
[],
|
|
12530
|
-
// 9: add PK
|
|
13202
|
+
// 9: add PK / column UNIQUE constraint
|
|
12531
13203
|
[],
|
|
12532
13204
|
// 10: add FK
|
|
12533
13205
|
[],
|
|
@@ -12547,7 +13219,7 @@ function generateDownMigrationSQL(diff, options) {
|
|
|
12547
13219
|
[]
|
|
12548
13220
|
// 18: CREATE/DROP POLICY
|
|
12549
13221
|
];
|
|
12550
|
-
for (const change of changes) {
|
|
13222
|
+
for (const change of diff.changes) {
|
|
12551
13223
|
const phase = getPhase(change.kind);
|
|
12552
13224
|
phases[phase].push(change);
|
|
12553
13225
|
}
|
|
@@ -12556,6 +13228,7 @@ function generateDownMigrationSQL(diff, options) {
|
|
|
12556
13228
|
for (let i = phases.length - 1; i >= 0; i--) {
|
|
12557
13229
|
for (const change of phases[i]) {
|
|
12558
13230
|
const down = changeToDownSQL(change, schemaName);
|
|
13231
|
+
if (!includeDestructive && down.destructive) continue;
|
|
12559
13232
|
destructive = down.destructive || destructive;
|
|
12560
13233
|
if (down.sql) statements.push(down.sql);
|
|
12561
13234
|
}
|
|
@@ -12600,7 +13273,10 @@ function generateAddFKSQL(tableName, fk, schemaName) {
|
|
|
12600
13273
|
const qualTable = qualifyTable(tableName, schemaName);
|
|
12601
13274
|
const constraintName = quoteIdent2(fkName(tableName, fk.columns), "alias");
|
|
12602
13275
|
const fkCols = fk.columns.map((n) => quoteIdent2(n, "alias")).join(", ");
|
|
12603
|
-
const refTable = qualifyTable(
|
|
13276
|
+
const refTable = qualifyTable(
|
|
13277
|
+
fk.references.table,
|
|
13278
|
+
fk.references.schema ?? schemaName
|
|
13279
|
+
);
|
|
12604
13280
|
const refCols = fk.references.columns.map((n) => quoteIdent2(n, "alias")).join(", ");
|
|
12605
13281
|
const onDelete = fk.onDelete ? ` ON DELETE ${mapOnDeleteAction(fk.onDelete)}` : "";
|
|
12606
13282
|
const onUpdate = fk.onUpdate ? ` ON UPDATE ${mapOnDeleteAction(fk.onUpdate)}` : "";
|
|
@@ -12686,10 +13362,11 @@ function quoteIdentifier(name) {
|
|
|
12686
13362
|
validateIdentifier(name, "alias");
|
|
12687
13363
|
return `"${name.replace(/"/g, '""')}"`;
|
|
12688
13364
|
}
|
|
12689
|
-
function qualifyTable2(tableName, schemaName, naming) {
|
|
13365
|
+
function qualifyTable2(tableName, schemaName, naming, transformSchema = true) {
|
|
12690
13366
|
const table = quoteIdentifier(naming.toDatabase(tableName));
|
|
12691
13367
|
if (schemaName) {
|
|
12692
|
-
|
|
13368
|
+
const schema = transformSchema ? naming.toDatabase(schemaName) : schemaName;
|
|
13369
|
+
return `${quoteIdentifier(schema)}.${table}`;
|
|
12693
13370
|
}
|
|
12694
13371
|
return table;
|
|
12695
13372
|
}
|
|
@@ -12760,7 +13437,7 @@ function generateAlterTableAddFK(tableName, fk, schemaName, naming) {
|
|
|
12760
13437
|
`fk_${tableName}_${fk.columns.join("_")}`
|
|
12761
13438
|
);
|
|
12762
13439
|
const fkCols = fk.columns.map((col) => quoteIdentifier(naming.toDatabase(col))).join(", ");
|
|
12763
|
-
const refTable = qualifyTable2(fk.references.table, schemaName, naming);
|
|
13440
|
+
const refTable = fk.references.schema !== void 0 ? qualifyTable2(fk.references.table, fk.references.schema, naming, false) : qualifyTable2(fk.references.table, schemaName, naming);
|
|
12764
13441
|
const refCols = fk.references.columns.map((col) => quoteIdentifier(naming.toDatabase(col))).join(", ");
|
|
12765
13442
|
const onDelete = fk.onDelete ? ` ON DELETE ${mapOnDeleteAction(fk.onDelete)}` : "";
|
|
12766
13443
|
const onUpdate = fk.onUpdate ? ` ON UPDATE ${mapOnDeleteAction(fk.onUpdate)}` : "";
|
|
@@ -12774,8 +13451,9 @@ function generateCreateIndex(tableName, idx, schemaName, naming) {
|
|
|
12774
13451
|
);
|
|
12775
13452
|
const qualifiedTable = qualifyTable2(tableName, schemaName, naming);
|
|
12776
13453
|
const unique = idx.unique ? "UNIQUE " : "";
|
|
12777
|
-
|
|
12778
|
-
|
|
13454
|
+
const indexMethod = idx.method;
|
|
13455
|
+
if (indexMethod) validateIndexMethod(indexMethod, "index method");
|
|
13456
|
+
const method = indexMethod ? ` USING ${indexMethod}` : "";
|
|
12779
13457
|
const colParts = [];
|
|
12780
13458
|
if (idx.expressions && idx.expressions.length > 0) {
|
|
12781
13459
|
for (const expr of idx.expressions) {
|
|
@@ -12795,10 +13473,11 @@ function generateCreateIndex(tableName, idx, schemaName, naming) {
|
|
|
12795
13473
|
const nullsNotDistinct = idx.unique && idx.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
|
|
12796
13474
|
const withParams = idx.with && Object.keys(idx.with).length > 0 ? ` WITH (${Object.entries(idx.with).map(([k, v]) => {
|
|
12797
13475
|
validateIdentifier(k, "alias");
|
|
12798
|
-
return `${k} = ${v}`;
|
|
13476
|
+
return `${k} = ${formatStorageParameterValue(v, `index WITH parameter "${k}"`)}`;
|
|
12799
13477
|
}).join(", ")})` : "";
|
|
12800
|
-
|
|
12801
|
-
|
|
13478
|
+
const whereExpr = idx.where;
|
|
13479
|
+
if (whereExpr) validateSqlExpression(whereExpr, "index WHERE predicate");
|
|
13480
|
+
const where = whereExpr ? ` WHERE ${whereExpr}` : "";
|
|
12802
13481
|
return `CREATE ${unique}INDEX ${indexName} ON ${qualifiedTable}${method} (${cols})${include}${nullsNotDistinct}${withParams}${where};`;
|
|
12803
13482
|
}
|
|
12804
13483
|
function generateCreatePolicy(tableName, policy, schemaName, naming) {
|
|
@@ -13025,6 +13704,7 @@ async function removeMigrationRecord(pool, name) {
|
|
|
13025
13704
|
}
|
|
13026
13705
|
|
|
13027
13706
|
// src/ddl/schema-diff.ts
|
|
13707
|
+
init_db_type();
|
|
13028
13708
|
init_naming_plugin();
|
|
13029
13709
|
function compareSchemata(schema, db, options) {
|
|
13030
13710
|
const changes = [];
|
|
@@ -13165,7 +13845,8 @@ function normalizeTable(table, plugin) {
|
|
|
13165
13845
|
columns: fk.columns.map(toDb),
|
|
13166
13846
|
references: {
|
|
13167
13847
|
table: toDb(fk.references.table),
|
|
13168
|
-
columns: fk.references.columns.map(toDb)
|
|
13848
|
+
columns: fk.references.columns.map(toDb),
|
|
13849
|
+
...fk.references.schema !== void 0 ? { schema: fk.references.schema } : {}
|
|
13169
13850
|
}
|
|
13170
13851
|
})),
|
|
13171
13852
|
indexes: table.indexes.map((idx) => ({
|
|
@@ -13211,9 +13892,7 @@ function compareColumns(schema, db, changes) {
|
|
|
13211
13892
|
}
|
|
13212
13893
|
}
|
|
13213
13894
|
function compareColumnDetails(tableName, schema, db, changes) {
|
|
13214
|
-
|
|
13215
|
-
const dbDbType = db.originalDbType?.toLowerCase();
|
|
13216
|
-
if (schemaDbType && dbDbType && schemaDbType !== dbDbType) {
|
|
13895
|
+
if (schema.originalDbType && db.originalDbType && !dbTypesEqual(schema.originalDbType, db.originalDbType)) {
|
|
13217
13896
|
changes.push({
|
|
13218
13897
|
kind: "alter_column_type",
|
|
13219
13898
|
table: tableName,
|
|
@@ -13258,6 +13937,21 @@ function compareColumnDetails(tableName, schema, db, changes) {
|
|
|
13258
13937
|
meta: { default: schema.default, oldDefault: db.default }
|
|
13259
13938
|
});
|
|
13260
13939
|
}
|
|
13940
|
+
const schemaUnique = schema.unique === true;
|
|
13941
|
+
const dbUnique = db.unique === true;
|
|
13942
|
+
if (schemaUnique !== dbUnique) {
|
|
13943
|
+
changes.push({
|
|
13944
|
+
kind: "alter_column_unique",
|
|
13945
|
+
table: tableName,
|
|
13946
|
+
column: schema.name,
|
|
13947
|
+
destructive: schemaUnique === false,
|
|
13948
|
+
details: `Change unique of "${schema.name}" from ${dbUnique} to ${schemaUnique}`,
|
|
13949
|
+
meta: {
|
|
13950
|
+
unique: schemaUnique,
|
|
13951
|
+
...db.uniqueConstraintName !== void 0 ? { constraintName: db.uniqueConstraintName } : {}
|
|
13952
|
+
}
|
|
13953
|
+
});
|
|
13954
|
+
}
|
|
13261
13955
|
if ((schema.collation ?? null) !== (db.collation ?? null)) {
|
|
13262
13956
|
changes.push({
|
|
13263
13957
|
kind: "alter_column_collation",
|
|
@@ -13329,7 +14023,8 @@ function comparePrimaryKeys(schema, db, changes) {
|
|
|
13329
14023
|
kind: "drop_primary_key",
|
|
13330
14024
|
table: schema.name,
|
|
13331
14025
|
destructive: true,
|
|
13332
|
-
details: `Drop primary key (${dbPK.join(", ")})
|
|
14026
|
+
details: `Drop primary key (${dbPK.join(", ")})`,
|
|
14027
|
+
meta: { columns: dbPK }
|
|
13333
14028
|
});
|
|
13334
14029
|
return;
|
|
13335
14030
|
}
|
|
@@ -13337,7 +14032,8 @@ function comparePrimaryKeys(schema, db, changes) {
|
|
|
13337
14032
|
kind: "drop_primary_key",
|
|
13338
14033
|
table: schema.name,
|
|
13339
14034
|
destructive: true,
|
|
13340
|
-
details: `Drop primary key (${dbPK.join(", ")})
|
|
14035
|
+
details: `Drop primary key (${dbPK.join(", ")})`,
|
|
14036
|
+
meta: { columns: dbPK }
|
|
13341
14037
|
});
|
|
13342
14038
|
changes.push({
|
|
13343
14039
|
kind: "add_primary_key",
|
|
@@ -13409,7 +14105,7 @@ function compareForeignKeys(schema, db, changes) {
|
|
|
13409
14105
|
}
|
|
13410
14106
|
}
|
|
13411
14107
|
function fkKey(fk) {
|
|
13412
|
-
return `${fk.columns.join(",")}\u2192${fk.references.table}(${fk.references.columns.join(",")})`;
|
|
14108
|
+
return `${fk.columns.join(",")}\u2192${fk.references.schema ?? ""}.${fk.references.table}(${fk.references.columns.join(",")})`;
|
|
13413
14109
|
}
|
|
13414
14110
|
function compareIndexes(schema, db, changes) {
|
|
13415
14111
|
const explicitIndexCols = new Set(
|
|
@@ -13442,16 +14138,15 @@ function compareIndexes(schema, db, changes) {
|
|
|
13442
14138
|
});
|
|
13443
14139
|
}
|
|
13444
14140
|
}
|
|
13445
|
-
const
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
)
|
|
14141
|
+
const dbColMap = new Map(db.columns.map((col) => [col.name, col]));
|
|
14142
|
+
const autoUniqueIndexColumns = new Set(
|
|
14143
|
+
schema.columns.filter((schemaCol) => {
|
|
14144
|
+
const dbCol = dbColMap.get(schemaCol.name);
|
|
14145
|
+
return dbCol !== void 0 && (schemaCol.unique === true || dbCol.unique === true);
|
|
14146
|
+
}).map((col) => col.name)
|
|
13452
14147
|
);
|
|
13453
14148
|
for (const [key, idx] of dbIdxMap) {
|
|
13454
|
-
if (!schemaIdxMap.has(key) && !autoFkIndexKeys.has(key) && !
|
|
14149
|
+
if (!schemaIdxMap.has(key) && !autoFkIndexKeys.has(key) && !isAutoUniqueIndex(schema.name, idx, autoUniqueIndexColumns)) {
|
|
13455
14150
|
changes.push({
|
|
13456
14151
|
kind: "drop_index",
|
|
13457
14152
|
table: schema.name,
|
|
@@ -13462,6 +14157,16 @@ function compareIndexes(schema, db, changes) {
|
|
|
13462
14157
|
}
|
|
13463
14158
|
}
|
|
13464
14159
|
}
|
|
14160
|
+
function isAutoUniqueIndex(tableName, idx, autoUniqueIndexColumns) {
|
|
14161
|
+
if (idx.columns.length !== 1) return false;
|
|
14162
|
+
const column = idx.columns[0];
|
|
14163
|
+
if (column === void 0 || !autoUniqueIndexColumns.has(column)) return false;
|
|
14164
|
+
if (idx.unique !== true) return false;
|
|
14165
|
+
if (idx.name !== void 0 && idx.name !== `${tableName}_${column}_key`) {
|
|
14166
|
+
return false;
|
|
14167
|
+
}
|
|
14168
|
+
return idx.nullsNotDistinct !== true && (idx.method === void 0 || idx.method === "btree") && idx.where === void 0 && (idx.expressions === void 0 || idx.expressions.length === 0) && (idx.include === void 0 || idx.include.length === 0) && (idx.opclass === void 0 || Object.keys(idx.opclass).length === 0) && (idx.with === void 0 || Object.keys(idx.with).length === 0);
|
|
14169
|
+
}
|
|
13465
14170
|
function indexKey(idx) {
|
|
13466
14171
|
const parts = [
|
|
13467
14172
|
idx.columns.join(","),
|
|
@@ -13658,8 +14363,13 @@ function compareEnums(schema, db, changes) {
|
|
|
13658
14363
|
const referencingColumns = [];
|
|
13659
14364
|
for (const [tableName, tableIR] of db.tables) {
|
|
13660
14365
|
for (const col of tableIR.columns) {
|
|
13661
|
-
|
|
13662
|
-
|
|
14366
|
+
const kind = col.originalDbType !== void 0 ? enumReferenceKind(col.originalDbType, name) : null;
|
|
14367
|
+
if (kind !== null) {
|
|
14368
|
+
referencingColumns.push({
|
|
14369
|
+
table: tableName,
|
|
14370
|
+
column: col.name,
|
|
14371
|
+
...kind === "array" ? { isArray: true } : {}
|
|
14372
|
+
});
|
|
13663
14373
|
}
|
|
13664
14374
|
}
|
|
13665
14375
|
}
|
|
@@ -13826,6 +14536,7 @@ function buildSummary(changes) {
|
|
|
13826
14536
|
case "alter_column_type":
|
|
13827
14537
|
case "alter_column_nullable":
|
|
13828
14538
|
case "alter_column_default":
|
|
14539
|
+
case "alter_column_unique":
|
|
13829
14540
|
columns.altered++;
|
|
13830
14541
|
break;
|
|
13831
14542
|
case "add_primary_key":
|
|
@@ -14048,6 +14759,7 @@ function vectorDims(column) {
|
|
|
14048
14759
|
|
|
14049
14760
|
// src/introspection.ts
|
|
14050
14761
|
init_assert_field();
|
|
14762
|
+
init_db_type();
|
|
14051
14763
|
import { ModelIRImpl } from "@dbsp/core";
|
|
14052
14764
|
import { buildRelationKeyFields } from "@dbsp/types";
|
|
14053
14765
|
function parseExpressionsList(raw) {
|
|
@@ -14076,6 +14788,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14076
14788
|
pksResult,
|
|
14077
14789
|
fksResult,
|
|
14078
14790
|
indexesResult,
|
|
14791
|
+
uniqueColumnsResult,
|
|
14079
14792
|
enumsResult,
|
|
14080
14793
|
commentsResult,
|
|
14081
14794
|
checksResult,
|
|
@@ -14083,7 +14796,8 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14083
14796
|
extensionsResult,
|
|
14084
14797
|
sequencesResult,
|
|
14085
14798
|
rlsResult,
|
|
14086
|
-
policiesResult
|
|
14799
|
+
policiesResult,
|
|
14800
|
+
formattedColumnTypesResult
|
|
14087
14801
|
] = await Promise.all([
|
|
14088
14802
|
// 1. Columns (including identity and collation)
|
|
14089
14803
|
pool.query(
|
|
@@ -14109,28 +14823,44 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14109
14823
|
// 3. Foreign keys
|
|
14110
14824
|
pool.query(
|
|
14111
14825
|
`SELECT
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14826
|
+
c.conname AS constraint_name,
|
|
14827
|
+
source_rel.relname AS source_table,
|
|
14828
|
+
source_attr.attname AS source_column,
|
|
14829
|
+
target_ns.nspname AS target_schema,
|
|
14830
|
+
target_rel.relname AS target_table,
|
|
14831
|
+
target_attr.attname AS target_column,
|
|
14832
|
+
CASE c.confdeltype
|
|
14833
|
+
WHEN 'c' THEN 'CASCADE'
|
|
14834
|
+
WHEN 'n' THEN 'SET NULL'
|
|
14835
|
+
WHEN 'd' THEN 'SET DEFAULT'
|
|
14836
|
+
WHEN 'r' THEN 'RESTRICT'
|
|
14837
|
+
ELSE 'NO ACTION'
|
|
14838
|
+
END AS delete_rule,
|
|
14839
|
+
CASE c.confupdtype
|
|
14840
|
+
WHEN 'c' THEN 'CASCADE'
|
|
14841
|
+
WHEN 'n' THEN 'SET NULL'
|
|
14842
|
+
WHEN 'd' THEN 'SET DEFAULT'
|
|
14843
|
+
WHEN 'r' THEN 'RESTRICT'
|
|
14844
|
+
ELSE 'NO ACTION'
|
|
14845
|
+
END AS update_rule,
|
|
14846
|
+
CASE WHEN c.condeferrable THEN 'YES' ELSE 'NO' END AS is_deferrable,
|
|
14847
|
+
CASE WHEN c.condeferred THEN 'YES' ELSE 'NO' END AS initially_deferred
|
|
14848
|
+
FROM pg_constraint c
|
|
14849
|
+
JOIN pg_class source_rel ON source_rel.oid = c.conrelid
|
|
14850
|
+
JOIN pg_namespace source_ns ON source_ns.oid = source_rel.relnamespace
|
|
14851
|
+
JOIN pg_class target_rel ON target_rel.oid = c.confrelid
|
|
14852
|
+
JOIN pg_namespace target_ns ON target_ns.oid = target_rel.relnamespace
|
|
14853
|
+
JOIN LATERAL unnest(c.conkey, c.confkey) WITH ORDINALITY AS cols(source_attnum, target_attnum, ord) ON true
|
|
14854
|
+
JOIN pg_attribute source_attr
|
|
14855
|
+
ON source_attr.attrelid = c.conrelid
|
|
14856
|
+
AND source_attr.attnum = cols.source_attnum
|
|
14857
|
+
JOIN pg_attribute target_attr
|
|
14858
|
+
ON target_attr.attrelid = c.confrelid
|
|
14859
|
+
AND target_attr.attnum = cols.target_attnum
|
|
14860
|
+
WHERE c.contype = 'f'
|
|
14861
|
+
AND c.conparentid = 0
|
|
14862
|
+
AND source_ns.nspname = $1
|
|
14863
|
+
ORDER BY c.conname, cols.ord`,
|
|
14134
14864
|
[schema]
|
|
14135
14865
|
),
|
|
14136
14866
|
// 4. Indexes (excluding PK-backing indexes and unique-constraint-backing indexes).
|
|
@@ -14194,7 +14924,19 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14194
14924
|
ORDER BY t.relname, i.relname`,
|
|
14195
14925
|
[schema]
|
|
14196
14926
|
),
|
|
14197
|
-
// 5.
|
|
14927
|
+
// 5. Single-column UNIQUE constraints tracked as ColumnIR.unique
|
|
14928
|
+
pool.query(
|
|
14929
|
+
`SELECT rel.relname AS table_name, att.attname AS column_name, c.conname AS constraint_name
|
|
14930
|
+
FROM pg_constraint c
|
|
14931
|
+
JOIN pg_class rel ON rel.oid = c.conrelid
|
|
14932
|
+
JOIN pg_namespace ns ON ns.oid = rel.relnamespace
|
|
14933
|
+
JOIN pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = c.conkey[1]
|
|
14934
|
+
WHERE c.contype = 'u'
|
|
14935
|
+
AND array_length(c.conkey, 1) = 1
|
|
14936
|
+
AND ns.nspname = $1`,
|
|
14937
|
+
[schema]
|
|
14938
|
+
),
|
|
14939
|
+
// 6. ENUM types
|
|
14198
14940
|
pool.query(
|
|
14199
14941
|
`SELECT
|
|
14200
14942
|
t.typname AS name,
|
|
@@ -14207,7 +14949,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14207
14949
|
GROUP BY t.typname`,
|
|
14208
14950
|
[schema]
|
|
14209
14951
|
),
|
|
14210
|
-
//
|
|
14952
|
+
// 7. Comments (table and column level) from pg_description
|
|
14211
14953
|
pool.query(
|
|
14212
14954
|
`SELECT
|
|
14213
14955
|
c.relname AS table_name,
|
|
@@ -14221,7 +14963,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14221
14963
|
AND d.objsubid >= 0`,
|
|
14222
14964
|
[schema]
|
|
14223
14965
|
),
|
|
14224
|
-
//
|
|
14966
|
+
// 8. CHECK constraints
|
|
14225
14967
|
pool.query(
|
|
14226
14968
|
`SELECT
|
|
14227
14969
|
c.conname AS name,
|
|
@@ -14233,7 +14975,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14233
14975
|
AND n.nspname = $1`,
|
|
14234
14976
|
[schema]
|
|
14235
14977
|
),
|
|
14236
|
-
//
|
|
14978
|
+
// 9. Partition configurations
|
|
14237
14979
|
pool.query(
|
|
14238
14980
|
`SELECT
|
|
14239
14981
|
c.relname AS table_name,
|
|
@@ -14248,13 +14990,13 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14248
14990
|
GROUP BY c.relname, p.partstrat`,
|
|
14249
14991
|
[schema]
|
|
14250
14992
|
),
|
|
14251
|
-
//
|
|
14993
|
+
// 10. Installed extensions (no schema param — queries globally; skip plpgsql)
|
|
14252
14994
|
pool.query(
|
|
14253
14995
|
`SELECT extname AS name
|
|
14254
14996
|
FROM pg_extension
|
|
14255
14997
|
WHERE extname != 'plpgsql'`
|
|
14256
14998
|
),
|
|
14257
|
-
//
|
|
14999
|
+
// 11. Sequences not backed by SERIAL
|
|
14258
15000
|
pool.query(
|
|
14259
15001
|
`SELECT s.sequencename AS name, s.start_value, s.increment_by, s.min_value, s.max_value, s.cycle
|
|
14260
15002
|
FROM pg_sequences s
|
|
@@ -14265,7 +15007,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14265
15007
|
AND d.objid IS NULL`,
|
|
14266
15008
|
[schema]
|
|
14267
15009
|
),
|
|
14268
|
-
//
|
|
15010
|
+
// 12. RLS enabled state per table
|
|
14269
15011
|
pool.query(
|
|
14270
15012
|
`SELECT c.relname AS table_name, c.relrowsecurity AS rls_enabled
|
|
14271
15013
|
FROM pg_class c
|
|
@@ -14274,7 +15016,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14274
15016
|
AND c.relkind = 'r'`,
|
|
14275
15017
|
[schema]
|
|
14276
15018
|
),
|
|
14277
|
-
//
|
|
15019
|
+
// 13. Row-Level Security policies
|
|
14278
15020
|
pool.query(
|
|
14279
15021
|
`SELECT
|
|
14280
15022
|
c.relname AS table_name,
|
|
@@ -14289,6 +15031,29 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14289
15031
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
14290
15032
|
WHERE n.nspname = $1`,
|
|
14291
15033
|
[schema]
|
|
15034
|
+
),
|
|
15035
|
+
// 14. Faithful SQL-facing column types via format_type, for two cases:
|
|
15036
|
+
// (a) typmod-bearing columns (atttypmod <> -1) — varchar(120), numeric(10,2),
|
|
15037
|
+
// timestamptz(3), bit(8), vector(768) — to preserve modifier fidelity (#261);
|
|
15038
|
+
// (b) array columns (typcategory 'A') — so integer[] is stored as `integer[]`
|
|
15039
|
+
// rather than the internal `_int4` udt_name.
|
|
15040
|
+
// Other columns (plain base types, enums/composites/domains) fall back to
|
|
15041
|
+
// quoteTypeIdentifier(udt_name) in buildTableIR, which keeps a bare typname for
|
|
15042
|
+
// lowercase types and quotes a case-sensitive custom type (`"Money"`).
|
|
15043
|
+
pool.query(
|
|
15044
|
+
`SELECT c.relname AS table_name,
|
|
15045
|
+
a.attname AS column_name,
|
|
15046
|
+
format_type(a.atttypid, a.atttypmod) AS db_type
|
|
15047
|
+
FROM pg_catalog.pg_attribute a
|
|
15048
|
+
JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
|
|
15049
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
15050
|
+
JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
|
|
15051
|
+
WHERE n.nspname = $1
|
|
15052
|
+
AND c.relkind IN ('r','p','v','m','f')
|
|
15053
|
+
AND a.attnum > 0
|
|
15054
|
+
AND NOT a.attisdropped
|
|
15055
|
+
AND (a.atttypmod <> -1 OR t.typcategory = 'A')`,
|
|
15056
|
+
[schema]
|
|
14292
15057
|
)
|
|
14293
15058
|
]);
|
|
14294
15059
|
return {
|
|
@@ -14296,6 +15061,7 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14296
15061
|
pks: pksResult.rows,
|
|
14297
15062
|
fks: fksResult.rows,
|
|
14298
15063
|
indexes: indexesResult.rows,
|
|
15064
|
+
uniqueColumns: uniqueColumnsResult.rows,
|
|
14299
15065
|
enums: enumsResult.rows,
|
|
14300
15066
|
comments: commentsResult.rows,
|
|
14301
15067
|
checks: checksResult.rows,
|
|
@@ -14303,7 +15069,8 @@ async function queryAllCatalogs(pool, schema) {
|
|
|
14303
15069
|
extensions: extensionsResult.rows,
|
|
14304
15070
|
sequences: sequencesResult.rows,
|
|
14305
15071
|
rls: rlsResult.rows,
|
|
14306
|
-
policies: policiesResult.rows
|
|
15072
|
+
policies: policiesResult.rows,
|
|
15073
|
+
formattedColumnTypes: formattedColumnTypesResult.rows
|
|
14307
15074
|
};
|
|
14308
15075
|
}
|
|
14309
15076
|
function buildPartitionMap(rows) {
|
|
@@ -14381,6 +15148,21 @@ function buildIndexMap(rows) {
|
|
|
14381
15148
|
}
|
|
14382
15149
|
return result;
|
|
14383
15150
|
}
|
|
15151
|
+
function buildUniqueColumnMap(rows) {
|
|
15152
|
+
const result = /* @__PURE__ */ new Map();
|
|
15153
|
+
for (const row of rows) {
|
|
15154
|
+
const existing = result.get(row.table_name);
|
|
15155
|
+
if (existing) {
|
|
15156
|
+
existing.set(row.column_name, row.constraint_name);
|
|
15157
|
+
} else {
|
|
15158
|
+
result.set(
|
|
15159
|
+
row.table_name,
|
|
15160
|
+
/* @__PURE__ */ new Map([[row.column_name, row.constraint_name]])
|
|
15161
|
+
);
|
|
15162
|
+
}
|
|
15163
|
+
}
|
|
15164
|
+
return result;
|
|
15165
|
+
}
|
|
14384
15166
|
function buildColumnMap(rows) {
|
|
14385
15167
|
const result = /* @__PURE__ */ new Map();
|
|
14386
15168
|
for (const col of rows) {
|
|
@@ -14408,14 +15190,16 @@ function buildPKMap(rows) {
|
|
|
14408
15190
|
function buildFKMap(rows) {
|
|
14409
15191
|
const result = /* @__PURE__ */ new Map();
|
|
14410
15192
|
for (const fk of rows) {
|
|
14411
|
-
const
|
|
15193
|
+
const key = JSON.stringify([fk.source_table, fk.constraint_name]);
|
|
15194
|
+
const existing = result.get(key);
|
|
14412
15195
|
if (existing) {
|
|
14413
15196
|
existing.cols.push(fk.source_column);
|
|
14414
15197
|
existing.refs.push(fk.target_column);
|
|
14415
15198
|
} else {
|
|
14416
|
-
result.set(
|
|
15199
|
+
result.set(key, {
|
|
14417
15200
|
source: fk.source_table,
|
|
14418
15201
|
target: fk.target_table,
|
|
15202
|
+
targetSchema: fk.target_schema,
|
|
14419
15203
|
cols: [fk.source_column],
|
|
14420
15204
|
refs: [fk.target_column],
|
|
14421
15205
|
deleteRule: fk.delete_rule,
|
|
@@ -14478,13 +15262,25 @@ function buildRLSMaps(rlsRows, policiesRows) {
|
|
|
14478
15262
|
}
|
|
14479
15263
|
return { tableRlsEnabled, tablePolicies };
|
|
14480
15264
|
}
|
|
15265
|
+
function columnTypeMapKey(tableName, columnName) {
|
|
15266
|
+
return `${tableName}\0${columnName}`;
|
|
15267
|
+
}
|
|
15268
|
+
function buildFormattedColumnTypeMap(rows) {
|
|
15269
|
+
const result = /* @__PURE__ */ new Map();
|
|
15270
|
+
for (const row of rows) {
|
|
15271
|
+
result.set(columnTypeMapKey(row.table_name, row.column_name), row.db_type);
|
|
15272
|
+
}
|
|
15273
|
+
return result;
|
|
15274
|
+
}
|
|
14481
15275
|
function buildTableIR(tableName, ctx) {
|
|
14482
15276
|
const rawCols = ctx.tableColumns.get(tableName) ?? [];
|
|
14483
15277
|
const pkCols = ctx.tablePKs.get(tableName);
|
|
15278
|
+
const uniqueColumns = ctx.uniqueColumns.get(tableName);
|
|
14484
15279
|
const columns = rawCols.map((col) => {
|
|
14485
15280
|
const identity = col.is_identity === "YES" && col.identity_generation ? col.identity_generation === "ALWAYS" ? "always" : "byDefault" : void 0;
|
|
14486
15281
|
const collation = col.collation_name && col.collation_name !== "default" ? col.collation_name : void 0;
|
|
14487
15282
|
const colComment = ctx.columnComments.get(`${tableName}.${col.column_name}`) ?? void 0;
|
|
15283
|
+
const uniqueConstraintName = uniqueColumns?.get(col.column_name);
|
|
14488
15284
|
return {
|
|
14489
15285
|
name: col.column_name,
|
|
14490
15286
|
type: mapPgType(col.data_type, col.udt_name),
|
|
@@ -14493,7 +15289,14 @@ function buildTableIR(tableName, ctx) {
|
|
|
14493
15289
|
// it verbatim (e.g. CURRENT_TIMESTAMP, nextval('seq'::regclass)) instead
|
|
14494
15290
|
// of quoting it as a string literal.
|
|
14495
15291
|
...col.column_default != null ? { default: { sql: col.column_default } } : {},
|
|
14496
|
-
|
|
15292
|
+
// format_type output (typmod-bearing + array columns) is already
|
|
15293
|
+
// SQL-facing; the udt_name fallback is a bare catalog typname, so
|
|
15294
|
+
// quote it when case-sensitive (`"Money"`) — a lowercase typname
|
|
15295
|
+
// stays bare, preserving the common enum/UDT identity.
|
|
15296
|
+
originalDbType: ctx.formattedColumnTypes.get(
|
|
15297
|
+
columnTypeMapKey(tableName, col.column_name)
|
|
15298
|
+
) ?? quoteTypeIdentifier(col.udt_name),
|
|
15299
|
+
...uniqueConstraintName !== void 0 ? { unique: true, uniqueConstraintName } : {},
|
|
14497
15300
|
...collation ? { collation } : {},
|
|
14498
15301
|
...identity ? { identity } : {},
|
|
14499
15302
|
...colComment ? { comment: colComment } : {}
|
|
@@ -14502,12 +15305,17 @@ function buildTableIR(tableName, ctx) {
|
|
|
14502
15305
|
const foreignKeys = [];
|
|
14503
15306
|
for (const [, fk] of ctx.fksByConstraint) {
|
|
14504
15307
|
if (fk.source !== tableName) continue;
|
|
14505
|
-
|
|
15308
|
+
const isCrossSchema = fk.targetSchema !== void 0 && fk.targetSchema !== ctx.schema;
|
|
15309
|
+
if (!isCrossSchema && !ctx.tableNames.includes(fk.target)) continue;
|
|
14506
15310
|
const onDelete = mapDeleteRule(fk.deleteRule);
|
|
14507
15311
|
const onUpdate = mapDeleteRule(fk.updateRule);
|
|
14508
15312
|
foreignKeys.push({
|
|
14509
15313
|
columns: fk.cols,
|
|
14510
|
-
references: {
|
|
15314
|
+
references: {
|
|
15315
|
+
table: fk.target,
|
|
15316
|
+
columns: fk.refs,
|
|
15317
|
+
...isCrossSchema ? { schema: fk.targetSchema } : {}
|
|
15318
|
+
},
|
|
14511
15319
|
...onDelete !== "NO ACTION" ? { onDelete } : {},
|
|
14512
15320
|
...onUpdate !== "NO ACTION" ? { onUpdate } : {},
|
|
14513
15321
|
...fk.deferred ? { deferred: true } : {}
|
|
@@ -14555,7 +15363,11 @@ async function introspect(pool, options) {
|
|
|
14555
15363
|
const tablePartitions = buildPartitionMap(raw.partitions);
|
|
14556
15364
|
const tableChecks = buildCheckMap(raw.checks);
|
|
14557
15365
|
const tableIndexes = buildIndexMap(raw.indexes);
|
|
15366
|
+
const uniqueColumns = buildUniqueColumnMap(raw.uniqueColumns);
|
|
14558
15367
|
const tableColumns = buildColumnMap(raw.columns);
|
|
15368
|
+
const formattedColumnTypes = buildFormattedColumnTypeMap(
|
|
15369
|
+
raw.formattedColumnTypes
|
|
15370
|
+
);
|
|
14559
15371
|
const tablePKs = buildPKMap(raw.pks);
|
|
14560
15372
|
const fksByConstraint = buildFKMap(raw.fks);
|
|
14561
15373
|
const enumMap = buildEnumMap(raw.enums);
|
|
@@ -14570,9 +15382,11 @@ async function introspect(pool, options) {
|
|
|
14570
15382
|
for (const tableName of tableNames) {
|
|
14571
15383
|
const table = buildTableIR(tableName, {
|
|
14572
15384
|
tableColumns,
|
|
15385
|
+
formattedColumnTypes,
|
|
14573
15386
|
tablePKs,
|
|
14574
15387
|
fksByConstraint,
|
|
14575
15388
|
tableIndexes,
|
|
15389
|
+
uniqueColumns,
|
|
14576
15390
|
tableChecks,
|
|
14577
15391
|
tableComments,
|
|
14578
15392
|
columnComments,
|
|
@@ -14580,20 +15394,33 @@ async function introspect(pool, options) {
|
|
|
14580
15394
|
tableRlsEnabled,
|
|
14581
15395
|
tablePolicies,
|
|
14582
15396
|
tableNames,
|
|
15397
|
+
schema,
|
|
14583
15398
|
warnings
|
|
14584
15399
|
});
|
|
14585
15400
|
tables.set(tableName, table);
|
|
14586
15401
|
}
|
|
14587
15402
|
const extensions = raw.extensions.map((r) => r.name);
|
|
14588
15403
|
const sequenceMap = buildSequenceMap(raw.sequences);
|
|
14589
|
-
const relations = inferRelations(fksByConstraint, tableNames);
|
|
14590
|
-
const hierarchies = detectHierarchies(
|
|
15404
|
+
const relations = inferRelations(fksByConstraint, tableNames, schema);
|
|
15405
|
+
const hierarchies = detectHierarchies(
|
|
15406
|
+
tables,
|
|
15407
|
+
fksByConstraint,
|
|
15408
|
+
tableNames,
|
|
15409
|
+
schema
|
|
15410
|
+
);
|
|
15411
|
+
const externalTables = /* @__PURE__ */ new Set();
|
|
15412
|
+
for (const fk of fksByConstraint.values()) {
|
|
15413
|
+
if (fk.targetSchema !== void 0 && fk.targetSchema !== schema && !tables.has(fk.target)) {
|
|
15414
|
+
externalTables.add(fk.target);
|
|
15415
|
+
}
|
|
15416
|
+
}
|
|
14591
15417
|
const modelIR = new ModelIRImpl(
|
|
14592
15418
|
tables,
|
|
14593
15419
|
relations,
|
|
14594
15420
|
enumMap,
|
|
14595
15421
|
extensions,
|
|
14596
|
-
sequenceMap
|
|
15422
|
+
sequenceMap,
|
|
15423
|
+
externalTables
|
|
14597
15424
|
);
|
|
14598
15425
|
return Object.assign(modelIR, {
|
|
14599
15426
|
hierarchies,
|
|
@@ -14601,10 +15428,11 @@ async function introspect(pool, options) {
|
|
|
14601
15428
|
warnings
|
|
14602
15429
|
});
|
|
14603
15430
|
}
|
|
14604
|
-
function inferRelations(fksByConstraint, filteredTables) {
|
|
15431
|
+
function inferRelations(fksByConstraint, filteredTables, schema) {
|
|
14605
15432
|
const relations = /* @__PURE__ */ new Map();
|
|
14606
15433
|
const filteredSet = new Set(filteredTables);
|
|
14607
15434
|
for (const [, fk] of fksByConstraint) {
|
|
15435
|
+
if (fk.targetSchema !== void 0 && fk.targetSchema !== schema) continue;
|
|
14608
15436
|
if (!filteredSet.has(fk.source) || !filteredSet.has(fk.target)) continue;
|
|
14609
15437
|
const belongsToName = deriveRelationName(fk.cols[0], fk.target);
|
|
14610
15438
|
const relationFk = {
|
|
@@ -14654,13 +15482,14 @@ function deriveRelationName(fkColumn, _targetTable) {
|
|
|
14654
15482
|
}
|
|
14655
15483
|
return fkColumn;
|
|
14656
15484
|
}
|
|
14657
|
-
function detectHierarchies(tables, fksByConstraint, filteredTables) {
|
|
15485
|
+
function detectHierarchies(tables, fksByConstraint, filteredTables, schema) {
|
|
14658
15486
|
const hierarchies = [];
|
|
14659
15487
|
const filteredSet = new Set(filteredTables);
|
|
14660
15488
|
const fksBySourceTarget = /* @__PURE__ */ new Map();
|
|
14661
15489
|
for (const [, fk] of fksByConstraint) {
|
|
15490
|
+
if (fk.targetSchema !== void 0 && fk.targetSchema !== schema) continue;
|
|
14662
15491
|
if (!filteredSet.has(fk.source) || !filteredSet.has(fk.target)) continue;
|
|
14663
|
-
if (fk.source === fk.target && fk.cols.length === 1) {
|
|
15492
|
+
if (fk.source === fk.target && fk.cols.length === 1 && (fk.targetSchema === void 0 || fk.targetSchema === schema)) {
|
|
14664
15493
|
const table = tables.get(fk.source);
|
|
14665
15494
|
const pk = table?.primaryKey;
|
|
14666
15495
|
const nodeIdColumn = typeof pk === "string" ? pk : pk?.[0] ?? DEFAULT_PK_COLUMN;
|
|
@@ -15601,11 +16430,7 @@ init_naming_plugin();
|
|
|
15601
16430
|
init_param_ref();
|
|
15602
16431
|
|
|
15603
16432
|
// src/pgsql-adapter.ts
|
|
15604
|
-
import {
|
|
15605
|
-
POSTGRESQL_CAPABILITIES as POSTGRESQL_CAPABILITIES2,
|
|
15606
|
-
plan as planFn2,
|
|
15607
|
-
validateTypeName as validateTypeName4
|
|
15608
|
-
} from "@dbsp/core";
|
|
16433
|
+
import { POSTGRESQL_CAPABILITIES as POSTGRESQL_CAPABILITIES2, plan as planFn2 } from "@dbsp/core";
|
|
15609
16434
|
import { getNqlBindingRefName as getNqlBindingRefName2, isNqlBindingRef as isNqlBindingRef2 } from "@dbsp/types/internal";
|
|
15610
16435
|
|
|
15611
16436
|
// src/adapter-compiler-includes.ts
|
|
@@ -15825,6 +16650,7 @@ init_compile_where();
|
|
|
15825
16650
|
import { countDistinctRelationPathsByName } from "@dbsp/core";
|
|
15826
16651
|
import { toColumnList as toColumnList10 } from "@dbsp/types";
|
|
15827
16652
|
init_compiler_utils();
|
|
16653
|
+
init_db_type();
|
|
15828
16654
|
init_types();
|
|
15829
16655
|
init_intent_to_decisions();
|
|
15830
16656
|
init_param_ref();
|
|
@@ -16727,16 +17553,6 @@ function buildIncludeTree(allDecisions) {
|
|
|
16727
17553
|
}
|
|
16728
17554
|
|
|
16729
17555
|
// src/adapter-compiler-select.ts
|
|
16730
|
-
var ADAPTER_MULTIWORD_BASE_TYPES = [
|
|
16731
|
-
"timestamp with time zone",
|
|
16732
|
-
"timestamp without time zone",
|
|
16733
|
-
"time with time zone",
|
|
16734
|
-
"time without time zone",
|
|
16735
|
-
"double precision",
|
|
16736
|
-
"character varying",
|
|
16737
|
-
"bit varying"
|
|
16738
|
-
];
|
|
16739
|
-
var ADAPTER_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16740
17556
|
function assertSafeTypeName(typeName, colIndex) {
|
|
16741
17557
|
const raw = typeName.trim();
|
|
16742
17558
|
if (raw.length === 0) {
|
|
@@ -16744,42 +17560,14 @@ function assertSafeTypeName(typeName, colIndex) {
|
|
|
16744
17560
|
`BatchValues compile error: type name at column index ${colIndex} must not be empty.`
|
|
16745
17561
|
);
|
|
16746
17562
|
}
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
throw new Error(
|
|
16752
|
-
`BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. At most one array suffix "[]" is allowed as a raw type-name input.`
|
|
16753
|
-
);
|
|
16754
|
-
}
|
|
16755
|
-
}
|
|
16756
|
-
const modifierMatch = rest.match(/\(([^)]*)\)$/);
|
|
16757
|
-
if (modifierMatch) {
|
|
16758
|
-
const inner = modifierMatch[1] ?? "";
|
|
16759
|
-
if (!/^\d+(?:,\d+)?$/.test(inner)) {
|
|
16760
|
-
throw new Error(
|
|
16761
|
-
`BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Type modifier must be "(N)" or "(N,M)" with digits only; got "(${inner})".`
|
|
16762
|
-
);
|
|
16763
|
-
}
|
|
16764
|
-
rest = rest.slice(0, rest.length - modifierMatch[0].length).trimEnd();
|
|
16765
|
-
}
|
|
16766
|
-
const baseLower = rest.toLowerCase();
|
|
16767
|
-
if (ADAPTER_MULTIWORD_BASE_TYPES.includes(baseLower)) {
|
|
16768
|
-
return;
|
|
16769
|
-
}
|
|
16770
|
-
const parts = rest.split(".");
|
|
16771
|
-
if (parts.length > 2) {
|
|
17563
|
+
try {
|
|
17564
|
+
validateDbType(raw);
|
|
17565
|
+
} catch (error) {
|
|
17566
|
+
const reason = error instanceof Error ? ` ${error.message}` : "";
|
|
16772
17567
|
throw new Error(
|
|
16773
|
-
`BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}
|
|
17568
|
+
`BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}.${reason}`
|
|
16774
17569
|
);
|
|
16775
17570
|
}
|
|
16776
|
-
for (const part of parts) {
|
|
16777
|
-
if (!ADAPTER_IDENT_RE.test(part)) {
|
|
16778
|
-
throw new Error(
|
|
16779
|
-
`BatchValues compile error: unsafe type name '${typeName}' at column index ${colIndex}. Base type "${part}" is not a valid SQL identifier and is not in the multi-word type allowlist.`
|
|
16780
|
-
);
|
|
16781
|
-
}
|
|
16782
|
-
}
|
|
16783
17571
|
}
|
|
16784
17572
|
function buildBatchValuesRangeFn(bv, startParamIndex, aliasOverride) {
|
|
16785
17573
|
const params = [];
|
|
@@ -16935,14 +17723,16 @@ function compileJoinIntents(joins, rootTable, schemaName, deps) {
|
|
|
16935
17723
|
schemaForFromName(schemaName, intent.table, deps.bindingNames, naming),
|
|
16936
17724
|
naming
|
|
16937
17725
|
);
|
|
16938
|
-
|
|
17726
|
+
const joinDecision = {
|
|
16939
17727
|
type: "join",
|
|
16940
17728
|
targetTable: intent.table,
|
|
16941
17729
|
alias: tableAlias,
|
|
16942
17730
|
joinType: intent.type,
|
|
16943
17731
|
joinRarg: joinedRangeVar,
|
|
16944
|
-
joinOnNode: onNode
|
|
16945
|
-
|
|
17732
|
+
joinOnNode: onNode,
|
|
17733
|
+
joinOnParams: paramState.parameters
|
|
17734
|
+
};
|
|
17735
|
+
results.push(joinDecision);
|
|
16946
17736
|
}
|
|
16947
17737
|
}
|
|
16948
17738
|
return results;
|
|
@@ -17240,6 +18030,7 @@ function compileWithIncludes(plan, options, deps) {
|
|
|
17240
18030
|
init_binding_registry();
|
|
17241
18031
|
init_compile_where();
|
|
17242
18032
|
init_compiler_utils();
|
|
18033
|
+
init_db_type();
|
|
17243
18034
|
init_handlers();
|
|
17244
18035
|
init_cte_compiler();
|
|
17245
18036
|
function whereIntentAsDecision(where) {
|
|
@@ -17352,7 +18143,14 @@ function getColumnTypes(tableName, columns, deps) {
|
|
|
17352
18143
|
const columnIR = table.columns.find((c) => c.name === col);
|
|
17353
18144
|
if (columnIR) {
|
|
17354
18145
|
result ??= {};
|
|
17355
|
-
|
|
18146
|
+
const authored = columnIR.originalDbType?.trim();
|
|
18147
|
+
const castTarget = authored !== void 0 && authored !== "" ? dbTypeCastTarget(validateDbType(authored)) : columnIR.type;
|
|
18148
|
+
if (castTarget.trim().endsWith("[]")) {
|
|
18149
|
+
throw new Error(
|
|
18150
|
+
`Batch mutation of array-typed column '${col}' (${castTarget}) is not supported: unnest flattens multi-dimensional arrays. Use single-row mutations for array columns.`
|
|
18151
|
+
);
|
|
18152
|
+
}
|
|
18153
|
+
result[col] = castTarget;
|
|
17356
18154
|
}
|
|
17357
18155
|
}
|
|
17358
18156
|
return result;
|
|
@@ -18139,6 +18937,7 @@ function buildRecursiveAnchorWhere(where, tableAlias, deps, state) {
|
|
|
18139
18937
|
init_assert_field();
|
|
18140
18938
|
init_ast_helpers();
|
|
18141
18939
|
init_binding_registry();
|
|
18940
|
+
init_db_type();
|
|
18142
18941
|
|
|
18143
18942
|
// src/ddl/index-operations.ts
|
|
18144
18943
|
init_validate();
|
|
@@ -18149,8 +18948,9 @@ function qualifyTable3(table, schema) {
|
|
|
18149
18948
|
return schema ? `${quoteIdent2(schema, "schema")}.${quoteIdent2(table, "table")}` : quoteIdent2(table, "table");
|
|
18150
18949
|
}
|
|
18151
18950
|
function generateCreateIndexSQL(table, options, schema) {
|
|
18152
|
-
|
|
18153
|
-
|
|
18951
|
+
const indexMethod = options.method;
|
|
18952
|
+
if (indexMethod !== void 0) {
|
|
18953
|
+
validateIndexMethod(indexMethod, "index method");
|
|
18154
18954
|
}
|
|
18155
18955
|
const parts = ["CREATE"];
|
|
18156
18956
|
if (options.unique) parts.push("UNIQUE");
|
|
@@ -18159,8 +18959,8 @@ function generateCreateIndexSQL(table, options, schema) {
|
|
|
18159
18959
|
if (options.ifNotExists) parts.push("IF NOT EXISTS");
|
|
18160
18960
|
parts.push(quoteIdentifier2(options.name));
|
|
18161
18961
|
parts.push(`ON ${qualifyTable3(table, schema)}`);
|
|
18162
|
-
if (
|
|
18163
|
-
parts.push(`USING ${
|
|
18962
|
+
if (indexMethod) {
|
|
18963
|
+
parts.push(`USING ${indexMethod}`);
|
|
18164
18964
|
}
|
|
18165
18965
|
const colParts = [];
|
|
18166
18966
|
for (const col of options.columns) {
|
|
@@ -18177,13 +18977,14 @@ function generateCreateIndexSQL(table, options, schema) {
|
|
|
18177
18977
|
if (options.with && Object.keys(options.with).length > 0) {
|
|
18178
18978
|
const withParams = Object.entries(options.with).map(([k, v]) => {
|
|
18179
18979
|
validateIdentifier(k, "alias");
|
|
18180
|
-
return `${k} = ${v}`;
|
|
18980
|
+
return `${k} = ${formatStorageParameterValue(v, `index WITH parameter "${k}"`)}`;
|
|
18181
18981
|
}).join(", ");
|
|
18182
18982
|
parts.push(`WITH (${withParams})`);
|
|
18183
18983
|
}
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18984
|
+
const whereExpr = options.where;
|
|
18985
|
+
if (whereExpr) {
|
|
18986
|
+
validateSqlExpression(whereExpr, "index WHERE predicate");
|
|
18987
|
+
parts.push(`WHERE ${whereExpr}`);
|
|
18187
18988
|
}
|
|
18188
18989
|
return parts.join(" ");
|
|
18189
18990
|
}
|
|
@@ -18194,10 +18995,11 @@ function buildColumnPart(col, opclassMap) {
|
|
|
18194
18995
|
if (opclass2) validateIdentifier(opclass2, "alias");
|
|
18195
18996
|
return opclass2 ? `${quoted} ${opclass2}` : quoted;
|
|
18196
18997
|
}
|
|
18197
|
-
|
|
18998
|
+
const expression = col.expression;
|
|
18999
|
+
validateSqlExpression(expression, "index expression");
|
|
18198
19000
|
const opclass = col.opclass;
|
|
18199
19001
|
if (opclass) validateIdentifier(opclass, "alias");
|
|
18200
|
-
return opclass ? `${
|
|
19002
|
+
return opclass ? `${expression} ${opclass}` : expression;
|
|
18201
19003
|
}
|
|
18202
19004
|
function generateDropIndexSQL(name, options) {
|
|
18203
19005
|
const parts = ["DROP INDEX"];
|
|
@@ -18589,7 +19391,8 @@ function resolveRuntimeBindingColumnType(bindingName, sourceTable, columnName) {
|
|
|
18589
19391
|
`NQL runtime binding '${bindingName}' cannot resolve projected column '${columnName}' on source table '${sourceTable.name}'.`
|
|
18590
19392
|
);
|
|
18591
19393
|
}
|
|
18592
|
-
const
|
|
19394
|
+
const originalDbType = column.originalDbType?.trim();
|
|
19395
|
+
const dbType = originalDbType || mapRuntimeBindingColumnType(column.type);
|
|
18593
19396
|
if (dbType === void 0 || dbType.trim() === "") {
|
|
18594
19397
|
throw new Error(
|
|
18595
19398
|
`NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${columnName}' on source table '${sourceTable.name}'.`
|
|
@@ -18597,14 +19400,14 @@ function resolveRuntimeBindingColumnType(bindingName, sourceTable, columnName) {
|
|
|
18597
19400
|
}
|
|
18598
19401
|
const typeName = dbType.trim();
|
|
18599
19402
|
try {
|
|
18600
|
-
|
|
19403
|
+
validateDbType(typeName);
|
|
18601
19404
|
} catch (error) {
|
|
18602
19405
|
const reason = error instanceof Error ? error.message : String(error);
|
|
18603
19406
|
throw new Error(
|
|
18604
19407
|
`NQL runtime binding '${bindingName}' cannot use PostgreSQL cast type for projected column '${columnName}': ${reason}`
|
|
18605
19408
|
);
|
|
18606
19409
|
}
|
|
18607
|
-
return typeName;
|
|
19410
|
+
return originalDbType !== void 0 ? dbTypeCastTarget(typeName) : typeName;
|
|
18608
19411
|
}
|
|
18609
19412
|
function resolveRuntimeBindingColumnTypes(name, binding, model, sourceTableName) {
|
|
18610
19413
|
if (model === void 0) {
|
|
@@ -18638,7 +19441,8 @@ function resolvePgTypeForColumnTypeInfo(bindingName, column, info) {
|
|
|
18638
19441
|
`NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${column}': unsupported aggregate kind '${String(info.fn)}'.`
|
|
18639
19442
|
);
|
|
18640
19443
|
}
|
|
18641
|
-
const
|
|
19444
|
+
const originalDbType = info.kind === "aggregate" ? void 0 : info.originalDbType?.trim();
|
|
19445
|
+
const rawType = info.kind === "aggregate" ? "bigint" : originalDbType || mapRuntimeBindingColumnType(info.type);
|
|
18642
19446
|
if (rawType === void 0 || rawType.trim() === "") {
|
|
18643
19447
|
throw new Error(
|
|
18644
19448
|
`NQL runtime binding '${bindingName}' cannot resolve a PostgreSQL type for projected column '${column}'.`
|
|
@@ -18646,14 +19450,14 @@ function resolvePgTypeForColumnTypeInfo(bindingName, column, info) {
|
|
|
18646
19450
|
}
|
|
18647
19451
|
const typeName = rawType.trim();
|
|
18648
19452
|
try {
|
|
18649
|
-
|
|
19453
|
+
validateDbType(typeName);
|
|
18650
19454
|
} catch (error) {
|
|
18651
19455
|
const reason = error instanceof Error ? error.message : String(error);
|
|
18652
19456
|
throw new Error(
|
|
18653
19457
|
`NQL runtime binding '${bindingName}' cannot use PostgreSQL cast type for projected column '${column}': ${reason}`
|
|
18654
19458
|
);
|
|
18655
19459
|
}
|
|
18656
|
-
return typeName;
|
|
19460
|
+
return originalDbType !== void 0 ? dbTypeCastTarget(typeName) : typeName;
|
|
18657
19461
|
}
|
|
18658
19462
|
function resolveRuntimeBindingCteColumnTypes(name, binding) {
|
|
18659
19463
|
const columnTypes = binding.columnTypes;
|