@vibeorm/sql 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ast.d.ts +331 -11
- package/dist/codecs.d.ts +48 -0
- package/dist/dialect.d.ts +44 -0
- package/dist/dialects/postgres-index-keys.d.ts +1 -0
- package/dist/dialects/postgres-physical-catalog.d.ts +7 -1
- package/dist/dialects/sqlite-fts.d.ts +28 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +757 -193
- package/dist/index.js.map +19 -17
- package/dist/json-list-parameter.d.ts +37 -0
- package/dist/pg-array.d.ts +13 -0
- package/dist/render.d.ts +11 -1
- package/package.json +5 -3
- package/dist/ast.d.ts.map +0 -1
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/codecs.d.ts.map +0 -1
- package/dist/composable.d.ts.map +0 -1
- package/dist/dialect.d.ts.map +0 -1
- package/dist/dialects/constraint-owned-index.d.ts.map +0 -1
- package/dist/dialects/diagnostics.d.ts.map +0 -1
- package/dist/dialects/external-dependents.d.ts.map +0 -1
- package/dist/dialects/external-ownership.d.ts.map +0 -1
- package/dist/dialects/migration-history.d.ts.map +0 -1
- package/dist/dialects/migration-inventory.d.ts.map +0 -1
- package/dist/dialects/migration-objects.d.ts.map +0 -1
- package/dist/dialects/migration-replay-coverage.d.ts.map +0 -1
- package/dist/dialects/migration-shadow.d.ts.map +0 -1
- package/dist/dialects/migration-visibility.d.ts.map +0 -1
- package/dist/dialects/module-dependency.d.ts.map +0 -1
- package/dist/dialects/mysql-check-probe.d.ts.map +0 -1
- package/dist/dialects/mysql.d.ts.map +0 -1
- package/dist/dialects/postgres-default-identity.d.ts.map +0 -1
- package/dist/dialects/postgres-diagnostics.d.ts.map +0 -1
- package/dist/dialects/postgres-index-keys.d.ts.map +0 -1
- package/dist/dialects/postgres-migration.d.ts.map +0 -1
- package/dist/dialects/postgres-physical-catalog.d.ts.map +0 -1
- package/dist/dialects/postgres-rls-readiness.d.ts.map +0 -1
- package/dist/dialects/postgres-rls.d.ts.map +0 -1
- package/dist/dialects/postgres-type-identity.d.ts.map +0 -1
- package/dist/dialects/postgres.d.ts.map +0 -1
- package/dist/dialects/registry.d.ts.map +0 -1
- package/dist/dialects/sqlite-constraints.d.ts.map +0 -1
- package/dist/dialects/sqlite-session.d.ts.map +0 -1
- package/dist/dialects/sqlite-table-definition.d.ts.map +0 -1
- package/dist/dialects/sqlite.d.ts.map +0 -1
- package/dist/exclusion.d.ts.map +0 -1
- package/dist/identifiers.d.ts.map +0 -1
- package/dist/index-columns.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/json-transport.d.ts.map +0 -1
- package/dist/like.d.ts.map +0 -1
- package/dist/locks.d.ts.map +0 -1
- package/dist/module-journal.d.ts.map +0 -1
- package/dist/native-enum-parameter.d.ts.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/pg-array.d.ts.map +0 -1
- package/dist/predicate.d.ts.map +0 -1
- package/dist/render.d.ts.map +0 -1
- package/dist/result-decoder.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,14 @@ function nativeEnumArrayParameter(params) {
|
|
|
7
7
|
function isNativeEnumArrayParameter(value) {
|
|
8
8
|
return typeof value === "object" && value !== null && NATIVE_ENUM_ARRAY_PARAMETER in value;
|
|
9
9
|
}
|
|
10
|
+
// packages/sql/src/json-list-parameter.ts
|
|
11
|
+
var JSON_LIST_PARAMETER = Symbol("vibeorm.jsonListParameter");
|
|
12
|
+
function jsonListParameter(params) {
|
|
13
|
+
return { [JSON_LIST_PARAMETER]: true, value: params.value, storage: params.storage };
|
|
14
|
+
}
|
|
15
|
+
function isJsonListParameter(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && JSON_LIST_PARAMETER in value;
|
|
17
|
+
}
|
|
10
18
|
// packages/sql/src/dialects/external-ownership.ts
|
|
11
19
|
import { unsupportedCapability } from "@vibeorm/schema";
|
|
12
20
|
function renderExternalOwnershipCatalog(params) {
|
|
@@ -49,6 +57,13 @@ class ParamCollector {
|
|
|
49
57
|
this.values.push(value.value);
|
|
50
58
|
return `${this.dialect.placeholder({ index: this.values.length })}::${this.dialect.quoteIdent({ name: value.type.namespace })}.${this.dialect.quoteIdent({ name: value.type.name })}[]`;
|
|
51
59
|
}
|
|
60
|
+
if (isJsonListParameter(value)) {
|
|
61
|
+
if (!this.dialect.capabilities.scalarArrays)
|
|
62
|
+
throw unsupportedCapability2({ dialect: this.dialect.name, feature: "scalarArrays" });
|
|
63
|
+
const whole = typeof value.value !== "string";
|
|
64
|
+
this.values.push(whole ? [...value.value] : value.value);
|
|
65
|
+
return `${this.dialect.placeholder({ index: this.values.length })}::text::${value.storage}${whole ? "[]" : ""}`;
|
|
66
|
+
}
|
|
52
67
|
this.values.push(value);
|
|
53
68
|
return this.dialect.placeholder({ index: this.values.length });
|
|
54
69
|
}
|
|
@@ -131,7 +146,7 @@ function renderPostgresPhysicalCatalog(params) {
|
|
|
131
146
|
text = `SELECT c.conname AS constraint_name, cl.relname AS table_name, att.attname AS column_name, fn.nspname AS foreign_namespace, fcl.relname AS foreign_table_name, fatt.attname AS foreign_column_name, c.confdeltype AS on_delete_char, c.confupdtype AS on_update_char, c.convalidated AS validated, c.condeferrable AS deferrable, (SELECT bool_and(t.tgenabled IN ('O', 'A')) FROM pg_catalog.pg_trigger t WHERE t.tgconstraint = c.oid) AS triggers_enabled FROM pg_catalog.pg_constraint c JOIN pg_catalog.pg_class cl ON cl.oid = c.conrelid JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace JOIN pg_catalog.pg_class fcl ON fcl.oid = c.confrelid JOIN pg_catalog.pg_namespace fn ON fn.oid = fcl.relnamespace JOIN unnest(c.conkey) WITH ORDINALITY AS lk(attnum, ord) ON true JOIN unnest(c.confkey) WITH ORDINALITY AS rk(attnum, ord) ON lk.ord = rk.ord JOIN pg_catalog.pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = lk.attnum JOIN pg_catalog.pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = rk.attnum WHERE c.contype = 'f' AND n.nspname = ${namespace} ORDER BY cl.relname, c.conname, lk.ord`;
|
|
132
147
|
break;
|
|
133
148
|
case "enums":
|
|
134
|
-
text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} ORDER BY t.typname, e.enumsortorder`;
|
|
149
|
+
text = `SELECT t.typname AS type_name, n.nspname AS type_namespace, e.enumlabel AS enum_value, e.enumsortorder AS sort_order FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${namespace} AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_type'::regclass AND d.objid = t.oid AND d.deptype = 'e') ORDER BY t.typname, e.enumsortorder`;
|
|
135
150
|
break;
|
|
136
151
|
case "indexes":
|
|
137
152
|
text = `SELECT tablename AS table_name, indexname AS index_name, indexdef AS index_def, con.conname AS backing_constraint, con.contype AS backing_constraint_type, health.indisvalid AS valid, health.indisready AS ready, health.indimmediate AS immediate, health.indexprs IS NULL AS plain_columns, health.indnkeyatts AS key_count, health.indnatts AS attribute_count FROM pg_indexes ix JOIN pg_catalog.pg_namespace ns ON ns.nspname = ix.schemaname JOIN pg_catalog.pg_class idx ON idx.relnamespace = ns.oid AND idx.relname = ix.indexname JOIN pg_catalog.pg_index health ON health.indexrelid = idx.oid LEFT JOIN pg_catalog.pg_constraint con ON con.conindid = idx.oid AND con.contype IN ('p', 'u', 'x') WHERE schemaname = ${namespace} ORDER BY tablename, indexname`;
|
|
@@ -146,7 +161,7 @@ function renderPostgresIndexKeys(params) {
|
|
|
146
161
|
if (dialect.name !== "postgres")
|
|
147
162
|
throw unsupportedCapability5({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
148
163
|
const collector = new ParamCollector(dialect);
|
|
149
|
-
const scope = params.table === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : `n.nspname
|
|
164
|
+
const scope = params.table !== undefined ? `n.nspname = ${collector.add(params.table.namespace)} AND c.relname = ${collector.add(params.table.name)}` : params.namespaces === undefined ? "n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg\\_%' ESCAPE '\\'" : params.namespaces.length === 0 ? "false" : `n.nspname IN (${[...new Set(params.namespaces)].map((namespace) => collector.add(namespace)).join(", ")})`;
|
|
150
165
|
const defaultResolution = `LEFT JOIN LATERAL (WITH RECURSIVE input_types AS (SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t WHERE t.oid = a.atttypid UNION ALL SELECT t.oid, t.typbasetype, t.typtype, t.typelem, t.typcategory FROM pg_catalog.pg_type t JOIN input_types previous ON t.oid = previous.typbasetype WHERE previous.typtype = 'd'), candidates AS (SELECT candidate.oid, CASE WHEN candidate.opcintype = input.oid THEN 0 WHEN class_input.typispreferred AND class_input.typcategory = input.typcategory THEN 1 ELSE 2 END AS priority FROM pg_catalog.pg_opclass candidate JOIN pg_catalog.pg_type class_input ON class_input.oid = candidate.opcintype CROSS JOIN input_types input WHERE input.typtype <> 'd' AND candidate.opcdefault AND candidate.opcmethod = idx.relam AND (candidate.opcintype = input.oid OR EXISTS (SELECT 1 FROM pg_catalog.pg_cast cast_entry WHERE cast_entry.castsource = input.oid AND cast_entry.casttarget = candidate.opcintype AND cast_entry.castmethod = 'b' AND cast_entry.castcontext = 'i') OR input.typtype = 'e' AND candidate.opcintype = 'pg_catalog.anyenum'::regtype OR input.typelem <> 0 AND candidate.opcintype = 'pg_catalog.anyarray'::regtype)) SELECT (array_agg(candidate.oid))[1] AS oid FROM candidates candidate WHERE candidate.priority = (SELECT min(priority) FROM candidates) HAVING count(*) = 1) resolved_default ON true`;
|
|
151
166
|
return {
|
|
152
167
|
text: `SELECT n.nspname AS table_namespace, c.relname AS table_name, idx.relname AS index_name, am.amname AS method, i.indisunique AS unique, pg_catalog.pg_get_expr(i.indpred, i.indrelid, false) AS predicate, i.indnullsnotdistinct AS nulls_not_distinct, k.ordinality AS ordinal, a.attname AS column_name, (i.indoption[k.ordinality - 1] & 1) <> 0 AS descending, opn.nspname AS operator_namespace, op.opcname AS operator_name, COALESCE(op.oid = resolved_default.oid, false) AS exact_default FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON c.oid = i.indrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace JOIN pg_catalog.pg_class idx ON idx.oid = i.indexrelid JOIN pg_catalog.pg_am am ON am.oid = idx.relam CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, ordinality) LEFT JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = k.attnum JOIN pg_catalog.pg_opclass op ON op.oid = i.indclass[k.ordinality - 1] JOIN pg_catalog.pg_namespace opn ON opn.oid = op.opcnamespace ${defaultResolution} WHERE ${scope} AND k.ordinality <= i.indnkeyatts ORDER BY n.nspname, c.relname, idx.relname, k.ordinality`,
|
|
@@ -334,6 +349,209 @@ function renderModuleJournal(params) {
|
|
|
334
349
|
}
|
|
335
350
|
return { text, values: collector.values };
|
|
336
351
|
}
|
|
352
|
+
// packages/sql/src/capabilities.ts
|
|
353
|
+
import {
|
|
354
|
+
DIALECT_CAPABILITIES,
|
|
355
|
+
POSTGRES_CAPABILITIES,
|
|
356
|
+
SQLITE_CAPABILITIES,
|
|
357
|
+
MYSQL_CAPABILITIES
|
|
358
|
+
} from "@vibeorm/schema";
|
|
359
|
+
|
|
360
|
+
// packages/sql/src/identifiers.ts
|
|
361
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
362
|
+
function assertNoNul(params) {
|
|
363
|
+
if (params.name.includes("\x00")) {
|
|
364
|
+
throw new VibeError2({
|
|
365
|
+
code: "VIBE_VALIDATION",
|
|
366
|
+
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
367
|
+
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return params.name;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// packages/sql/src/dialects/sqlite.ts
|
|
374
|
+
var sqliteDialect = {
|
|
375
|
+
name: "sqlite",
|
|
376
|
+
capabilities: SQLITE_CAPABILITIES,
|
|
377
|
+
inArrayStrategy: "expand",
|
|
378
|
+
tupleInStrategy: "rowValue",
|
|
379
|
+
timeOfDayInStrategy: "in",
|
|
380
|
+
upsertForm: "onConflict",
|
|
381
|
+
conflictKeyEquality: {
|
|
382
|
+
foldingScalars: [],
|
|
383
|
+
textIsBinaryByDefault: true,
|
|
384
|
+
duplicatesInOneStatement: "lastWriteWins"
|
|
385
|
+
},
|
|
386
|
+
excludedRowRef: { form: "keyword", name: "excluded" },
|
|
387
|
+
doNothingForm: "onConflictDoNothing",
|
|
388
|
+
insensitiveLike: "lower",
|
|
389
|
+
strictMatchStrategy: "glob",
|
|
390
|
+
greatestForm: "frame",
|
|
391
|
+
orderByNullsMode: "native",
|
|
392
|
+
offsetRequiresLimit: { limitText: "-1" },
|
|
393
|
+
likeEscapeClause: " ESCAPE '\\'",
|
|
394
|
+
jsonPathMode: "sqliteExtract",
|
|
395
|
+
supportedJsonOps: [
|
|
396
|
+
"equals",
|
|
397
|
+
"string_contains",
|
|
398
|
+
"string_starts_with",
|
|
399
|
+
"string_ends_with",
|
|
400
|
+
"array_contains",
|
|
401
|
+
"array_starts_with",
|
|
402
|
+
"array_ends_with"
|
|
403
|
+
],
|
|
404
|
+
jsonParamCast: "",
|
|
405
|
+
lastInsertIdSql: null,
|
|
406
|
+
rowLockSyntax: null,
|
|
407
|
+
advisoryLockFunctions: null,
|
|
408
|
+
rowDefaultKeyword: false,
|
|
409
|
+
statementChain: null,
|
|
410
|
+
paramBudget: 900,
|
|
411
|
+
booleanLiteral: { true: "1", false: "0" },
|
|
412
|
+
quoteIdent({ name }) {
|
|
413
|
+
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
414
|
+
},
|
|
415
|
+
quoteStringLiteral({ value }) {
|
|
416
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
417
|
+
},
|
|
418
|
+
placeholder() {
|
|
419
|
+
return "?";
|
|
420
|
+
},
|
|
421
|
+
renderDbNow({ offsetSeconds, collector }) {
|
|
422
|
+
if (offsetSeconds === 0)
|
|
423
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
424
|
+
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
429
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
430
|
+
function tableDefinitionParts(params) {
|
|
431
|
+
const sql = params.sql;
|
|
432
|
+
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
433
|
+
throw unavailable();
|
|
434
|
+
const parts = [];
|
|
435
|
+
let start = -1;
|
|
436
|
+
let depth = 0;
|
|
437
|
+
let quote;
|
|
438
|
+
for (let index = 0;index < sql.length; index++) {
|
|
439
|
+
const char = sql[index];
|
|
440
|
+
if (quote !== undefined) {
|
|
441
|
+
if (char === quote) {
|
|
442
|
+
if (quote !== "]" && sql[index + 1] === quote)
|
|
443
|
+
index++;
|
|
444
|
+
else
|
|
445
|
+
quote = undefined;
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
450
|
+
quote = char;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (char === "[") {
|
|
454
|
+
quote = "]";
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
458
|
+
const end = sql.indexOf(`
|
|
459
|
+
`, index + 2);
|
|
460
|
+
if (end < 0)
|
|
461
|
+
break;
|
|
462
|
+
index = end;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
466
|
+
const end = sql.indexOf("*/", index + 2);
|
|
467
|
+
if (end < 0)
|
|
468
|
+
break;
|
|
469
|
+
index = end + 1;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (char === "(") {
|
|
473
|
+
if (depth === 0)
|
|
474
|
+
start = index + 1;
|
|
475
|
+
depth++;
|
|
476
|
+
} else if (char === ")") {
|
|
477
|
+
depth--;
|
|
478
|
+
if (depth === 0 && start >= 0) {
|
|
479
|
+
parts.push(sql.slice(start, index).trim());
|
|
480
|
+
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
481
|
+
}
|
|
482
|
+
} else if (char === "," && depth === 1) {
|
|
483
|
+
parts.push(sql.slice(start, index).trim());
|
|
484
|
+
start = index + 1;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
throw unavailable();
|
|
488
|
+
}
|
|
489
|
+
function unavailable() {
|
|
490
|
+
return new VibeError3({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
491
|
+
}
|
|
492
|
+
function sqliteColumnClauses(params) {
|
|
493
|
+
const columns = [];
|
|
494
|
+
for (const part of tableDefinitionParts(params).clauses) {
|
|
495
|
+
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
496
|
+
if (token === null)
|
|
497
|
+
throw unavailable();
|
|
498
|
+
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
499
|
+
continue;
|
|
500
|
+
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
501
|
+
columns.push({ name, sql: part });
|
|
502
|
+
}
|
|
503
|
+
return columns;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// packages/sql/src/dialects/sqlite-fts.ts
|
|
507
|
+
function updateStatements(params) {
|
|
508
|
+
const quote = (name) => sqliteDialect.quoteIdent({ name });
|
|
509
|
+
const columns = params.columns.map((column) => quote(column)).join(", ");
|
|
510
|
+
const oldValues = params.columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
511
|
+
const newValues = params.columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
512
|
+
const changes = [
|
|
513
|
+
"old.rowid IS NOT new.rowid",
|
|
514
|
+
...params.columns.map((column) => `old.${quote(column)} COLLATE BINARY IS NOT new.${quote(column)} COLLATE BINARY`)
|
|
515
|
+
].join(" OR ");
|
|
516
|
+
const target = `${quote(`${params.ftsTable}_au`)} AFTER UPDATE ON ${quote(params.table)}`;
|
|
517
|
+
const body = `BEGIN
|
|
518
|
+
INSERT INTO ${quote(params.ftsTable)}(${quote(params.ftsTable)}, rowid, ${columns}) VALUES ('delete', old.rowid, ${oldValues});
|
|
519
|
+
INSERT INTO ${quote(params.ftsTable)}(rowid, ${columns}) VALUES (new.rowid, ${newValues});
|
|
520
|
+
END`;
|
|
521
|
+
const tail = `${target} WHEN (${changes}) ${body}`;
|
|
522
|
+
return {
|
|
523
|
+
create: `CREATE TRIGGER IF NOT EXISTS ${tail};`,
|
|
524
|
+
definition: `CREATE TRIGGER ${tail}`,
|
|
525
|
+
legacyDefinition: `CREATE TRIGGER ${target} ${body}`
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function renderSqliteFtsUpdateTrigger(params) {
|
|
529
|
+
const { create, definition } = updateStatements(params);
|
|
530
|
+
return { create, definition };
|
|
531
|
+
}
|
|
532
|
+
function sqliteFtsUpdateUpgrade(params) {
|
|
533
|
+
const quoted = '"(?:[^"]|"")+"';
|
|
534
|
+
const body = new RegExp(`BEGIN\\n INSERT INTO (${quoted})\\((${quoted}), rowid, (${quoted}(?:, ${quoted})*)\\) VALUES \\('delete', old\\.rowid, `, "g");
|
|
535
|
+
for (const match of params.create.matchAll(body)) {
|
|
536
|
+
const ftsTable = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[1]})` })[0].name;
|
|
537
|
+
const columns = sqliteColumnClauses({ sql: `CREATE TABLE x (${match[3]})` }).map((column) => column.name);
|
|
538
|
+
if (params.name !== `${ftsTable}_au`)
|
|
539
|
+
continue;
|
|
540
|
+
const statements = updateStatements({ table: params.table, ftsTable, columns });
|
|
541
|
+
if (params.create !== statements.create)
|
|
542
|
+
continue;
|
|
543
|
+
const installed = params.installed === statements.legacyDefinition ? "legacy" : params.installed === statements.definition ? "current" : undefined;
|
|
544
|
+
if (installed === undefined)
|
|
545
|
+
continue;
|
|
546
|
+
if (params.tableDefinition === undefined)
|
|
547
|
+
return { kind: "tableRequired", ftsTable };
|
|
548
|
+
const tableTail = `${sqliteDialect.quoteIdent({ name: ftsTable })} USING fts5(${columns.map((name) => sqliteDialect.quoteIdent({ name })).join(", ")}, content=${sqliteDialect.quoteStringLiteral({ value: params.table })})`;
|
|
549
|
+
if (params.tableDefinition !== `CREATE VIRTUAL TABLE ${tableTail}` && params.tableDefinition !== `CREATE VIRTUAL TABLE IF NOT EXISTS ${tableTail};`)
|
|
550
|
+
continue;
|
|
551
|
+
return { kind: "recognized", ftsTable, legacyDefinition: statements.legacyDefinition, currentDefinition: statements.definition, drop: `DROP TRIGGER IF EXISTS ${sqliteDialect.quoteIdent({ name: `${ftsTable}_au` })};`, installed };
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
337
555
|
// packages/sql/src/dialects/sqlite-session.ts
|
|
338
556
|
var SQLITE_SESSION_SQL = Object.freeze({
|
|
339
557
|
lockingMode: "PRAGMA main.locking_mode",
|
|
@@ -346,10 +564,10 @@ var SQLITE_SESSION_SQL = Object.freeze({
|
|
|
346
564
|
foreignKeysOn: "PRAGMA foreign_keys = ON"
|
|
347
565
|
});
|
|
348
566
|
// packages/sql/src/composable.ts
|
|
349
|
-
import { VibeError as
|
|
567
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
350
568
|
var fragments = new WeakMap;
|
|
351
569
|
function invalid(message) {
|
|
352
|
-
throw new
|
|
570
|
+
throw new VibeError4({ code: "VIBE_VALIDATION", message });
|
|
353
571
|
}
|
|
354
572
|
function construct(parts) {
|
|
355
573
|
const snapshot = Object.freeze([...parts]);
|
|
@@ -365,6 +583,9 @@ function isSqlFragment(value) {
|
|
|
365
583
|
function partsOf(value) {
|
|
366
584
|
return fragments.get(value) ?? invalid("Expected a constructed SQL fragment");
|
|
367
585
|
}
|
|
586
|
+
function containsGreatest(fragment) {
|
|
587
|
+
return partsOf(fragment).some((part) => typeof part !== "string" && ("greatest" in part));
|
|
588
|
+
}
|
|
368
589
|
function identifier(parts) {
|
|
369
590
|
if (!Array.isArray(parts) || parts.length === 0 || parts.some((part) => typeof part !== "string" || part.length === 0 || part.includes("\x00"))) {
|
|
370
591
|
invalid("SQL identifiers require non-empty, NUL-free name components");
|
|
@@ -427,14 +648,25 @@ var sql = Object.freeze(Object.assign(template, {
|
|
|
427
648
|
function renderSqlFragment(params) {
|
|
428
649
|
const { dialect, collector, conflict } = params;
|
|
429
650
|
const render = (fragment) => renderSqlFragment({ ...params, fragment });
|
|
651
|
+
const encode = (part) => {
|
|
652
|
+
if (part.value === undefined)
|
|
653
|
+
invalid("SQL parameters cannot be undefined; use null explicitly");
|
|
654
|
+
return part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name });
|
|
655
|
+
};
|
|
656
|
+
const frameOperand = (fragment) => {
|
|
657
|
+
const parts = partsOf(fragment).filter((part) => part !== "");
|
|
658
|
+
const only = parts.length === 1 ? parts[0] : undefined;
|
|
659
|
+
const value = only !== undefined && typeof only !== "string" && "value" in only ? { encoded: encode(only) } : undefined;
|
|
660
|
+
if (value !== undefined && value.encoded === null)
|
|
661
|
+
return "NULL";
|
|
662
|
+
const text = value === undefined ? render(fragment) : collector.add(value.encoded);
|
|
663
|
+
return dialect.greatestForm === "wrapped-frame" ? `(SELECT ${text})` : text;
|
|
664
|
+
};
|
|
430
665
|
return partsOf(params.fragment).map((part) => {
|
|
431
666
|
if (typeof part === "string")
|
|
432
667
|
return part;
|
|
433
|
-
if ("value" in part)
|
|
434
|
-
|
|
435
|
-
invalid("SQL parameters cannot be undefined; use null explicitly");
|
|
436
|
-
return collector.add(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
|
|
437
|
-
}
|
|
668
|
+
if ("value" in part)
|
|
669
|
+
return collector.add(encode(part));
|
|
438
670
|
if ("ident" in part)
|
|
439
671
|
return part.ident.map((name) => dialect.quoteIdent({ name })).join(".");
|
|
440
672
|
if ("row" in part) {
|
|
@@ -447,7 +679,14 @@ function renderSqlFragment(params) {
|
|
|
447
679
|
const qualifier = part.row === "existing" ? dialect.quoteIdent({ name: conflict.table }) : ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
|
|
448
680
|
return `${qualifier}.${dialect.quoteIdent({ name })}`;
|
|
449
681
|
}
|
|
450
|
-
|
|
682
|
+
if (dialect.greatestForm === "native")
|
|
683
|
+
return `GREATEST(${part.greatest.map(render).join(", ")})`;
|
|
684
|
+
if (!part.greatest.some(containsGreatest)) {
|
|
685
|
+
return render(part.greatest.reduce((left, right) => sql`(CASE WHEN ${left} IS NULL THEN ${right} WHEN ${right} IS NULL THEN ${left} WHEN ${left} >= ${right} THEN ${left} ELSE ${right} END)`));
|
|
686
|
+
}
|
|
687
|
+
const [left, right] = part.greatest.map(frameOperand);
|
|
688
|
+
const [a, b, frame] = ["a", "b", "g"].map((name) => dialect.quoteIdent({ name }));
|
|
689
|
+
return `(SELECT CASE WHEN ${a} IS NULL THEN ${b} WHEN ${b} IS NULL THEN ${a} WHEN ${a} >= ${b} THEN ${a} ELSE ${b} END FROM (SELECT ${left} AS ${a}, ${right} AS ${b}) AS ${frame})`;
|
|
451
690
|
}).join("");
|
|
452
691
|
}
|
|
453
692
|
function mapConflictColumns(params) {
|
|
@@ -474,7 +713,7 @@ function sqlUsesIncoming(params) {
|
|
|
474
713
|
return partsOf(params.fragment).some((part) => typeof part !== "string" && (("row" in part) && part.row === "incoming" || ("greatest" in part) && part.greatest.some((fragment) => sqlUsesIncoming({ fragment }))));
|
|
475
714
|
}
|
|
476
715
|
// packages/sql/src/result-decoder.ts
|
|
477
|
-
import { VibeError as
|
|
716
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
478
717
|
|
|
479
718
|
// packages/sql/src/pg-array.ts
|
|
480
719
|
var PG_ARRAY_QUOTE = /^$|^NULL$|[{},"\\\s]/i;
|
|
@@ -489,7 +728,7 @@ function toPgArrayLiteral(params) {
|
|
|
489
728
|
return `{${parts.join(",")}}`;
|
|
490
729
|
}
|
|
491
730
|
function parsePgArrayLiteral(params) {
|
|
492
|
-
const
|
|
731
|
+
const literal = params.literal.startsWith("[") ? params.literal.replace(/^\[-?\d+:-?\d+\]=/, "") : params.literal;
|
|
493
732
|
if (literal.length < 2 || !literal.startsWith("{") || !literal.endsWith("}"))
|
|
494
733
|
return null;
|
|
495
734
|
const inner = literal.slice(1, -1);
|
|
@@ -527,9 +766,63 @@ function parsePgArrayLiteral(params) {
|
|
|
527
766
|
}
|
|
528
767
|
return out;
|
|
529
768
|
}
|
|
769
|
+
function parsePgArrayLiteralNested(params) {
|
|
770
|
+
const text = params.literal.replace(/^(?:\[-?\d+:-?\d+\])+=/, "");
|
|
771
|
+
const cursor = { index: 0 };
|
|
772
|
+
const parsed = parseDimension({ text, cursor });
|
|
773
|
+
return parsed !== null && cursor.index === text.length ? parsed : null;
|
|
774
|
+
}
|
|
775
|
+
function parseDimension(params) {
|
|
776
|
+
const { text, cursor } = params;
|
|
777
|
+
if (text[cursor.index] !== "{")
|
|
778
|
+
return null;
|
|
779
|
+
cursor.index++;
|
|
780
|
+
const out = [];
|
|
781
|
+
if (text[cursor.index] === "}") {
|
|
782
|
+
cursor.index++;
|
|
783
|
+
return out;
|
|
784
|
+
}
|
|
785
|
+
while (cursor.index < text.length) {
|
|
786
|
+
if (text[cursor.index] === "{") {
|
|
787
|
+
const nested = parseDimension({ text, cursor });
|
|
788
|
+
if (nested === null)
|
|
789
|
+
return null;
|
|
790
|
+
out.push(nested);
|
|
791
|
+
} else if (text[cursor.index] === '"') {
|
|
792
|
+
let element = "";
|
|
793
|
+
cursor.index++;
|
|
794
|
+
while (cursor.index < text.length && text[cursor.index] !== '"') {
|
|
795
|
+
if (text[cursor.index] === "\\")
|
|
796
|
+
cursor.index++;
|
|
797
|
+
element += text[cursor.index] ?? "";
|
|
798
|
+
cursor.index++;
|
|
799
|
+
}
|
|
800
|
+
if (text[cursor.index] !== '"')
|
|
801
|
+
return null;
|
|
802
|
+
cursor.index++;
|
|
803
|
+
out.push(element);
|
|
804
|
+
} else {
|
|
805
|
+
let element = "";
|
|
806
|
+
while (cursor.index < text.length && text[cursor.index] !== "," && text[cursor.index] !== "}") {
|
|
807
|
+
element += text[cursor.index];
|
|
808
|
+
cursor.index++;
|
|
809
|
+
}
|
|
810
|
+
out.push(element === "NULL" ? null : element);
|
|
811
|
+
}
|
|
812
|
+
if (text[cursor.index] === ",") {
|
|
813
|
+
cursor.index++;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (text[cursor.index] !== "}")
|
|
817
|
+
return null;
|
|
818
|
+
cursor.index++;
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
530
823
|
|
|
531
824
|
// packages/sql/src/codecs.ts
|
|
532
|
-
import { VibeError as
|
|
825
|
+
import { VibeError as VibeError5, invalidStoredData, timeOfDayType } from "@vibeorm/schema";
|
|
533
826
|
function enumMembershipCodec(params) {
|
|
534
827
|
const { model, field, enum: enumName } = params;
|
|
535
828
|
const members = new Set(params.members);
|
|
@@ -537,7 +830,7 @@ function enumMembershipCodec(params) {
|
|
|
537
830
|
encode: (value) => {
|
|
538
831
|
if (typeof value === "string" && members.has(value))
|
|
539
832
|
return value;
|
|
540
|
-
throw new
|
|
833
|
+
throw new VibeError5({
|
|
541
834
|
code: "VIBE_VALIDATION",
|
|
542
835
|
message: `Invalid value for enum ${enumName} on ${model}.${field}`,
|
|
543
836
|
meta: { model, field, enum: enumName }
|
|
@@ -577,7 +870,7 @@ var bigIntCodec = {
|
|
|
577
870
|
return BigInt(value.trim());
|
|
578
871
|
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
579
872
|
return BigInt(value);
|
|
580
|
-
throw new
|
|
873
|
+
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "BigInt requires an integer string or a safe integer", meta: { value } });
|
|
581
874
|
}
|
|
582
875
|
};
|
|
583
876
|
var decimalCodec = {
|
|
@@ -594,6 +887,7 @@ var mysqlDecimalCodec = {
|
|
|
594
887
|
}
|
|
595
888
|
};
|
|
596
889
|
var ZONE_LESS_DATETIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
890
|
+
var PG_ZONED_DATETIME = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)(\.\d{1,9})?([+-]\d{2})(?::?(\d{2}))?$/;
|
|
597
891
|
function parseDateTimeText(params) {
|
|
598
892
|
const trimmed = params.text.trim();
|
|
599
893
|
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
@@ -601,13 +895,20 @@ function parseDateTimeText(params) {
|
|
|
601
895
|
if (!Number.isNaN(asUtc.getTime()))
|
|
602
896
|
return asUtc;
|
|
603
897
|
}
|
|
898
|
+
const zoned = PG_ZONED_DATETIME.exec(trimmed);
|
|
899
|
+
if (zoned !== null) {
|
|
900
|
+
const [, date, time, fraction, hours, minutes] = zoned;
|
|
901
|
+
const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
|
|
902
|
+
if (!Number.isNaN(iso.getTime()))
|
|
903
|
+
return iso;
|
|
904
|
+
}
|
|
604
905
|
const parsed = new Date(trimmed);
|
|
605
906
|
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
606
907
|
}
|
|
607
908
|
var dateNativeCodec = {
|
|
608
909
|
encode: (value) => {
|
|
609
910
|
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
610
|
-
throw new
|
|
911
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
611
912
|
}
|
|
612
913
|
return value;
|
|
613
914
|
},
|
|
@@ -615,7 +916,7 @@ var dateNativeCodec = {
|
|
|
615
916
|
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
616
917
|
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
617
918
|
return date;
|
|
618
|
-
throw new
|
|
919
|
+
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "DateTime requires a finite timestamp; use an explicit extended timestamp decoder for infinity" });
|
|
619
920
|
}
|
|
620
921
|
};
|
|
621
922
|
var dateTextCodec = {
|
|
@@ -624,7 +925,7 @@ var dateTextCodec = {
|
|
|
624
925
|
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
625
926
|
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
626
927
|
return date.toISOString();
|
|
627
|
-
throw new
|
|
928
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
628
929
|
},
|
|
629
930
|
decode: dateNativeCodec.decode
|
|
630
931
|
};
|
|
@@ -645,6 +946,44 @@ var mysqlDateCodec = {
|
|
|
645
946
|
},
|
|
646
947
|
decode: dateNativeCodec.decode
|
|
647
948
|
};
|
|
949
|
+
var MILLISECONDS_PER_DAY = 86400000;
|
|
950
|
+
var TIME_OF_DAY_TEXT = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(?:([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/;
|
|
951
|
+
var pad = (value, width) => String(value).padStart(width, "0");
|
|
952
|
+
var utcMillisecondOfDay = (date) => (date.getTime() % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY;
|
|
953
|
+
var timeOfDayText = (millisecond) => `${pad(Math.floor(millisecond / 3600000), 2)}:${pad(Math.floor(millisecond / 60000) % 60, 2)}:${pad(Math.floor(millisecond / 1000) % 60, 2)}.${pad(millisecond % 1000, 3)}`;
|
|
954
|
+
function makeTimeOfDayCodec(params) {
|
|
955
|
+
const suffix = params.zoned ? "+00" : "";
|
|
956
|
+
return Object.freeze({
|
|
957
|
+
encode: (value) => {
|
|
958
|
+
dateNativeCodec.encode(value);
|
|
959
|
+
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
960
|
+
if (date === undefined || !Number.isFinite(date.getTime())) {
|
|
961
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
|
|
962
|
+
}
|
|
963
|
+
return `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
964
|
+
},
|
|
965
|
+
decode: (value) => {
|
|
966
|
+
if (value instanceof Date && Number.isFinite(value.getTime()))
|
|
967
|
+
return new Date(utcMillisecondOfDay(value));
|
|
968
|
+
const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
|
|
969
|
+
if (match !== null) {
|
|
970
|
+
const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
|
|
971
|
+
const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
|
|
972
|
+
const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
|
|
973
|
+
if (millisecond <= MILLISECONDS_PER_DAY) {
|
|
974
|
+
return new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
throw new VibeError5({ code: "VIBE_DATA_INVALID", message: "a time-of-day DateTime column returned a value that is not a time of day", meta: { value } });
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
var timeCodec = makeTimeOfDayCodec({ zoned: false });
|
|
982
|
+
var timetzCodec = makeTimeOfDayCodec({ zoned: true });
|
|
983
|
+
function timeOfDayCodec(params) {
|
|
984
|
+
const type = timeOfDayType({ nativeType: params.nativeType });
|
|
985
|
+
return type === "timetz" ? timetzCodec : type === "time" ? timeCodec : undefined;
|
|
986
|
+
}
|
|
648
987
|
var booleanIntCodec = {
|
|
649
988
|
encode: (value) => typeof value === "boolean" ? value ? 1 : 0 : value,
|
|
650
989
|
decode: (value) => typeof value === "number" ? value !== 0 : value
|
|
@@ -661,7 +1000,7 @@ var jsonCodec = {
|
|
|
661
1000
|
try {
|
|
662
1001
|
return JSON.parse(value);
|
|
663
1002
|
} catch (cause) {
|
|
664
|
-
throw new
|
|
1003
|
+
throw new VibeError5({
|
|
665
1004
|
code: "VIBE_INTERNAL",
|
|
666
1005
|
message: "a Json column returned text that is not valid JSON",
|
|
667
1006
|
meta: { value },
|
|
@@ -670,6 +1009,39 @@ var jsonCodec = {
|
|
|
670
1009
|
}
|
|
671
1010
|
}
|
|
672
1011
|
};
|
|
1012
|
+
function shortestFloat32(params) {
|
|
1013
|
+
const { value } = params;
|
|
1014
|
+
if (value === 0 || !Number.isFinite(value))
|
|
1015
|
+
return value;
|
|
1016
|
+
const target = Math.fround(value);
|
|
1017
|
+
for (let precision = 1;precision <= 9; precision++) {
|
|
1018
|
+
const candidate = Number(value.toPrecision(precision));
|
|
1019
|
+
if (Math.fround(candidate) === target)
|
|
1020
|
+
return candidate;
|
|
1021
|
+
}
|
|
1022
|
+
return value;
|
|
1023
|
+
}
|
|
1024
|
+
var float32Codec = {
|
|
1025
|
+
encode: identity,
|
|
1026
|
+
decode: (value) => {
|
|
1027
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
1028
|
+
return typeof number === "number" ? shortestFloat32({ value: number }) : number;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
var booleanFromText = (text) => text === "t" ? true : text === "f" ? false : text;
|
|
1032
|
+
var bytesFromText = (text) => /^\\x(?:[0-9a-fA-F]{2})*$/.test(text) ? new Uint8Array(Buffer.from(text.slice(2), "hex")) : text;
|
|
1033
|
+
function postgresArrayElementFromText(params) {
|
|
1034
|
+
switch (params.scalarType) {
|
|
1035
|
+
case "Boolean":
|
|
1036
|
+
return booleanFromText;
|
|
1037
|
+
case "Bytes":
|
|
1038
|
+
return bytesFromText;
|
|
1039
|
+
case "Json":
|
|
1040
|
+
return jsonCodec.decode;
|
|
1041
|
+
default:
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
673
1045
|
var POSTGRES_CODECS = {
|
|
674
1046
|
String: passthroughCodec,
|
|
675
1047
|
Boolean: passthroughCodec,
|
|
@@ -711,7 +1083,7 @@ var DIALECT_CODECS = {
|
|
|
711
1083
|
|
|
712
1084
|
// packages/sql/src/result-decoder.ts
|
|
713
1085
|
function refuse(encoding, message) {
|
|
714
|
-
throw new
|
|
1086
|
+
throw new VibeError6({ code: encoding ? "VIBE_VALIDATION" : "VIBE_DATA_INVALID", message });
|
|
715
1087
|
}
|
|
716
1088
|
function scalar(type, valid) {
|
|
717
1089
|
const convert = (params, encoding) => {
|
|
@@ -757,7 +1129,7 @@ var extendedDecoder = Object.freeze({
|
|
|
757
1129
|
encode: (params) => {
|
|
758
1130
|
if (params.value === "infinity" || params.value === "-infinity") {
|
|
759
1131
|
if (params.dialect !== "postgres")
|
|
760
|
-
throw new
|
|
1132
|
+
throw new VibeError6({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Timestamp infinity parameters require PostgreSQL" });
|
|
761
1133
|
return params.value;
|
|
762
1134
|
}
|
|
763
1135
|
return dateDecoder.encode(params);
|
|
@@ -815,10 +1187,10 @@ var decode = Object.freeze({
|
|
|
815
1187
|
}
|
|
816
1188
|
});
|
|
817
1189
|
// packages/sql/src/exclusion.ts
|
|
818
|
-
import { VibeError as
|
|
1190
|
+
import { VibeError as VibeError8, fieldTypeIdentity as fieldTypeIdentity2, isEnumType as isEnumType2, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
819
1191
|
|
|
820
1192
|
// packages/sql/src/predicate.ts
|
|
821
|
-
import { VibeError as
|
|
1193
|
+
import { VibeError as VibeError7, fieldTypeIdentity, isEnumType, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
822
1194
|
function renderPredicate(params) {
|
|
823
1195
|
if (params.usage === "exclusionPredicate" && !params.dialect.capabilities.exclusionConstraints)
|
|
824
1196
|
throw unsupportedCapability8({ dialect: params.dialect.name, feature: "exclusionConstraints" });
|
|
@@ -858,7 +1230,7 @@ var INT64_MIN = -(2n ** 63n);
|
|
|
858
1230
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
859
1231
|
function refuse2(params) {
|
|
860
1232
|
const { context, rule, message } = params;
|
|
861
|
-
throw new
|
|
1233
|
+
throw new VibeError7({
|
|
862
1234
|
code: "VIBE_SCHEMA_INVALID",
|
|
863
1235
|
message: `${context.site} on "${context.model.name}": ${message}`,
|
|
864
1236
|
meta: { rule, model: context.model.name, site: context.site, ...params.meta ?? {} }
|
|
@@ -1122,7 +1494,7 @@ function describe(params) {
|
|
|
1122
1494
|
|
|
1123
1495
|
// packages/sql/src/exclusion.ts
|
|
1124
1496
|
function invalid2(rule) {
|
|
1125
|
-
throw new
|
|
1497
|
+
throw new VibeError8({ code: "VIBE_SCHEMA_INVALID", message: `Invalid exclusion constraint: ${rule}`, meta: { rule: `exclusion-${rule}` } });
|
|
1126
1498
|
}
|
|
1127
1499
|
function compileExclusion(params) {
|
|
1128
1500
|
const { exclusion: spec, model, dialect } = params;
|
|
@@ -1245,7 +1617,7 @@ function isSqlDefaultCell(value) {
|
|
|
1245
1617
|
return value === SQL_DEFAULT_CELL;
|
|
1246
1618
|
}
|
|
1247
1619
|
function jsonParamCell(params) {
|
|
1248
|
-
return { __jsonParam: true, text: params.text };
|
|
1620
|
+
return params.storage === "json" ? { __jsonParam: true, text: params.text, storage: "json" } : { __jsonParam: true, text: params.text };
|
|
1249
1621
|
}
|
|
1250
1622
|
function isJsonParamCell(value) {
|
|
1251
1623
|
return typeof value === "object" && value !== null && value.__jsonParam === true;
|
|
@@ -1256,7 +1628,30 @@ function excludedRef(params) {
|
|
|
1256
1628
|
function isExcludedRef(value) {
|
|
1257
1629
|
return typeof value === "object" && value !== null && value.__excluded === true;
|
|
1258
1630
|
}
|
|
1631
|
+
function returningColumnName(column) {
|
|
1632
|
+
return typeof column === "string" ? column : column.name;
|
|
1633
|
+
}
|
|
1259
1634
|
var ADVISORY_LOCK_RESULT_COLUMN = "acquired";
|
|
1635
|
+
function stepColumnRef(params) {
|
|
1636
|
+
return { __stepColumn: true, step: params.step, column: params.column };
|
|
1637
|
+
}
|
|
1638
|
+
function isStepColumnRef(value) {
|
|
1639
|
+
return typeof value === "object" && value !== null && value.__stepColumn === true;
|
|
1640
|
+
}
|
|
1641
|
+
var CHAIN_VETO_PREFIX = "__vibe_veto:";
|
|
1642
|
+
var CHAIN_VETO_TOKEN_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
1643
|
+
function chainVetoToken(params) {
|
|
1644
|
+
const { message, nonce } = params;
|
|
1645
|
+
if (nonce === "")
|
|
1646
|
+
return;
|
|
1647
|
+
for (let start = message.indexOf(CHAIN_VETO_PREFIX);start >= 0; start = message.indexOf(CHAIN_VETO_PREFIX, start + 1)) {
|
|
1648
|
+
const rest = message.slice(start + CHAIN_VETO_PREFIX.length);
|
|
1649
|
+
const match = /^([a-z][a-z0-9_]*):/.exec(rest);
|
|
1650
|
+
if (match !== null && rest.startsWith(`${nonce}:`, match[0].length))
|
|
1651
|
+
return match[1];
|
|
1652
|
+
}
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1260
1655
|
// packages/sql/src/locks.ts
|
|
1261
1656
|
import { internalError, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
1262
1657
|
var ROW_LOCK_PROSE = Object.freeze({
|
|
@@ -1346,33 +1741,13 @@ function projectionOutputName(params) {
|
|
|
1346
1741
|
const { column } = params;
|
|
1347
1742
|
return column.transform === undefined ? column.alias ?? column.name : column.name;
|
|
1348
1743
|
}
|
|
1349
|
-
// packages/sql/src/capabilities.ts
|
|
1350
|
-
import {
|
|
1351
|
-
DIALECT_CAPABILITIES,
|
|
1352
|
-
POSTGRES_CAPABILITIES,
|
|
1353
|
-
SQLITE_CAPABILITIES,
|
|
1354
|
-
MYSQL_CAPABILITIES
|
|
1355
|
-
} from "@vibeorm/schema";
|
|
1356
|
-
|
|
1357
|
-
// packages/sql/src/identifiers.ts
|
|
1358
|
-
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
1359
|
-
function assertNoNul(params) {
|
|
1360
|
-
if (params.name.includes("\x00")) {
|
|
1361
|
-
throw new VibeError7({
|
|
1362
|
-
code: "VIBE_VALIDATION",
|
|
1363
|
-
message: "an SQL identifier cannot contain a NUL character \u2014 the server would stop reading it there and address a different object",
|
|
1364
|
-
meta: { name: params.name.replaceAll("\x00", "\\0") }
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
return params.name;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
1744
|
// packages/sql/src/dialects/postgres.ts
|
|
1371
1745
|
var postgresDialect = {
|
|
1372
1746
|
name: "postgres",
|
|
1373
1747
|
capabilities: POSTGRES_CAPABILITIES,
|
|
1374
1748
|
inArrayStrategy: "any",
|
|
1375
1749
|
tupleInStrategy: "rowValue",
|
|
1750
|
+
timeOfDayInStrategy: "in",
|
|
1376
1751
|
upsertForm: "onConflict",
|
|
1377
1752
|
conflictKeyEquality: {
|
|
1378
1753
|
foldingScalars: ["Decimal"],
|
|
@@ -1383,6 +1758,7 @@ var postgresDialect = {
|
|
|
1383
1758
|
doNothingForm: "onConflictDoNothing",
|
|
1384
1759
|
insensitiveLike: "ilike",
|
|
1385
1760
|
strictMatchStrategy: "native",
|
|
1761
|
+
greatestForm: "native",
|
|
1386
1762
|
orderByNullsMode: "native",
|
|
1387
1763
|
offsetRequiresLimit: null,
|
|
1388
1764
|
likeEscapeClause: null,
|
|
@@ -1419,6 +1795,7 @@ var postgresDialect = {
|
|
|
1419
1795
|
keyCast: Object.freeze({ single: "::bigint", pair: "::int4" })
|
|
1420
1796
|
},
|
|
1421
1797
|
rowDefaultKeyword: true,
|
|
1798
|
+
statementChain: "dataModifyingCte",
|
|
1422
1799
|
paramBudget: 30000,
|
|
1423
1800
|
booleanLiteral: { true: "true", false: "false" },
|
|
1424
1801
|
quoteIdent({ name }) {
|
|
@@ -1436,63 +1813,13 @@ var postgresDialect = {
|
|
|
1436
1813
|
return `(now() + make_interval(secs => ${collector.add(offsetSeconds)}))`;
|
|
1437
1814
|
}
|
|
1438
1815
|
};
|
|
1439
|
-
// packages/sql/src/dialects/sqlite.ts
|
|
1440
|
-
var sqliteDialect = {
|
|
1441
|
-
name: "sqlite",
|
|
1442
|
-
capabilities: SQLITE_CAPABILITIES,
|
|
1443
|
-
inArrayStrategy: "expand",
|
|
1444
|
-
tupleInStrategy: "rowValue",
|
|
1445
|
-
upsertForm: "onConflict",
|
|
1446
|
-
conflictKeyEquality: {
|
|
1447
|
-
foldingScalars: [],
|
|
1448
|
-
textIsBinaryByDefault: true,
|
|
1449
|
-
duplicatesInOneStatement: "lastWriteWins"
|
|
1450
|
-
},
|
|
1451
|
-
excludedRowRef: { form: "keyword", name: "excluded" },
|
|
1452
|
-
doNothingForm: "onConflictDoNothing",
|
|
1453
|
-
insensitiveLike: "lower",
|
|
1454
|
-
strictMatchStrategy: "glob",
|
|
1455
|
-
orderByNullsMode: "native",
|
|
1456
|
-
offsetRequiresLimit: { limitText: "-1" },
|
|
1457
|
-
likeEscapeClause: " ESCAPE '\\'",
|
|
1458
|
-
jsonPathMode: "sqliteExtract",
|
|
1459
|
-
supportedJsonOps: [
|
|
1460
|
-
"equals",
|
|
1461
|
-
"string_contains",
|
|
1462
|
-
"string_starts_with",
|
|
1463
|
-
"string_ends_with",
|
|
1464
|
-
"array_contains",
|
|
1465
|
-
"array_starts_with",
|
|
1466
|
-
"array_ends_with"
|
|
1467
|
-
],
|
|
1468
|
-
jsonParamCast: "",
|
|
1469
|
-
lastInsertIdSql: null,
|
|
1470
|
-
rowLockSyntax: null,
|
|
1471
|
-
advisoryLockFunctions: null,
|
|
1472
|
-
rowDefaultKeyword: false,
|
|
1473
|
-
paramBudget: 900,
|
|
1474
|
-
booleanLiteral: { true: "1", false: "0" },
|
|
1475
|
-
quoteIdent({ name }) {
|
|
1476
|
-
return `"${assertNoNul({ name }).replaceAll('"', '""')}"`;
|
|
1477
|
-
},
|
|
1478
|
-
quoteStringLiteral({ value }) {
|
|
1479
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
1480
|
-
},
|
|
1481
|
-
placeholder() {
|
|
1482
|
-
return "?";
|
|
1483
|
-
},
|
|
1484
|
-
renderDbNow({ offsetSeconds, collector }) {
|
|
1485
|
-
if (offsetSeconds === 0)
|
|
1486
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
1487
|
-
return `strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${collector.add(offsetSeconds)} || ' seconds')`;
|
|
1488
|
-
}
|
|
1489
|
-
};
|
|
1490
1816
|
// packages/sql/src/dialects/mysql.ts
|
|
1491
1817
|
var mysqlDialect = {
|
|
1492
1818
|
name: "mysql",
|
|
1493
1819
|
capabilities: MYSQL_CAPABILITIES,
|
|
1494
1820
|
inArrayStrategy: "expand",
|
|
1495
1821
|
tupleInStrategy: "rowValue",
|
|
1822
|
+
timeOfDayInStrategy: "equalities",
|
|
1496
1823
|
upsertForm: "onDuplicateKey",
|
|
1497
1824
|
conflictKeyEquality: {
|
|
1498
1825
|
foldingScalars: ["Decimal"],
|
|
@@ -1504,6 +1831,7 @@ var mysqlDialect = {
|
|
|
1504
1831
|
doNothingForm: "insertIgnore",
|
|
1505
1832
|
insensitiveLike: "lower",
|
|
1506
1833
|
strictMatchStrategy: "binary",
|
|
1834
|
+
greatestForm: "wrapped-frame",
|
|
1507
1835
|
orderByNullsMode: "emulate",
|
|
1508
1836
|
offsetRequiresLimit: { limitText: "18446744073709551615" },
|
|
1509
1837
|
likeEscapeClause: null,
|
|
@@ -1525,6 +1853,7 @@ var mysqlDialect = {
|
|
|
1525
1853
|
},
|
|
1526
1854
|
advisoryLockFunctions: null,
|
|
1527
1855
|
rowDefaultKeyword: true,
|
|
1856
|
+
statementChain: null,
|
|
1528
1857
|
paramBudget: 1e4,
|
|
1529
1858
|
booleanLiteral: { true: "1", false: "0" },
|
|
1530
1859
|
quoteIdent({ name }) {
|
|
@@ -1551,83 +1880,6 @@ var SQL_DIALECTS = {
|
|
|
1551
1880
|
function getSqlDialect(params) {
|
|
1552
1881
|
return SQL_DIALECTS[params.dialect];
|
|
1553
1882
|
}
|
|
1554
|
-
// packages/sql/src/dialects/sqlite-table-definition.ts
|
|
1555
|
-
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
1556
|
-
function tableDefinitionParts(params) {
|
|
1557
|
-
const sql = params.sql;
|
|
1558
|
-
if (!/^\s*CREATE\s+(?:TEMP(?:ORARY)?\s+)?TABLE\b/i.test(sql))
|
|
1559
|
-
throw unavailable();
|
|
1560
|
-
const parts = [];
|
|
1561
|
-
let start = -1;
|
|
1562
|
-
let depth = 0;
|
|
1563
|
-
let quote;
|
|
1564
|
-
for (let index = 0;index < sql.length; index++) {
|
|
1565
|
-
const char = sql[index];
|
|
1566
|
-
if (quote !== undefined) {
|
|
1567
|
-
if (char === quote) {
|
|
1568
|
-
if (quote !== "]" && sql[index + 1] === quote)
|
|
1569
|
-
index++;
|
|
1570
|
-
else
|
|
1571
|
-
quote = undefined;
|
|
1572
|
-
}
|
|
1573
|
-
continue;
|
|
1574
|
-
}
|
|
1575
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
1576
|
-
quote = char;
|
|
1577
|
-
continue;
|
|
1578
|
-
}
|
|
1579
|
-
if (char === "[") {
|
|
1580
|
-
quote = "]";
|
|
1581
|
-
continue;
|
|
1582
|
-
}
|
|
1583
|
-
if (char === "-" && sql[index + 1] === "-") {
|
|
1584
|
-
const end = sql.indexOf(`
|
|
1585
|
-
`, index + 2);
|
|
1586
|
-
if (end < 0)
|
|
1587
|
-
break;
|
|
1588
|
-
index = end;
|
|
1589
|
-
continue;
|
|
1590
|
-
}
|
|
1591
|
-
if (char === "/" && sql[index + 1] === "*") {
|
|
1592
|
-
const end = sql.indexOf("*/", index + 2);
|
|
1593
|
-
if (end < 0)
|
|
1594
|
-
break;
|
|
1595
|
-
index = end + 1;
|
|
1596
|
-
continue;
|
|
1597
|
-
}
|
|
1598
|
-
if (char === "(") {
|
|
1599
|
-
if (depth === 0)
|
|
1600
|
-
start = index + 1;
|
|
1601
|
-
depth++;
|
|
1602
|
-
} else if (char === ")") {
|
|
1603
|
-
depth--;
|
|
1604
|
-
if (depth === 0 && start >= 0) {
|
|
1605
|
-
parts.push(sql.slice(start, index).trim());
|
|
1606
|
-
return { clauses: parts, suffix: sql.slice(index + 1) };
|
|
1607
|
-
}
|
|
1608
|
-
} else if (char === "," && depth === 1) {
|
|
1609
|
-
parts.push(sql.slice(start, index).trim());
|
|
1610
|
-
start = index + 1;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
throw unavailable();
|
|
1614
|
-
}
|
|
1615
|
-
function unavailable() {
|
|
1616
|
-
return new VibeError8({ code: "VIBE_MIGRATION", message: "Cannot preserve table definition", meta: { reason: "table-definition-unavailable" } });
|
|
1617
|
-
}
|
|
1618
|
-
function sqliteColumnClauses(params) {
|
|
1619
|
-
const columns = [];
|
|
1620
|
-
for (const part of tableDefinitionParts(params).clauses) {
|
|
1621
|
-
const token = /^(?:"((?:[^"]|"")+)"|`((?:[^`]|``)+)`|\[([^\]]+)\]|([A-Za-z_][A-Za-z_0-9]*))/.exec(part);
|
|
1622
|
-
if (token === null)
|
|
1623
|
-
throw unavailable();
|
|
1624
|
-
if (token[4] !== undefined && /^(?:CONSTRAINT|PRIMARY|UNIQUE|CHECK|FOREIGN)$/i.test(token[4]))
|
|
1625
|
-
continue;
|
|
1626
|
-
const name = token[1]?.replaceAll('""', '"') ?? token[2]?.replaceAll("``", "`") ?? token[3] ?? token[4];
|
|
1627
|
-
columns.push({ name, sql: part });
|
|
1628
|
-
}
|
|
1629
|
-
return columns;
|
|
1630
|
-
}
|
|
1631
1883
|
// packages/sql/src/dialects/diagnostics.ts
|
|
1632
1884
|
import { unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
1633
1885
|
var QUERIES = Object.freeze({
|
|
@@ -2108,6 +2360,8 @@ function renderStatement(params) {
|
|
|
2108
2360
|
return renderUpdate({ dialect, statement, collector });
|
|
2109
2361
|
case "delete":
|
|
2110
2362
|
return renderDelete({ dialect, statement, collector });
|
|
2363
|
+
case "chain":
|
|
2364
|
+
return renderChain({ dialect, statement, collector });
|
|
2111
2365
|
case "rlsReadiness":
|
|
2112
2366
|
return renderRlsReadinessQuery({ dialect, ...statement });
|
|
2113
2367
|
case "rlsProbeTable":
|
|
@@ -2141,8 +2395,29 @@ function quoteCol(dialect, column) {
|
|
|
2141
2395
|
}
|
|
2142
2396
|
return `CAST(${ref} AS REAL)`;
|
|
2143
2397
|
}
|
|
2398
|
+
if (column.castJsonb !== undefined) {
|
|
2399
|
+
if (dialect.name !== "postgres") {
|
|
2400
|
+
throw internalError2({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
2401
|
+
}
|
|
2402
|
+
return `${ref}::${column.castJsonb}`;
|
|
2403
|
+
}
|
|
2144
2404
|
return ref;
|
|
2145
2405
|
}
|
|
2406
|
+
function typedOrderRef(params) {
|
|
2407
|
+
const { statement, column } = params;
|
|
2408
|
+
if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined)
|
|
2409
|
+
return column;
|
|
2410
|
+
const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
|
|
2411
|
+
return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
|
|
2412
|
+
}
|
|
2413
|
+
function jsonCellCast(dialect, cell) {
|
|
2414
|
+
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
2415
|
+
return dialect.jsonParamCast;
|
|
2416
|
+
if (dialect.name !== "postgres") {
|
|
2417
|
+
throw internalError2({ message: "JsonParamCell storage json is a postgres construct" });
|
|
2418
|
+
}
|
|
2419
|
+
return "::text::json";
|
|
2420
|
+
}
|
|
2146
2421
|
function quoteProjection(dialect, column) {
|
|
2147
2422
|
if (column.transform === undefined) {
|
|
2148
2423
|
const base = quoteCol(dialect, column);
|
|
@@ -2151,7 +2426,7 @@ function quoteProjection(dialect, column) {
|
|
|
2151
2426
|
return `${base} AS ${dialect.quoteIdent({ name: column.alias })}`;
|
|
2152
2427
|
}
|
|
2153
2428
|
if (dialect.name !== "postgres") {
|
|
2154
|
-
throw internalError2({ message: "ColRef.transform is a postgres
|
|
2429
|
+
throw internalError2({ message: "ColRef.transform is a postgres construct" });
|
|
2155
2430
|
}
|
|
2156
2431
|
const ref = quoteCol(dialect, column);
|
|
2157
2432
|
const alias = dialect.quoteIdent({ name: column.name });
|
|
@@ -2159,6 +2434,10 @@ function quoteProjection(dialect, column) {
|
|
|
2159
2434
|
return `${ref}::text AS ${alias}`;
|
|
2160
2435
|
return `encode(${ref}, 'base64') AS ${alias}`;
|
|
2161
2436
|
}
|
|
2437
|
+
function renderProjection(params) {
|
|
2438
|
+
const { dialect, columns } = params;
|
|
2439
|
+
return columns.map((column) => quoteProjection(dialect, column)).join(", ");
|
|
2440
|
+
}
|
|
2162
2441
|
function renderTable(dialect, table) {
|
|
2163
2442
|
if (table.namespace !== undefined)
|
|
2164
2443
|
assertSqlNamespace2({ namespace: table.namespace, dialect: dialect.name });
|
|
@@ -2219,7 +2498,8 @@ function renderExpr(params) {
|
|
|
2219
2498
|
if (isDbNowMarker(expr.value)) {
|
|
2220
2499
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
2221
2500
|
}
|
|
2222
|
-
|
|
2501
|
+
const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
|
|
2502
|
+
return `${compared.column} ${op} ${compared.operand}`;
|
|
2223
2503
|
}
|
|
2224
2504
|
case "cmpCol": {
|
|
2225
2505
|
const op = COMPARISON_SQL2[expr.op];
|
|
@@ -2235,6 +2515,10 @@ function renderExpr(params) {
|
|
|
2235
2515
|
return expr.negate === true ? "1 = 1" : "1 = 0";
|
|
2236
2516
|
}
|
|
2237
2517
|
const column = quoteCol(dialect, expr.column);
|
|
2518
|
+
if (expr.timeOfDay === true && dialect.timeOfDayInStrategy === "equalities") {
|
|
2519
|
+
const equalities = collector.addMany(expr.values).map((placeholder) => `${column} = ${placeholder}`);
|
|
2520
|
+
return `${expr.negate === true ? "NOT " : ""}(${equalities.join(" OR ")})`;
|
|
2521
|
+
}
|
|
2238
2522
|
if (dialect.inArrayStrategy === "any") {
|
|
2239
2523
|
const base = `${column} = ANY(${collector.add(Array.isArray(expr.values) ? [...expr.values] : expr.values)})`;
|
|
2240
2524
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
@@ -2328,16 +2612,21 @@ function renderExpr(params) {
|
|
|
2328
2612
|
}
|
|
2329
2613
|
case "arrayHas": {
|
|
2330
2614
|
assertScalarArrays(dialect, 'the scalar-list filter "has"');
|
|
2331
|
-
const
|
|
2615
|
+
const compared = listComparison({ dialect, column: expr.column, value: expr.value, collector });
|
|
2616
|
+
const base = `${compared.operand} = ANY(${compared.column})`;
|
|
2332
2617
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
2333
2618
|
}
|
|
2334
2619
|
case "arrayContainsAll": {
|
|
2335
2620
|
assertScalarArrays(dialect, 'the scalar-list filter "hasEvery"');
|
|
2336
|
-
|
|
2621
|
+
const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
|
|
2622
|
+
const compared = listComparison({ dialect, column: expr.column, value: values, collector });
|
|
2623
|
+
return `${compared.column} @> ${compared.operand}`;
|
|
2337
2624
|
}
|
|
2338
2625
|
case "arrayOverlaps": {
|
|
2339
2626
|
assertScalarArrays(dialect, 'the scalar-list filter "hasSome"');
|
|
2340
|
-
|
|
2627
|
+
const values = Array.isArray(expr.values) ? [...expr.values] : expr.values;
|
|
2628
|
+
const compared = listComparison({ dialect, column: expr.column, value: values, collector });
|
|
2629
|
+
return `${compared.column} && ${compared.operand}`;
|
|
2341
2630
|
}
|
|
2342
2631
|
case "arrayIsEmpty": {
|
|
2343
2632
|
assertScalarArrays(dialect, 'the scalar-list filter "isEmpty"');
|
|
@@ -2385,6 +2674,16 @@ function jsonPathString(path) {
|
|
|
2385
2674
|
}
|
|
2386
2675
|
return out;
|
|
2387
2676
|
}
|
|
2677
|
+
function listComparison(params) {
|
|
2678
|
+
const { dialect, column, value, collector } = params;
|
|
2679
|
+
if (isJsonListParameter(value) && value.storage === "json") {
|
|
2680
|
+
return {
|
|
2681
|
+
column: `${quoteCol(dialect, column)}::jsonb[]`,
|
|
2682
|
+
operand: collector.add(jsonListParameter({ value: value.value, storage: "jsonb" }))
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
return { column: quoteCol(dialect, column), operand: collector.add(value) };
|
|
2686
|
+
}
|
|
2388
2687
|
function jsonStringPattern(params) {
|
|
2389
2688
|
const { expr } = params;
|
|
2390
2689
|
if (typeof expr.value !== "string") {
|
|
@@ -2422,19 +2721,22 @@ function renderJsonFilter(params) {
|
|
|
2422
2721
|
if (dialect.jsonPathMode === "operators") {
|
|
2423
2722
|
const column = quoteCol(dialect, expr.column);
|
|
2424
2723
|
const pathParam = `${collector.add(expr.path.map(String))}::text[]`;
|
|
2724
|
+
const extracted = expr.storage === "json" ? `(${column} #> ${pathParam})::jsonb` : `(${column} #> ${pathParam})`;
|
|
2425
2725
|
switch (expr.op) {
|
|
2426
2726
|
case "equals":
|
|
2427
|
-
return
|
|
2727
|
+
return `${extracted} ${cmp} ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2428
2728
|
case "string_contains":
|
|
2429
2729
|
case "string_starts_with":
|
|
2430
|
-
case "string_ends_with":
|
|
2431
|
-
|
|
2730
|
+
case "string_ends_with": {
|
|
2731
|
+
const target = expr.storage === "json" ? `(${extracted} #>> '{}')` : `(${column} #>> ${pathParam})`;
|
|
2732
|
+
return renderJsonLike({ dialect, target, expr, collector });
|
|
2733
|
+
}
|
|
2432
2734
|
case "array_contains":
|
|
2433
|
-
return
|
|
2735
|
+
return `${extracted} @> ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2434
2736
|
case "array_starts_with":
|
|
2435
|
-
return
|
|
2737
|
+
return `${extracted} -> 0 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2436
2738
|
case "array_ends_with":
|
|
2437
|
-
return
|
|
2739
|
+
return `${extracted} -> -1 = ${collector.add(JSON.stringify(expr.value))}${dialect.jsonParamCast}`;
|
|
2438
2740
|
}
|
|
2439
2741
|
}
|
|
2440
2742
|
if (dialect.jsonPathMode === "sqliteExtract") {
|
|
@@ -2578,7 +2880,14 @@ function renderLateralJoin(params) {
|
|
|
2578
2880
|
const terms = join.select.orderBy;
|
|
2579
2881
|
const columnTerms = terms.filter((term) => ("column" in term));
|
|
2580
2882
|
if (columnTerms.length === terms.length && columnTerms.every((term) => projected.has(term.column.name))) {
|
|
2581
|
-
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
2883
|
+
const rendered = columnTerms.map((term) => renderOrderTerm({
|
|
2884
|
+
dialect,
|
|
2885
|
+
term: {
|
|
2886
|
+
...term,
|
|
2887
|
+
column: { table: "__sub", name: term.column.name, ...term.column.castJsonb === undefined ? {} : { castJsonb: term.column.castJsonb } }
|
|
2888
|
+
},
|
|
2889
|
+
collector
|
|
2890
|
+
}));
|
|
2582
2891
|
aggOrder = ` ORDER BY ${rendered.join(", ")}`;
|
|
2583
2892
|
}
|
|
2584
2893
|
}
|
|
@@ -2587,6 +2896,15 @@ function renderLateralJoin(params) {
|
|
|
2587
2896
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
2588
2897
|
return `LEFT JOIN LATERAL (SELECT ${projection} FROM (${inner.text}) AS ${subAlias}) AS ${alias} ON true`;
|
|
2589
2898
|
}
|
|
2899
|
+
function renderCountSubquery(params) {
|
|
2900
|
+
const { dialect, subquery, collector } = params;
|
|
2901
|
+
const select = subquery.select;
|
|
2902
|
+
if (select.columns.length !== 0 || select.selectExpr?.kind !== "countAll" || (select.aggregates?.length ?? 0) > 0 || select.rowNumber !== undefined || select.groupBy !== undefined || select.having !== undefined || select.limit !== undefined || select.offset !== undefined || select.distinctOn !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined) {
|
|
2903
|
+
throw internalError2({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
2904
|
+
}
|
|
2905
|
+
const inner = renderSelect({ dialect, statement: select, collector });
|
|
2906
|
+
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
2907
|
+
}
|
|
2590
2908
|
function applyReturning(params) {
|
|
2591
2909
|
const { dialect, requested } = params;
|
|
2592
2910
|
if (requested === undefined || requested.length === 0) {
|
|
@@ -2595,9 +2913,65 @@ function applyReturning(params) {
|
|
|
2595
2913
|
if (!dialect.capabilities.returning) {
|
|
2596
2914
|
return { suffix: "", status: "omitted" };
|
|
2597
2915
|
}
|
|
2598
|
-
const columns = requested.map((
|
|
2916
|
+
const columns = requested.map((column) => {
|
|
2917
|
+
if (typeof column === "string")
|
|
2918
|
+
return dialect.quoteIdent({ name: column });
|
|
2919
|
+
if (dialect.name !== "postgres") {
|
|
2920
|
+
throw internalError2({ message: "a castText RETURNING column is a postgres construct" });
|
|
2921
|
+
}
|
|
2922
|
+
const name = dialect.quoteIdent({ name: column.name });
|
|
2923
|
+
return `${name}::text AS ${name}`;
|
|
2924
|
+
}).join(", ");
|
|
2599
2925
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
2600
2926
|
}
|
|
2927
|
+
function renderKeyTableJoin(params) {
|
|
2928
|
+
const { dialect, join, collector } = params;
|
|
2929
|
+
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
2930
|
+
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
2931
|
+
if (!admitted) {
|
|
2932
|
+
throw unsupportedCapability15({
|
|
2933
|
+
dialect: dialect.name,
|
|
2934
|
+
feature: "relation key table",
|
|
2935
|
+
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
2939
|
+
throw internalError2({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
2940
|
+
}
|
|
2941
|
+
if (join.rows.length === 0)
|
|
2942
|
+
throw internalError2({ message: "key table needs at least one key row" });
|
|
2943
|
+
for (const member of join.members) {
|
|
2944
|
+
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
2945
|
+
throw internalError2({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
const alias = dialect.quoteIdent({ name: join.alias });
|
|
2949
|
+
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
2950
|
+
const rows = join.rows.map((row, ordinal) => {
|
|
2951
|
+
if (row.length !== join.members.length) {
|
|
2952
|
+
throw internalError2({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
2953
|
+
}
|
|
2954
|
+
const cells = row.map((value, index) => {
|
|
2955
|
+
const placeholder = collector.add(value);
|
|
2956
|
+
const member = join.members[index];
|
|
2957
|
+
switch (member.kind) {
|
|
2958
|
+
case "integer":
|
|
2959
|
+
return dialect.name === "mysql" ? `CAST(${placeholder} AS SIGNED)` : `CAST(${placeholder} AS bigint)`;
|
|
2960
|
+
case "decimal":
|
|
2961
|
+
return `CAST(${placeholder} AS DECIMAL(65,${member.scale}))`;
|
|
2962
|
+
case "citext":
|
|
2963
|
+
return `CAST(${placeholder} AS citext)`;
|
|
2964
|
+
case "text":
|
|
2965
|
+
return `CAST(${placeholder} AS text)`;
|
|
2966
|
+
default:
|
|
2967
|
+
return placeholder;
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
return dialect.name === "mysql" ? `ROW(${cells.join(", ")}, ${ordinal})` : `(${cells.join(", ")}, ${ordinal})`;
|
|
2971
|
+
});
|
|
2972
|
+
const on = join.columns.map((column, index) => `${quoteCol(dialect, column)} = ${alias}.${memberNames[index]}`).join(" AND ");
|
|
2973
|
+
return `INNER JOIN (VALUES ${rows.join(", ")}) AS ${alias} (${[...memberNames, dialect.quoteIdent({ name: join.ordinalAlias })].join(", ")}) ON ${on}`;
|
|
2974
|
+
}
|
|
2601
2975
|
function renderSelect(params) {
|
|
2602
2976
|
const { dialect, statement, collector } = params;
|
|
2603
2977
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
@@ -2615,6 +2989,11 @@ function renderSelect(params) {
|
|
|
2615
2989
|
columnList.push(`${renderAggRef(dialect, selection.agg)} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
2616
2990
|
}
|
|
2617
2991
|
}
|
|
2992
|
+
if (statement.countSubqueries !== undefined) {
|
|
2993
|
+
for (const subquery of statement.countSubqueries) {
|
|
2994
|
+
columnList.push(renderCountSubquery({ dialect, subquery, collector }));
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2618
2997
|
if (statement.rowNumber !== undefined) {
|
|
2619
2998
|
const rowNumber = statement.rowNumber;
|
|
2620
2999
|
if (rowNumber.partitionBy.length === 0) {
|
|
@@ -2633,7 +3012,7 @@ function renderSelect(params) {
|
|
|
2633
3012
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
2634
3013
|
});
|
|
2635
3014
|
}
|
|
2636
|
-
distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, column)).join(", ")}) `;
|
|
3015
|
+
distinctPrefix = `DISTINCT ON (${statement.distinctOn.map((column) => quoteCol(dialect, typedOrderRef({ statement, column }))).join(", ")}) `;
|
|
2637
3016
|
}
|
|
2638
3017
|
const columns = columnList.length === 0 ? "*" : columnList.join(", ");
|
|
2639
3018
|
let from;
|
|
@@ -2656,6 +3035,9 @@ function renderSelect(params) {
|
|
|
2656
3035
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
2657
3036
|
}
|
|
2658
3037
|
}
|
|
3038
|
+
if (statement.keyTable !== undefined) {
|
|
3039
|
+
text += ` ${renderKeyTableJoin({ dialect, join: statement.keyTable, collector })}`;
|
|
3040
|
+
}
|
|
2659
3041
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
2660
3042
|
if (!dialect.capabilities.lateralJoin) {
|
|
2661
3043
|
throw unsupportedCapability15({
|
|
@@ -2678,7 +3060,7 @@ function renderSelect(params) {
|
|
|
2678
3060
|
text += ` HAVING ${renderExpr({ dialect, expr: statement.having, collector })}`;
|
|
2679
3061
|
}
|
|
2680
3062
|
if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
|
|
2681
|
-
const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term, collector }));
|
|
3063
|
+
const terms = statement.orderBy.map((term) => renderOrderTerm({ dialect, term: "column" in term ? { ...term, column: typedOrderRef({ statement, column: term.column }) } : term, collector }));
|
|
2682
3064
|
text += ` ORDER BY ${terms.join(", ")}`;
|
|
2683
3065
|
}
|
|
2684
3066
|
if (statement.limit !== undefined) {
|
|
@@ -2720,7 +3102,7 @@ function renderInsert(params) {
|
|
|
2720
3102
|
const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
2721
3103
|
const rowsSql = statement.rows.map((row) => `(${row.map((value) => {
|
|
2722
3104
|
if (isJsonParamCell(value))
|
|
2723
|
-
return `${collector.add(value.text)}${dialect
|
|
3105
|
+
return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
2724
3106
|
if (isDbNowMarker(value)) {
|
|
2725
3107
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
2726
3108
|
}
|
|
@@ -2852,7 +3234,7 @@ function renderAssignments(params) {
|
|
|
2852
3234
|
return `${column} = ${renderIncomingRowColumn({ dialect, column: value.column })}`;
|
|
2853
3235
|
}
|
|
2854
3236
|
if (isJsonParamCell(value)) {
|
|
2855
|
-
return `${column} = ${collector.add(value.text)}${dialect
|
|
3237
|
+
return `${column} = ${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
2856
3238
|
}
|
|
2857
3239
|
if (isSetOperation(value)) {
|
|
2858
3240
|
const operand = collector.add(value.value);
|
|
@@ -2881,6 +3263,172 @@ function renderDelete(params) {
|
|
|
2881
3263
|
const returning = applyReturning({ dialect, requested: statement.returning });
|
|
2882
3264
|
return { text: text + returning.suffix, values: collector.values, returning: returning.status };
|
|
2883
3265
|
}
|
|
3266
|
+
var CHAIN_ROWS_ALIAS = "__vibe_rows";
|
|
3267
|
+
var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
3268
|
+
function renderChain(params) {
|
|
3269
|
+
const { dialect, statement, collector } = params;
|
|
3270
|
+
if (dialect.statementChain === null) {
|
|
3271
|
+
throw unsupportedCapability15({
|
|
3272
|
+
dialect: dialect.name,
|
|
3273
|
+
feature: "a statement chain (data-modifying WITH steps)",
|
|
3274
|
+
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
if (statement.steps.length === 0)
|
|
3278
|
+
throw internalError2({ message: "a statement chain needs at least one step" });
|
|
3279
|
+
const defined = new Set;
|
|
3280
|
+
const steps = [];
|
|
3281
|
+
for (const step of statement.steps) {
|
|
3282
|
+
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
3283
|
+
throw internalError2({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
3284
|
+
}
|
|
3285
|
+
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
3286
|
+
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
3287
|
+
defined.add(step.name);
|
|
3288
|
+
}
|
|
3289
|
+
const result = statement.result;
|
|
3290
|
+
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
3291
|
+
throw internalError2({ message: "a chain result needs at least one source step and one column" });
|
|
3292
|
+
}
|
|
3293
|
+
for (const name of result.rowsFrom) {
|
|
3294
|
+
if (!defined.has(name))
|
|
3295
|
+
throw internalError2({ message: `chain result reads undefined step "${name}"` });
|
|
3296
|
+
}
|
|
3297
|
+
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3298
|
+
const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
|
|
3299
|
+
let select = union;
|
|
3300
|
+
if (result.verdict !== undefined || result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
3301
|
+
const extra = [];
|
|
3302
|
+
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
3303
|
+
if (alias !== undefined && result.columns.includes(alias)) {
|
|
3304
|
+
throw internalError2({ message: `chain result alias "${alias}" collides with a result column` });
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
3308
|
+
const projected = result.columns.map((name) => `${rows}.${dialect.quoteIdent({ name })}`);
|
|
3309
|
+
if (result.verdict !== undefined) {
|
|
3310
|
+
extra.push(`${renderExpr({ dialect, expr: result.verdict.expr, collector })} AS ${dialect.quoteIdent({ name: result.verdict.alias })}`);
|
|
3311
|
+
}
|
|
3312
|
+
if (result.tally !== undefined || result.veto !== undefined && result.veto.length > 0) {
|
|
3313
|
+
extra.push(renderTallyColumn({ dialect, result, defined, collector }));
|
|
3314
|
+
}
|
|
3315
|
+
select = `SELECT ${[...projected, ...extra].join(", ")} FROM (SELECT 1) AS ${dialect.quoteIdent({ name: CHAIN_ONE_ALIAS })}` + ` LEFT JOIN (${union}) AS ${rows} ON TRUE`;
|
|
3316
|
+
}
|
|
3317
|
+
return { text: `WITH ${steps.join(", ")} ${select}`, values: collector.values, returning: "none" };
|
|
3318
|
+
}
|
|
3319
|
+
function renderChainStep(params) {
|
|
3320
|
+
const { dialect, statement, collector } = params;
|
|
3321
|
+
switch (statement.kind) {
|
|
3322
|
+
case "select":
|
|
3323
|
+
return renderSelect({ dialect, statement, collector }).text;
|
|
3324
|
+
case "insert":
|
|
3325
|
+
return renderInsert({ dialect, statement, collector }).text;
|
|
3326
|
+
case "insertSelect":
|
|
3327
|
+
return renderInsertSelect({ dialect, statement, collector, earlier: params.earlier });
|
|
3328
|
+
case "update":
|
|
3329
|
+
return renderUpdate({ dialect, statement, collector }).text;
|
|
3330
|
+
case "delete":
|
|
3331
|
+
return renderDelete({ dialect, statement, collector }).text;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function renderSelectCell(params) {
|
|
3335
|
+
const { dialect, value, collector } = params;
|
|
3336
|
+
if (isJsonParamCell(value))
|
|
3337
|
+
return `${collector.add(value.text)}${jsonCellCast(dialect, value)}`;
|
|
3338
|
+
if (isDbNowMarker(value))
|
|
3339
|
+
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
3340
|
+
if (isSqlDefaultCell(value)) {
|
|
3341
|
+
throw internalError2({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
3342
|
+
}
|
|
3343
|
+
return collector.add(value);
|
|
3344
|
+
}
|
|
3345
|
+
function renderRowSource(params) {
|
|
3346
|
+
const { dialect, source, collector } = params;
|
|
3347
|
+
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
3348
|
+
throw internalError2({ message: "a row source needs at least one column and one row" });
|
|
3349
|
+
}
|
|
3350
|
+
const typedBy = renderTable(dialect, source.typedBy);
|
|
3351
|
+
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
3352
|
+
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
3353
|
+
for (const row of source.rows) {
|
|
3354
|
+
if (row.length !== source.columns.length) {
|
|
3355
|
+
throw internalError2({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
3356
|
+
}
|
|
3357
|
+
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
3358
|
+
}
|
|
3359
|
+
const alias = dialect.quoteIdent({ name: source.alias });
|
|
3360
|
+
const columnList = columns.join(", ");
|
|
3361
|
+
return `(SELECT ${columnList} FROM (VALUES ${rows.join(", ")}) AS ${alias} (${columnList}) OFFSET 1) AS ${alias}`;
|
|
3362
|
+
}
|
|
3363
|
+
function renderTallyColumn(params) {
|
|
3364
|
+
const { dialect, result, defined, collector } = params;
|
|
3365
|
+
const tally = result.tally;
|
|
3366
|
+
if (tally === undefined)
|
|
3367
|
+
throw internalError2({ message: "a chain veto needs a tally column to guard" });
|
|
3368
|
+
if (tally.steps.length === 0)
|
|
3369
|
+
throw internalError2({ message: "a chain tally needs at least one step" });
|
|
3370
|
+
const step = (name) => {
|
|
3371
|
+
if (!defined.has(name))
|
|
3372
|
+
throw internalError2({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
3373
|
+
return dialect.quoteIdent({ name });
|
|
3374
|
+
};
|
|
3375
|
+
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
3376
|
+
const sum = tally.steps.map(count).join(" + ");
|
|
3377
|
+
const vetoes = result.veto ?? [];
|
|
3378
|
+
if (vetoes.length === 0)
|
|
3379
|
+
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
3380
|
+
const arms = vetoes.map((veto) => {
|
|
3381
|
+
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
3382
|
+
throw internalError2({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
3383
|
+
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
3384
|
+
throw internalError2({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
3385
|
+
const minRows = veto.minRows ?? 1;
|
|
3386
|
+
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
3387
|
+
throw internalError2({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
3388
|
+
const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
|
|
3389
|
+
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
3390
|
+
});
|
|
3391
|
+
return `CASE ${arms.join(" ")} ELSE ${sum} END AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
3392
|
+
}
|
|
3393
|
+
function renderInsertSelect(params) {
|
|
3394
|
+
const { dialect, statement, collector } = params;
|
|
3395
|
+
if (statement.columns.length === 0)
|
|
3396
|
+
throw internalError2({ message: "insert requires at least one column" });
|
|
3397
|
+
if (statement.cells.length !== statement.columns.length) {
|
|
3398
|
+
throw internalError2({
|
|
3399
|
+
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
3400
|
+
});
|
|
3401
|
+
}
|
|
3402
|
+
const from = statement.from ?? [];
|
|
3403
|
+
for (const name of from) {
|
|
3404
|
+
if (!params.earlier.has(name))
|
|
3405
|
+
throw internalError2({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
3406
|
+
}
|
|
3407
|
+
const source = statement.source;
|
|
3408
|
+
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
3409
|
+
throw internalError2({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
3410
|
+
}
|
|
3411
|
+
const cells = statement.cells.map((value) => {
|
|
3412
|
+
if (isStepColumnRef(value)) {
|
|
3413
|
+
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
3414
|
+
throw internalError2({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
3415
|
+
}
|
|
3416
|
+
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
3417
|
+
}
|
|
3418
|
+
return renderSelectCell({ dialect, value, collector });
|
|
3419
|
+
});
|
|
3420
|
+
const columnList = statement.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
3421
|
+
let text = `INSERT INTO ${renderTable(dialect, statement.table)} (${columnList}) SELECT ${cells.join(", ")}`;
|
|
3422
|
+
const relations = from.map((name) => dialect.quoteIdent({ name }));
|
|
3423
|
+
if (source !== undefined)
|
|
3424
|
+
relations.push(renderRowSource({ dialect, source, collector }));
|
|
3425
|
+
if (relations.length > 0)
|
|
3426
|
+
text += ` FROM ${relations.join(" CROSS JOIN ")}`;
|
|
3427
|
+
if (statement.where !== undefined) {
|
|
3428
|
+
text += ` WHERE ${renderExpr({ dialect, expr: statement.where, collector })}`;
|
|
3429
|
+
}
|
|
3430
|
+
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
3431
|
+
}
|
|
2884
3432
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
2885
3433
|
import { unsupportedCapability as unsupportedCapability16 } from "@vibeorm/schema";
|
|
2886
3434
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
@@ -3410,6 +3958,8 @@ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.att
|
|
|
3410
3958
|
export {
|
|
3411
3959
|
ADVISORY_KEY_BOUNDS,
|
|
3412
3960
|
ADVISORY_LOCK_RESULT_COLUMN,
|
|
3961
|
+
CHAIN_VETO_PREFIX,
|
|
3962
|
+
CHAIN_VETO_TOKEN_PATTERN,
|
|
3413
3963
|
DIALECT_CAPABILITIES,
|
|
3414
3964
|
DIALECT_CODECS,
|
|
3415
3965
|
JSON_TRANSPORT,
|
|
@@ -3431,6 +3981,7 @@ export {
|
|
|
3431
3981
|
SQLITE_SESSION_SQL,
|
|
3432
3982
|
SQL_DEFAULT_CELL,
|
|
3433
3983
|
SQL_DIALECTS,
|
|
3984
|
+
chainVetoToken,
|
|
3434
3985
|
compileExclusion,
|
|
3435
3986
|
compileSql,
|
|
3436
3987
|
containsPattern,
|
|
@@ -3443,23 +3994,29 @@ export {
|
|
|
3443
3994
|
escapeGlobPattern,
|
|
3444
3995
|
escapeLikePattern,
|
|
3445
3996
|
excludedRef,
|
|
3997
|
+
float32Codec,
|
|
3446
3998
|
getSqlDialect,
|
|
3447
3999
|
isDbNowMarker,
|
|
3448
4000
|
isExcludedRef,
|
|
3449
4001
|
isFixedDiagnostic,
|
|
4002
|
+
isJsonListParameter,
|
|
3450
4003
|
isJsonParamCell,
|
|
3451
4004
|
isListSetOperation,
|
|
3452
4005
|
isNativeEnumArrayParameter,
|
|
3453
4006
|
isSetOperation,
|
|
3454
4007
|
isSqlDefaultCell,
|
|
3455
4008
|
isSqlFragment,
|
|
4009
|
+
isStepColumnRef,
|
|
4010
|
+
jsonListParameter,
|
|
3456
4011
|
jsonParamCell,
|
|
3457
4012
|
mapConflictColumns,
|
|
3458
4013
|
mysqlDialect,
|
|
3459
4014
|
nativeEnumArrayParameter,
|
|
3460
4015
|
parseDateTimeText,
|
|
3461
4016
|
parsePgArrayLiteral,
|
|
4017
|
+
parsePgArrayLiteralNested,
|
|
3462
4018
|
parsePostgresTypeName,
|
|
4019
|
+
postgresArrayElementFromText,
|
|
3463
4020
|
postgresDialect,
|
|
3464
4021
|
projectionOutputName,
|
|
3465
4022
|
renderAdvisoryLock,
|
|
@@ -3501,6 +4058,7 @@ export {
|
|
|
3501
4058
|
renderPostgresTypeName,
|
|
3502
4059
|
renderPostgresWorkload,
|
|
3503
4060
|
renderPredicate,
|
|
4061
|
+
renderProjection,
|
|
3504
4062
|
renderRelationStateQuery,
|
|
3505
4063
|
renderReplayCoverageGaps,
|
|
3506
4064
|
renderRlsColumnType,
|
|
@@ -3521,17 +4079,23 @@ export {
|
|
|
3521
4079
|
renderSqlFragment,
|
|
3522
4080
|
renderSqliteColumnInventory,
|
|
3523
4081
|
renderSqliteForeignKeyInventory,
|
|
4082
|
+
renderSqliteFtsUpdateTrigger,
|
|
3524
4083
|
renderSqliteRebuildObjects,
|
|
3525
4084
|
renderStatement,
|
|
4085
|
+
returningColumnName,
|
|
4086
|
+
shortestFloat32,
|
|
3526
4087
|
sql,
|
|
3527
4088
|
sqlUsesIncoming,
|
|
3528
4089
|
sqliteColumnClauses,
|
|
3529
4090
|
sqliteDialect,
|
|
4091
|
+
sqliteFtsUpdateUpgrade,
|
|
3530
4092
|
sqliteTableConstraints,
|
|
3531
4093
|
startsWithPattern,
|
|
4094
|
+
stepColumnRef,
|
|
3532
4095
|
strictMatchPattern,
|
|
3533
4096
|
tableDefinitionParts,
|
|
4097
|
+
timeOfDayCodec,
|
|
3534
4098
|
toPgArrayLiteral
|
|
3535
4099
|
};
|
|
3536
4100
|
|
|
3537
|
-
//# debugId=
|
|
4101
|
+
//# debugId=67799F9417158D9264756E2164756E21
|