@vibeorm/sql 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.d.ts +29 -1
- package/dist/date-truncation.d.ts +27 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +254 -196
- package/dist/index.js.map +7 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1216,7 +1216,7 @@ function renderPostgresIndexKeys(params) {
|
|
|
1216
1216
|
throw unsupportedCapability6({ dialect: dialect.name, feature: "indexOperatorClasses" });
|
|
1217
1217
|
const collector = new ParamCollector(dialect);
|
|
1218
1218
|
const scope = params.table !== undefined ? `${params.expressionMetadata && params.table.namespace === "pg_temp" ? "n.oid = pg_catalog.pg_my_temp_schema()" : `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(", ")})`;
|
|
1219
|
-
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 = ${params.expressionMetadata ? "key_attribute.atttypid" : "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`;
|
|
1219
|
+
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 = ${params.expressionMetadata ? "COALESCE(a.atttypid, key_attribute.atttypid)" : "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`;
|
|
1220
1220
|
const expressionColumns = params.expressionMetadata ? `, CASE WHEN k.attnum = 0 THEN pg_catalog.pg_get_indexdef(i.indexrelid, k.ordinality::integer, false) ELSE NULL END AS expression, pg_catalog.pg_get_indexdef(i.indexrelid, 0, false) AS index_definition, pg_catalog.format_type(key_attribute.atttypid, key_attribute.atttypmod) AS result_type, (i.indoption[k.ordinality - 1] & 2) <> 0 AS nulls_first, ((i.indoption[k.ordinality - 1] & 2) <> 0) = ((i.indoption[k.ordinality - 1] & 1) <> 0) AS default_null_order, i.indcollation[k.ordinality - 1] AS collation_oid, CASE WHEN k.attnum = 0 THEN NULL ELSE i.indcollation[k.ordinality - 1] = a.attcollation END AS default_collation, i.indisvalid AS valid, i.indisready AS ready, i.indimmediate AS immediate, i.indnkeyatts AS key_count, i.indnatts AS attribute_count` : "";
|
|
1221
1221
|
const expressionJoins = params.expressionMetadata ? ` JOIN pg_catalog.pg_attribute key_attribute ON key_attribute.attrelid = idx.oid AND key_attribute.attnum = k.ordinality` : "";
|
|
1222
1222
|
return {
|
|
@@ -3281,17 +3281,53 @@ function renderLocalDateCheck({ dialect, table, column }) {
|
|
|
3281
3281
|
expression: `${quoted} IS date(${quoted}) AND (${quoted} IS NULL OR ${quoted} >= ${dialect.quoteStringLiteral({ value: "0001-01-01" })})`
|
|
3282
3282
|
};
|
|
3283
3283
|
}
|
|
3284
|
+
// packages/sql/src/date-truncation.ts
|
|
3285
|
+
import { DATE_TRUNCATION_UNITS, VibeError as VibeError16, internalError as internalError2, unsupportedCapability as unsupportedCapability17 } from "@vibeorm/schema";
|
|
3286
|
+
var TIME_ZONE_NAME = /^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/;
|
|
3287
|
+
function isTimeZoneName(params) {
|
|
3288
|
+
return typeof params.value === "string" && TIME_ZONE_NAME.test(params.value);
|
|
3289
|
+
}
|
|
3290
|
+
function renderDateTruncation(params) {
|
|
3291
|
+
const { dialect, reference, truncation } = params;
|
|
3292
|
+
if (!dialect.capabilities.dateTruncation) {
|
|
3293
|
+
throw unsupportedCapability17({
|
|
3294
|
+
dialect: dialect.name,
|
|
3295
|
+
feature: "a truncated groupBy key ({ field, truncate, timeZone })",
|
|
3296
|
+
hint: "only PostgreSQL and PGlite truncate a timestamp in a named time zone \u2014 group by the plain field and bucket the rows in code instead"
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
if (!DATE_TRUNCATION_UNITS.includes(truncation.unit)) {
|
|
3300
|
+
throw internalError2({ message: `unknown date truncation unit ${JSON.stringify(truncation.unit)}` });
|
|
3301
|
+
}
|
|
3302
|
+
if (!isTimeZoneName({ value: truncation.timeZone })) {
|
|
3303
|
+
throw new VibeError16({
|
|
3304
|
+
code: "VIBE_VALIDATION",
|
|
3305
|
+
message: `time zone ${JSON.stringify(truncation.timeZone)} is not a time-zone name \u2014 use an IANA name such as "Europe/Oslo" or "UTC"`,
|
|
3306
|
+
meta: { timeZone: truncation.timeZone }
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
const unit = dialect.quoteStringLiteral({ value: truncation.unit });
|
|
3310
|
+
const zone = dialect.quoteStringLiteral({ value: truncation.timeZone });
|
|
3311
|
+
switch (truncation.storage) {
|
|
3312
|
+
case "timestamptz":
|
|
3313
|
+
return `date_trunc(${unit}, ${reference}, ${zone})`;
|
|
3314
|
+
case "timestamp":
|
|
3315
|
+
return `(date_trunc(${unit}, ${reference} AT TIME ZONE 'UTC', ${zone}) AT TIME ZONE 'UTC')`;
|
|
3316
|
+
default:
|
|
3317
|
+
throw internalError2({ message: `unknown date truncation storage ${JSON.stringify(truncation.storage)}` });
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3284
3320
|
// packages/sql/src/generated-columns.ts
|
|
3285
|
-
import { VibeError as
|
|
3321
|
+
import { VibeError as VibeError17, unsupportedCapability as unsupportedCapability19 } from "@vibeorm/schema";
|
|
3286
3322
|
|
|
3287
3323
|
// packages/sql/src/dialects/postgres-generated-columns.ts
|
|
3288
|
-
import { unsupportedCapability as
|
|
3324
|
+
import { unsupportedCapability as unsupportedCapability18 } from "@vibeorm/schema";
|
|
3289
3325
|
function renderPostgresGeneratedColumnClause({ generated }) {
|
|
3290
3326
|
return `GENERATED ALWAYS AS (${generated.expression}) STORED`;
|
|
3291
3327
|
}
|
|
3292
3328
|
function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
|
|
3293
3329
|
if (dialect.name !== "postgres")
|
|
3294
|
-
throw
|
|
3330
|
+
throw unsupportedCapability18({ dialect: dialect.name, feature: "rollbackGeneratedColumnProbe" });
|
|
3295
3331
|
const q = ({ name }) => dialect.quoteIdent({ name });
|
|
3296
3332
|
let text;
|
|
3297
3333
|
switch (probe.operation) {
|
|
@@ -3312,20 +3348,20 @@ function renderPostgresGeneratedColumnProbe({ dialect, probe }) {
|
|
|
3312
3348
|
}
|
|
3313
3349
|
function renderPostgresDropGeneratedExpression({ dialect, table, column }) {
|
|
3314
3350
|
if (dialect.name !== "postgres")
|
|
3315
|
-
throw
|
|
3351
|
+
throw unsupportedCapability18({ dialect: dialect.name, feature: "dropGeneratedExpression" });
|
|
3316
3352
|
return { text: `ALTER TABLE ${dialect.quoteIdent({ name: table.namespace })}.${dialect.quoteIdent({ name: table.name })} ALTER COLUMN ${dialect.quoteIdent({ name: column })} DROP EXPRESSION`, values: [], returning: "none" };
|
|
3317
3353
|
}
|
|
3318
3354
|
|
|
3319
3355
|
// packages/sql/src/generated-columns.ts
|
|
3320
3356
|
function renderGeneratedColumnClause({ dialect, generated }) {
|
|
3321
3357
|
if (!("generatedColumns" in dialect.capabilities) || dialect.capabilities.generatedColumns !== true) {
|
|
3322
|
-
throw
|
|
3358
|
+
throw unsupportedCapability19({ dialect: dialect.name, feature: "generatedColumns" });
|
|
3323
3359
|
}
|
|
3324
3360
|
if (generated.storage !== "stored") {
|
|
3325
|
-
throw
|
|
3361
|
+
throw unsupportedCapability19({ dialect: dialect.name, feature: "virtualGeneratedColumns" });
|
|
3326
3362
|
}
|
|
3327
3363
|
if (typeof generated.expression !== "string" || generated.expression.trim().length === 0 || generated.expression.includes("\x00")) {
|
|
3328
|
-
throw new
|
|
3364
|
+
throw new VibeError17({ code: "VIBE_SCHEMA_INVALID", message: "A generated column requires non-empty, NUL-free SQL", meta: { reason: "generated-expression-invalid" } });
|
|
3329
3365
|
}
|
|
3330
3366
|
switch (dialect.name) {
|
|
3331
3367
|
case "postgres":
|
|
@@ -3337,10 +3373,10 @@ function renderGeneratedColumnClause({ dialect, generated }) {
|
|
|
3337
3373
|
}
|
|
3338
3374
|
}
|
|
3339
3375
|
// packages/sql/src/row-rules.ts
|
|
3340
|
-
import { VibeError as
|
|
3376
|
+
import { VibeError as VibeError21, internalError as internalError3, normalizeRowRules, rowRuleGateField, rowRuleIdentities, rowRuleProtectedFields, rowRuleShapeIssue, rowRuleTriggerName, unsupportedCapability as unsupportedCapability23 } from "@vibeorm/schema";
|
|
3341
3377
|
|
|
3342
3378
|
// packages/sql/src/dialects/postgres-row-rules.ts
|
|
3343
|
-
import { VibeError as
|
|
3379
|
+
import { VibeError as VibeError18, unsupportedCapability as unsupportedCapability20 } from "@vibeorm/schema";
|
|
3344
3380
|
function comparison({ dialect, field }) {
|
|
3345
3381
|
const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
3346
3382
|
const cast = field.type === "Json" ? `::pg_catalog.jsonb${field.isList ? "[]" : ""}` : "";
|
|
@@ -3348,7 +3384,7 @@ function comparison({ dialect, field }) {
|
|
|
3348
3384
|
}
|
|
3349
3385
|
function renderPostgresRowRule({ dialect, context }) {
|
|
3350
3386
|
if (dialect.name !== "postgres")
|
|
3351
|
-
throw
|
|
3387
|
+
throw unsupportedCapability20({ dialect: dialect.name, feature: "postgresRowRules" });
|
|
3352
3388
|
const { rule, table, functionIdentity, triggerIdentity, protectedFields, gateField } = context;
|
|
3353
3389
|
const qualify = ({ name }) => `${dialect.quoteIdent({ name: name.namespace })}.${dialect.quoteIdent({ name: name.name })}`;
|
|
3354
3390
|
const functionName = qualify({ name: functionIdentity.name });
|
|
@@ -3365,7 +3401,7 @@ END;`;
|
|
|
3365
3401
|
} else {
|
|
3366
3402
|
const terms = protectedFields.map((field) => comparison({ dialect, field }));
|
|
3367
3403
|
if (terms.length === 0) {
|
|
3368
|
-
throw new
|
|
3404
|
+
throw new VibeError18({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
|
|
3369
3405
|
}
|
|
3370
3406
|
const condition = gateField === undefined ? terms.join(`
|
|
3371
3407
|
OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
@@ -3400,19 +3436,19 @@ FOR EACH ROW EXECUTE FUNCTION ${functionName}();`;
|
|
|
3400
3436
|
}
|
|
3401
3437
|
|
|
3402
3438
|
// packages/sql/src/dialects/sqlite-row-rules.ts
|
|
3403
|
-
import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as
|
|
3439
|
+
import { SQLITE_ROW_RULE_ERROR_PREFIX, VibeError as VibeError19, unsupportedCapability as unsupportedCapability21 } from "@vibeorm/schema";
|
|
3404
3440
|
function renderSqliteRowRule({ dialect, context }) {
|
|
3405
3441
|
if (dialect.name !== "sqlite")
|
|
3406
|
-
throw
|
|
3442
|
+
throw unsupportedCapability21({ dialect: dialect.name, feature: "sqliteRowRules" });
|
|
3407
3443
|
const { rule, table, triggers, protectedFields, gateField } = context;
|
|
3408
3444
|
if (table.namespace !== "main")
|
|
3409
|
-
throw
|
|
3445
|
+
throw unsupportedCapability21({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
3410
3446
|
const terms = protectedFields.map((field) => {
|
|
3411
3447
|
const column = dialect.quoteIdent({ name: field.dbName ?? field.name });
|
|
3412
3448
|
return `OLD.${column} IS NOT NEW.${column}`;
|
|
3413
3449
|
});
|
|
3414
3450
|
if (rule.kind !== "appendOnly" && terms.length === 0)
|
|
3415
|
-
throw new
|
|
3451
|
+
throw new VibeError19({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
|
|
3416
3452
|
const condition = rule.kind === "appendOnly" ? "" : gateField === undefined ? `WHEN ${terms.join(`
|
|
3417
3453
|
OR `)}
|
|
3418
3454
|
` : `WHEN OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
@@ -3437,11 +3473,11 @@ END;`,
|
|
|
3437
3473
|
}
|
|
3438
3474
|
|
|
3439
3475
|
// packages/sql/src/dialects/mysql-row-rules.ts
|
|
3440
|
-
import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as
|
|
3476
|
+
import { MYSQL_ROW_RULE_MESSAGE_MAX_BYTES, VibeError as VibeError20, mysqlRowRuleMessageByteLength, unsupportedCapability as unsupportedCapability22 } from "@vibeorm/schema";
|
|
3441
3477
|
function diagnosticLiteral({ name, context, dialect }) {
|
|
3442
3478
|
const message = `Check constraint '${name}' is violated.`;
|
|
3443
3479
|
if (mysqlRowRuleMessageByteLength({ triggerName: name }) > MYSQL_ROW_RULE_MESSAGE_MAX_BYTES)
|
|
3444
|
-
throw new
|
|
3480
|
+
throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${context.rule.name} exceeds MySQL's ${MYSQL_ROW_RULE_MESSAGE_MAX_BYTES}-byte diagnostic limit; supply a shorter name stem`, meta: { model: context.model, rule: context.rule.name } });
|
|
3445
3481
|
if (message.includes("\\")) {
|
|
3446
3482
|
const hex = Array.from(new TextEncoder().encode(message), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3447
3483
|
return `_utf8mb4 X'${hex}'`;
|
|
@@ -3450,11 +3486,11 @@ function diagnosticLiteral({ name, context, dialect }) {
|
|
|
3450
3486
|
}
|
|
3451
3487
|
function renderMysqlRowRule({ dialect, context }) {
|
|
3452
3488
|
if (dialect.name !== "mysql")
|
|
3453
|
-
throw
|
|
3489
|
+
throw unsupportedCapability22({ dialect: dialect.name, feature: "mysqlRowRules" });
|
|
3454
3490
|
const { rule, table, triggers, protectedFields, gateField, placement } = context;
|
|
3455
3491
|
for (const name of Object.keys(placement ?? {}))
|
|
3456
3492
|
if (!triggers.some(({ identity }) => identity.name === name))
|
|
3457
|
-
throw new
|
|
3493
|
+
throw new VibeError20({ code: "VIBE_INTERNAL", message: "A MySQL trigger placement names a trigger this rule does not own", meta: { model: context.model, rule: rule.name, trigger: name } });
|
|
3458
3494
|
const position = ({ name }) => {
|
|
3459
3495
|
const entry = placement?.[name];
|
|
3460
3496
|
if (entry === undefined)
|
|
@@ -3467,7 +3503,7 @@ function renderMysqlRowRule({ dialect, context }) {
|
|
|
3467
3503
|
return `NOT (OLD.${column} <=> NEW.${column})`;
|
|
3468
3504
|
});
|
|
3469
3505
|
if (rule.kind !== "appendOnly" && terms.length === 0)
|
|
3470
|
-
throw new
|
|
3506
|
+
throw new VibeError20({ code: "VIBE_SCHEMA_INVALID", message: `Row rule ${rule.name} has no protected fields`, meta: { model: context.model, rule: rule.name } });
|
|
3471
3507
|
const condition = gateField === undefined ? terms.join(`
|
|
3472
3508
|
OR `) : `OLD.${dialect.quoteIdent({ name: gateField.dbName ?? gateField.name })} IS NOT NULL AND (
|
|
3473
3509
|
${terms.join(`
|
|
@@ -3496,10 +3532,10 @@ END;`, values: [] };
|
|
|
3496
3532
|
function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
3497
3533
|
const issue = rowRuleShapeIssue({ value: rule });
|
|
3498
3534
|
if (issue !== undefined) {
|
|
3499
|
-
throw new
|
|
3535
|
+
throw new VibeError21({ code: "VIBE_SCHEMA_INVALID", message: issue, meta: { model: model.name } });
|
|
3500
3536
|
}
|
|
3501
3537
|
if (placement !== undefined && Object.keys(placement).length > 0 && dialect.name !== "mysql")
|
|
3502
|
-
throw
|
|
3538
|
+
throw unsupportedCapability23({ dialect: dialect.name, feature: "rowRuleTriggerPlacement" });
|
|
3503
3539
|
const normalized = normalizeRowRules({ schema, model, rules: [rule], dialect: dialect.name })[0];
|
|
3504
3540
|
switch (dialect.name) {
|
|
3505
3541
|
case "postgres": {
|
|
@@ -3507,7 +3543,7 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
3507
3543
|
const functionIdentity = objects.find((object) => object.role === "function")?.identity;
|
|
3508
3544
|
const triggerIdentity = objects.find((object) => object.role === "trigger")?.identity;
|
|
3509
3545
|
if (functionIdentity?.kind !== "function" || triggerIdentity?.kind !== "trigger")
|
|
3510
|
-
throw
|
|
3546
|
+
throw internalError3({ message: "PostgreSQL row rules own one function and one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
3511
3547
|
const gate = rowRuleGateField({ rule: normalized });
|
|
3512
3548
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
3513
3549
|
return renderPostgresRowRule({
|
|
@@ -3528,15 +3564,15 @@ function renderRowRule({ schema, model, rule, dialect, placement }) {
|
|
|
3528
3564
|
const objects = rowRuleIdentities({ schema, model, rule: normalized, dialect: dialect.name });
|
|
3529
3565
|
const triggers = objects.map(({ role, identity }) => {
|
|
3530
3566
|
if (role !== "trigger" || identity.kind !== "trigger")
|
|
3531
|
-
throw
|
|
3567
|
+
throw internalError3({ message: "Event row rules own only triggers", meta: { model: model.name, rule: normalized.name } });
|
|
3532
3568
|
const event = ["update", "delete"].find((event) => identity.name === rowRuleTriggerName({ rule: normalized, event }));
|
|
3533
3569
|
if (event === undefined)
|
|
3534
|
-
throw
|
|
3570
|
+
throw internalError3({ message: "Row-rule identity has no declared event", meta: { model: model.name, rule: normalized.name, trigger: identity.name } });
|
|
3535
3571
|
return { event, identity: { kind: "trigger", table: identity.table, name: identity.name } };
|
|
3536
3572
|
});
|
|
3537
3573
|
const table = triggers[0]?.identity.table;
|
|
3538
3574
|
if (table === undefined)
|
|
3539
|
-
throw
|
|
3575
|
+
throw internalError3({ message: "Event row rules require at least one trigger", meta: { model: model.name, rule: normalized.name } });
|
|
3540
3576
|
const gate = rowRuleGateField({ rule: normalized });
|
|
3541
3577
|
const gateField = gate === undefined ? undefined : model.fields.find((field) => field.name === gate);
|
|
3542
3578
|
const context = {
|
|
@@ -3567,7 +3603,7 @@ function renderSqliteJsonEquality(params) {
|
|
|
3567
3603
|
}
|
|
3568
3604
|
|
|
3569
3605
|
// packages/sql/src/dialects/diagnostics.ts
|
|
3570
|
-
import { unsupportedCapability as
|
|
3606
|
+
import { unsupportedCapability as unsupportedCapability24 } from "@vibeorm/schema";
|
|
3571
3607
|
var QUERIES = Object.freeze({
|
|
3572
3608
|
postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
|
|
3573
3609
|
sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
|
|
@@ -3575,7 +3611,7 @@ var QUERIES = Object.freeze({
|
|
|
3575
3611
|
function renderDiagnostic(params) {
|
|
3576
3612
|
const query = QUERIES[params.query];
|
|
3577
3613
|
if (params.dialect.name !== query.dialect) {
|
|
3578
|
-
throw
|
|
3614
|
+
throw unsupportedCapability24({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
|
|
3579
3615
|
}
|
|
3580
3616
|
return { text: query.text, values: [], returning: "none" };
|
|
3581
3617
|
}
|
|
@@ -3584,7 +3620,7 @@ function isFixedDiagnostic(params) {
|
|
|
3584
3620
|
}
|
|
3585
3621
|
|
|
3586
3622
|
// packages/sql/src/dialects/postgres-diagnostics.ts
|
|
3587
|
-
import { VibeError as
|
|
3623
|
+
import { VibeError as VibeError22, unsupportedCapability as unsupportedCapability25 } from "@vibeorm/schema";
|
|
3588
3624
|
function renderPostgresExplain(params) {
|
|
3589
3625
|
const { dialect, query, options } = params;
|
|
3590
3626
|
requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
|
|
@@ -3619,7 +3655,7 @@ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
|
|
|
3619
3655
|
});
|
|
3620
3656
|
function requirePostgres(params) {
|
|
3621
3657
|
if (params.dialect.name !== "postgres") {
|
|
3622
|
-
throw
|
|
3658
|
+
throw unsupportedCapability25({
|
|
3623
3659
|
dialect: params.dialect.name,
|
|
3624
3660
|
feature: params.feature,
|
|
3625
3661
|
hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
|
|
@@ -3637,7 +3673,7 @@ function renderPostgresWorkload(params) {
|
|
|
3637
3673
|
if (query === "statementSnapshot") {
|
|
3638
3674
|
const bounded = limit ?? 20;
|
|
3639
3675
|
if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
|
|
3640
|
-
throw new
|
|
3676
|
+
throw new VibeError22({
|
|
3641
3677
|
code: "VIBE_CONFIG",
|
|
3642
3678
|
message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
|
|
3643
3679
|
meta: { limit }
|
|
@@ -3656,7 +3692,7 @@ function renderPostgresWorkload(params) {
|
|
|
3656
3692
|
function requireArg(params) {
|
|
3657
3693
|
const { value, name, query } = params;
|
|
3658
3694
|
if (value === undefined || value.length === 0) {
|
|
3659
|
-
throw new
|
|
3695
|
+
throw new VibeError22({
|
|
3660
3696
|
code: "VIBE_CONFIG",
|
|
3661
3697
|
message: `the "${query}" diagnostic read requires a ${name}`,
|
|
3662
3698
|
meta: { query, argument: name }
|
|
@@ -3666,10 +3702,10 @@ function requireArg(params) {
|
|
|
3666
3702
|
}
|
|
3667
3703
|
|
|
3668
3704
|
// packages/sql/src/dialects/postgres-rls-readiness.ts
|
|
3669
|
-
import { unsupportedCapability as
|
|
3705
|
+
import { unsupportedCapability as unsupportedCapability27 } from "@vibeorm/schema";
|
|
3670
3706
|
|
|
3671
3707
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
3672
|
-
import { RlsContextError, VibeError as
|
|
3708
|
+
import { RlsContextError, VibeError as VibeError23, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability26, validateRlsContextIR } from "@vibeorm/schema";
|
|
3673
3709
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
3674
3710
|
var COLUMN_TYPES = {
|
|
3675
3711
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -3682,18 +3718,18 @@ var COLUMN_TYPES = {
|
|
|
3682
3718
|
};
|
|
3683
3719
|
function requireRls(params) {
|
|
3684
3720
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
3685
|
-
throw
|
|
3721
|
+
throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
3686
3722
|
}
|
|
3687
3723
|
}
|
|
3688
3724
|
function identifier2(params) {
|
|
3689
3725
|
if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
|
|
3690
|
-
throw new
|
|
3726
|
+
throw new VibeError23({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
|
|
3691
3727
|
}
|
|
3692
3728
|
return params.dialect.quoteIdent({ name: params.name });
|
|
3693
3729
|
}
|
|
3694
3730
|
function tableRef(params) {
|
|
3695
3731
|
if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
|
|
3696
|
-
throw
|
|
3732
|
+
throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
|
|
3697
3733
|
}
|
|
3698
3734
|
return `${identifier2({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier2({ dialect: params.dialect, name: params.table })}`;
|
|
3699
3735
|
}
|
|
@@ -3734,7 +3770,7 @@ function renderRlsColumnType(params) {
|
|
|
3734
3770
|
if (native !== undefined)
|
|
3735
3771
|
return native;
|
|
3736
3772
|
}
|
|
3737
|
-
throw
|
|
3773
|
+
throw unsupportedCapability26({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
|
|
3738
3774
|
}
|
|
3739
3775
|
function protectedModel(params) {
|
|
3740
3776
|
requireRls(params);
|
|
@@ -3742,7 +3778,7 @@ function protectedModel(params) {
|
|
|
3742
3778
|
assertValidSchemaIR({ schema: params.schema });
|
|
3743
3779
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
3744
3780
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
3745
|
-
throw new
|
|
3781
|
+
throw new VibeError23({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
|
|
3746
3782
|
}
|
|
3747
3783
|
return model;
|
|
3748
3784
|
}
|
|
@@ -3775,7 +3811,7 @@ function predicate(params) {
|
|
|
3775
3811
|
}).join(" AND ");
|
|
3776
3812
|
return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
|
|
3777
3813
|
}
|
|
3778
|
-
throw new
|
|
3814
|
+
throw new VibeError23({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
|
|
3779
3815
|
}
|
|
3780
3816
|
function renderRlsPolicy(params) {
|
|
3781
3817
|
const model = protectedModel(params);
|
|
@@ -3816,7 +3852,7 @@ function renderRlsContext(params) {
|
|
|
3816
3852
|
}
|
|
3817
3853
|
function runtimeRoleOf(params) {
|
|
3818
3854
|
if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
|
|
3819
|
-
throw new
|
|
3855
|
+
throw new VibeError23({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
|
|
3820
3856
|
}
|
|
3821
3857
|
return params.role;
|
|
3822
3858
|
}
|
|
@@ -3973,7 +4009,7 @@ ORDER BY c.relname, p.polname`,
|
|
|
3973
4009
|
});
|
|
3974
4010
|
function requireRls2(params) {
|
|
3975
4011
|
if (!params.dialect.capabilities.nativeRowLevelSecurity) {
|
|
3976
|
-
throw
|
|
4012
|
+
throw unsupportedCapability27({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
|
|
3977
4013
|
}
|
|
3978
4014
|
}
|
|
3979
4015
|
function renderRlsReadinessQuery(params) {
|
|
@@ -4005,7 +4041,7 @@ function renderRlsPolicyPath(params) {
|
|
|
4005
4041
|
}
|
|
4006
4042
|
|
|
4007
4043
|
// packages/sql/src/render.ts
|
|
4008
|
-
import { assertSqlNamespace as assertSqlNamespace2, internalError as
|
|
4044
|
+
import { assertSqlNamespace as assertSqlNamespace2, internalError as internalError4, unsupportedCapability as unsupportedCapability28 } from "@vibeorm/schema";
|
|
4009
4045
|
|
|
4010
4046
|
// packages/sql/src/like.ts
|
|
4011
4047
|
function escapeLikePattern(params) {
|
|
@@ -4091,15 +4127,17 @@ function renderStatement(params) {
|
|
|
4091
4127
|
function quoteCol(dialect, column) {
|
|
4092
4128
|
const name = dialect.quoteIdent({ name: column.name });
|
|
4093
4129
|
const ref = column.table === undefined ? name : `${dialect.quoteIdent({ name: column.table })}.${name}`;
|
|
4130
|
+
if (column.truncate !== undefined)
|
|
4131
|
+
return renderDateTruncation({ dialect, reference: ref, truncation: column.truncate });
|
|
4094
4132
|
if (column.castReal === true) {
|
|
4095
4133
|
if (dialect.name !== "sqlite") {
|
|
4096
|
-
throw
|
|
4134
|
+
throw internalError4({ message: "ColRef.castReal is a sqlite Decimal construct" });
|
|
4097
4135
|
}
|
|
4098
4136
|
return `CAST(${ref} AS REAL)`;
|
|
4099
4137
|
}
|
|
4100
4138
|
if (column.castJsonb !== undefined) {
|
|
4101
4139
|
if (dialect.name !== "postgres") {
|
|
4102
|
-
throw
|
|
4140
|
+
throw internalError4({ message: "ColRef.castJsonb is a postgres json construct" });
|
|
4103
4141
|
}
|
|
4104
4142
|
return `${ref}::${column.castJsonb}`;
|
|
4105
4143
|
}
|
|
@@ -4107,7 +4145,7 @@ function quoteCol(dialect, column) {
|
|
|
4107
4145
|
}
|
|
4108
4146
|
function typedOrderRef(params) {
|
|
4109
4147
|
const { statement, column } = params;
|
|
4110
|
-
if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined)
|
|
4148
|
+
if (column.table !== undefined || column.castReal === true || column.castJsonb !== undefined || column.truncate !== undefined)
|
|
4111
4149
|
return column;
|
|
4112
4150
|
const renamed = statement.columns.some((projected) => (projected.alias ?? projected.name) === column.name && (projected.transform !== undefined || projected.alias !== undefined));
|
|
4113
4151
|
return renamed ? { ...column, table: statement.table.alias ?? statement.table.name } : column;
|
|
@@ -4116,16 +4154,17 @@ function jsonCellCast(dialect, cell) {
|
|
|
4116
4154
|
if (cell.storage !== "json" || dialect.jsonParamCast === "")
|
|
4117
4155
|
return dialect.jsonParamCast;
|
|
4118
4156
|
if (dialect.name !== "postgres") {
|
|
4119
|
-
throw
|
|
4157
|
+
throw internalError4({ message: "JsonParamCell storage json is a postgres construct" });
|
|
4120
4158
|
}
|
|
4121
4159
|
return "::text::json";
|
|
4122
4160
|
}
|
|
4123
4161
|
function quoteProjection(dialect, column) {
|
|
4124
4162
|
if (column.transform === undefined) {
|
|
4125
4163
|
const base = quoteCol(dialect, column);
|
|
4126
|
-
|
|
4164
|
+
const alias = column.alias ?? (column.truncate === undefined ? undefined : column.name);
|
|
4165
|
+
if (alias === undefined)
|
|
4127
4166
|
return base;
|
|
4128
|
-
return `${base} AS ${dialect.quoteIdent({ name:
|
|
4167
|
+
return `${base} AS ${dialect.quoteIdent({ name: alias })}`;
|
|
4129
4168
|
}
|
|
4130
4169
|
if (column.transform === "jsonText") {
|
|
4131
4170
|
const ref = quoteCol(dialect, column);
|
|
@@ -4139,7 +4178,7 @@ function quoteProjection(dialect, column) {
|
|
|
4139
4178
|
if (column.transform === "encodeBase64" && dialect.name === "mysql")
|
|
4140
4179
|
return `TO_BASE64(${ref}) AS ${alias}`;
|
|
4141
4180
|
if (dialect.name !== "postgres") {
|
|
4142
|
-
throw
|
|
4181
|
+
throw internalError4({ message: `${column.transform} is a postgres construct` });
|
|
4143
4182
|
}
|
|
4144
4183
|
if (column.transform === "encodeArrayBase64")
|
|
4145
4184
|
return `encode(array_send(${ref}), 'base64') AS ${alias}`;
|
|
@@ -4163,7 +4202,7 @@ function renderTable(dialect, table) {
|
|
|
4163
4202
|
}
|
|
4164
4203
|
function assertCount(value, what) {
|
|
4165
4204
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4166
|
-
throw
|
|
4205
|
+
throw internalError4({ message: `${what} must be a non-negative integer, got ${value}` });
|
|
4167
4206
|
}
|
|
4168
4207
|
}
|
|
4169
4208
|
var COMPARISON_SQL2 = {
|
|
@@ -4195,7 +4234,7 @@ function renderAggRef(dialect, agg) {
|
|
|
4195
4234
|
function assertScalarArrays(dialect, feature) {
|
|
4196
4235
|
if (dialect.capabilities.scalarArrays)
|
|
4197
4236
|
return;
|
|
4198
|
-
throw
|
|
4237
|
+
throw unsupportedCapability28({
|
|
4199
4238
|
dialect: dialect.name,
|
|
4200
4239
|
feature,
|
|
4201
4240
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -4207,7 +4246,7 @@ function renderExpr(params) {
|
|
|
4207
4246
|
case "cmp": {
|
|
4208
4247
|
const op = COMPARISON_SQL2[expr.op];
|
|
4209
4248
|
if (op === undefined) {
|
|
4210
|
-
throw
|
|
4249
|
+
throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
|
|
4211
4250
|
}
|
|
4212
4251
|
if (isDbNowMarker(expr.value)) {
|
|
4213
4252
|
return `${quoteCol(dialect, expr.column)} ${op} ${dialect.renderDbNow({ offsetSeconds: expr.value.offsetSeconds, collector })}`;
|
|
@@ -4218,7 +4257,7 @@ function renderExpr(params) {
|
|
|
4218
4257
|
case "cmpCol": {
|
|
4219
4258
|
const op = COMPARISON_SQL2[expr.op];
|
|
4220
4259
|
if (op === undefined) {
|
|
4221
|
-
throw
|
|
4260
|
+
throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
|
|
4222
4261
|
}
|
|
4223
4262
|
return `${quoteCol(dialect, expr.left)} ${op} ${quoteCol(dialect, expr.right)}`;
|
|
4224
4263
|
}
|
|
@@ -4242,11 +4281,11 @@ function renderExpr(params) {
|
|
|
4242
4281
|
}
|
|
4243
4282
|
case "tupleIn": {
|
|
4244
4283
|
if (expr.columns.length === 0) {
|
|
4245
|
-
throw
|
|
4284
|
+
throw internalError4({ message: "tupleIn needs at least one column" });
|
|
4246
4285
|
}
|
|
4247
4286
|
for (const row of expr.rows) {
|
|
4248
4287
|
if (row.length !== expr.columns.length) {
|
|
4249
|
-
throw
|
|
4288
|
+
throw internalError4({
|
|
4250
4289
|
message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
|
|
4251
4290
|
});
|
|
4252
4291
|
}
|
|
@@ -4272,7 +4311,7 @@ function renderExpr(params) {
|
|
|
4272
4311
|
case "rowCompare": {
|
|
4273
4312
|
const op = COMPARISON_SQL2[expr.op];
|
|
4274
4313
|
if (op === undefined || expr.columns.length === 0 || expr.values.length !== expr.columns.length) {
|
|
4275
|
-
throw
|
|
4314
|
+
throw internalError4({ message: `rowCompare needs a range operator and one value per column (${expr.columns.length} columns, ${expr.values.length} values)` });
|
|
4276
4315
|
}
|
|
4277
4316
|
const columns = expr.columns.map((column) => quoteCol(dialect, column));
|
|
4278
4317
|
const placeholders = expr.values.map((value) => collector.add(value));
|
|
@@ -4280,9 +4319,9 @@ function renderExpr(params) {
|
|
|
4280
4319
|
}
|
|
4281
4320
|
case "inSelect": {
|
|
4282
4321
|
if (expr.columns.length === 0)
|
|
4283
|
-
throw
|
|
4322
|
+
throw internalError4({ message: "inSelect needs at least one column" });
|
|
4284
4323
|
if (dialect.name === "mysql" && (expr.select.limit !== undefined || expr.select.offset !== undefined)) {
|
|
4285
|
-
throw
|
|
4324
|
+
throw unsupportedCapability28({
|
|
4286
4325
|
dialect: dialect.name,
|
|
4287
4326
|
feature: "LIMIT inside an IN subquery",
|
|
4288
4327
|
hint: "mysql refuses LIMIT directly inside an IN subquery (error 1235); wrap the limited select in a derived table"
|
|
@@ -4353,7 +4392,7 @@ function renderExpr(params) {
|
|
|
4353
4392
|
case "aggCmp": {
|
|
4354
4393
|
const op = COMPARISON_SQL2[expr.op];
|
|
4355
4394
|
if (op === undefined) {
|
|
4356
|
-
throw
|
|
4395
|
+
throw internalError4({ message: `unknown comparison operator "${expr.op}"` });
|
|
4357
4396
|
}
|
|
4358
4397
|
return `${renderAggRef(dialect, expr.agg)} ${op} ${collector.add(expr.value)}`;
|
|
4359
4398
|
}
|
|
@@ -4435,13 +4474,13 @@ function renderKeyList(params) {
|
|
|
4435
4474
|
const { dialect, expr, collector } = params;
|
|
4436
4475
|
const arity = expr.columns.length;
|
|
4437
4476
|
if (arity === 0)
|
|
4438
|
-
throw
|
|
4477
|
+
throw internalError4({ message: "keyList needs at least one column" });
|
|
4439
4478
|
if (expr.members.length !== arity) {
|
|
4440
|
-
throw
|
|
4479
|
+
throw internalError4({ message: `keyList has ${expr.members.length} members for ${arity} columns` });
|
|
4441
4480
|
}
|
|
4442
4481
|
for (const row of expr.rows) {
|
|
4443
4482
|
if (row.length !== arity)
|
|
4444
|
-
throw
|
|
4483
|
+
throw internalError4({ message: `keyList row has ${row.length} values for ${arity} columns` });
|
|
4445
4484
|
}
|
|
4446
4485
|
if (expr.rows.length === 0)
|
|
4447
4486
|
return "1 = 0";
|
|
@@ -4473,25 +4512,25 @@ function renderKeyPage(params) {
|
|
|
4473
4512
|
const { select } = page;
|
|
4474
4513
|
const arity = page.keyColumns.length;
|
|
4475
4514
|
if (arity === 0 || page.members.length !== arity) {
|
|
4476
|
-
throw
|
|
4515
|
+
throw internalError4({ message: `keyPage has ${page.members.length} members for ${arity} key columns` });
|
|
4477
4516
|
}
|
|
4478
4517
|
if (page.rows.length === 0)
|
|
4479
|
-
throw
|
|
4518
|
+
throw internalError4({ message: "keyPage needs at least one key" });
|
|
4480
4519
|
for (const row of page.rows) {
|
|
4481
4520
|
if (row.length !== arity)
|
|
4482
|
-
throw
|
|
4521
|
+
throw internalError4({ message: `keyPage row has ${row.length} values for ${arity} key columns` });
|
|
4483
4522
|
}
|
|
4484
4523
|
if (page.identity.length === 0)
|
|
4485
|
-
throw
|
|
4524
|
+
throw internalError4({ message: "keyPage needs the child's identity columns" });
|
|
4486
4525
|
const order = select.orderBy ?? [];
|
|
4487
4526
|
if (order.length === 0 || order.some((term) => !("column" in term))) {
|
|
4488
|
-
throw
|
|
4527
|
+
throw internalError4({ message: "keyPage needs a non-empty order of plain column terms" });
|
|
4489
4528
|
}
|
|
4490
4529
|
if (select.table.alias !== undefined || Object.keys(select).some((member) => !KEY_PAGE_SELECT_MEMBERS.has(member))) {
|
|
4491
|
-
throw
|
|
4530
|
+
throw internalError4({ message: "keyPage select carries a member the per-key page cannot render" });
|
|
4492
4531
|
}
|
|
4493
4532
|
if (dialect.keyPageStrategy === "window") {
|
|
4494
|
-
throw
|
|
4533
|
+
throw unsupportedCapability28({
|
|
4495
4534
|
dialect: dialect.name,
|
|
4496
4535
|
feature: "a per-key page (keyPage)",
|
|
4497
4536
|
hint: "the runtime keeps the ROW_NUMBER window on this dialect"
|
|
@@ -4519,7 +4558,7 @@ function renderKeyPage(params) {
|
|
|
4519
4558
|
const keys = dialect.quoteIdent({ name: keyAlias });
|
|
4520
4559
|
if (dialect.keyPageStrategy === "lateral") {
|
|
4521
4560
|
if (keyListBindsUntypedRows({ members: page.members })) {
|
|
4522
|
-
throw
|
|
4561
|
+
throw unsupportedCapability28({
|
|
4523
4562
|
dialect: dialect.name,
|
|
4524
4563
|
feature: "a per-key page over a DateTime key without a declared native type",
|
|
4525
4564
|
hint: "the runtime keeps the ROW_NUMBER window for such keys"
|
|
@@ -4547,22 +4586,22 @@ function renderKeyPage(params) {
|
|
|
4547
4586
|
function renderKeyRows(params) {
|
|
4548
4587
|
const { dialect, statement, keys, collector } = params;
|
|
4549
4588
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.fromUnion !== undefined || statement.table.alias !== undefined) {
|
|
4550
|
-
throw
|
|
4589
|
+
throw internalError4({ message: "fromKeys uses table.name as the derived-table alias \u2014 fromSelect, keyPage, fromUnion and table.alias must be unset" });
|
|
4551
4590
|
}
|
|
4552
4591
|
if (statement.rowLock !== undefined)
|
|
4553
|
-
throw
|
|
4592
|
+
throw internalError4({ message: "a locking read cannot read from key rows" });
|
|
4554
4593
|
const arity = keys.columns.length;
|
|
4555
4594
|
if (arity === 0 || keys.members.length !== arity) {
|
|
4556
|
-
throw
|
|
4595
|
+
throw internalError4({ message: `fromKeys has ${keys.members.length} members for ${arity} columns` });
|
|
4557
4596
|
}
|
|
4558
4597
|
if (keys.rows.length === 0)
|
|
4559
|
-
throw
|
|
4598
|
+
throw internalError4({ message: "fromKeys needs at least one key row" });
|
|
4560
4599
|
for (const row of keys.rows) {
|
|
4561
4600
|
if (row.length !== arity)
|
|
4562
|
-
throw
|
|
4601
|
+
throw internalError4({ message: `fromKeys row has ${row.length} values for ${arity} columns` });
|
|
4563
4602
|
}
|
|
4564
4603
|
if (dialect.keyListStrategy !== "unnest" || keyListBindsUntypedRows({ members: keys.members })) {
|
|
4565
|
-
throw
|
|
4604
|
+
throw unsupportedCapability28({
|
|
4566
4605
|
dialect: dialect.name,
|
|
4567
4606
|
feature: dialect.keyListStrategy !== "unnest" ? "a key-rows FROM (fromKeys)" : "a key-rows FROM over a DateTime key without a declared native type",
|
|
4568
4607
|
hint: "the runtime keeps the per-relation loads for this write"
|
|
@@ -4578,20 +4617,20 @@ function renderUnionPage(params) {
|
|
|
4578
4617
|
const { dialect, statement, collector } = params;
|
|
4579
4618
|
const branches = statement.fromUnion ?? [];
|
|
4580
4619
|
if (branches.length < 2)
|
|
4581
|
-
throw
|
|
4620
|
+
throw internalError4({ message: "fromUnion needs at least two branches" });
|
|
4582
4621
|
if (statement.fromSelect !== undefined || statement.keyPage !== undefined || statement.table.alias !== undefined) {
|
|
4583
|
-
throw
|
|
4622
|
+
throw internalError4({ message: "fromUnion uses table.name as the derived-table alias \u2014 fromSelect, keyPage and table.alias must be unset" });
|
|
4584
4623
|
}
|
|
4585
4624
|
if (statement.rowLock !== undefined)
|
|
4586
|
-
throw
|
|
4625
|
+
throw internalError4({ message: "a locking read cannot read from a union page" });
|
|
4587
4626
|
const outputNames = (branch) => branch.columns.map((column) => column.alias ?? column.name).join("\x00");
|
|
4588
4627
|
const names = outputNames(branches[0]);
|
|
4589
4628
|
return branches.map((branch, index) => {
|
|
4590
4629
|
if (branch.columns.length === 0 || Object.keys(branch).some((member) => !UNION_BRANCH_MEMBERS.has(member))) {
|
|
4591
|
-
throw
|
|
4630
|
+
throw internalError4({ message: "a fromUnion branch must be a plain SELECT <columns> FROM <table> [WHERE] [ORDER BY] [LIMIT]" });
|
|
4592
4631
|
}
|
|
4593
4632
|
if (outputNames(branch) !== names)
|
|
4594
|
-
throw
|
|
4633
|
+
throw internalError4({ message: "fromUnion branches must project the same output names in the same order" });
|
|
4595
4634
|
const inner = renderSelect({ dialect, statement: branch, collector });
|
|
4596
4635
|
return `SELECT * FROM (${inner.text}) AS ${dialect.quoteIdent({ name: `__vibe_u${index}` })}`;
|
|
4597
4636
|
}).join(" UNION ALL ");
|
|
@@ -4599,7 +4638,7 @@ function renderUnionPage(params) {
|
|
|
4599
4638
|
function jsonStringPattern(params) {
|
|
4600
4639
|
const { expr } = params;
|
|
4601
4640
|
if (typeof expr.value !== "string") {
|
|
4602
|
-
throw
|
|
4641
|
+
throw internalError4({ message: `json ${expr.op} expects a string operand` });
|
|
4603
4642
|
}
|
|
4604
4643
|
if (expr.op === "string_contains")
|
|
4605
4644
|
return containsPattern({ raw: expr.value });
|
|
@@ -4623,7 +4662,7 @@ function renderJsonLike(params) {
|
|
|
4623
4662
|
function renderJsonFilter(params) {
|
|
4624
4663
|
const { dialect, expr, collector } = params;
|
|
4625
4664
|
if (!dialect.supportedJsonOps.includes(expr.op)) {
|
|
4626
|
-
throw
|
|
4665
|
+
throw unsupportedCapability28({
|
|
4627
4666
|
dialect: dialect.name,
|
|
4628
4667
|
feature: `the json filter "${expr.op}"`,
|
|
4629
4668
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -4690,7 +4729,7 @@ function renderJsonFilter(params) {
|
|
|
4690
4729
|
const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
|
|
4691
4730
|
const tests = elements.map((element) => {
|
|
4692
4731
|
if (element === null || typeof element === "object" && element !== null) {
|
|
4693
|
-
throw
|
|
4732
|
+
throw unsupportedCapability28({
|
|
4694
4733
|
dialect: dialect.name,
|
|
4695
4734
|
feature: "json array_contains with object/array/null elements",
|
|
4696
4735
|
hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
|
|
@@ -4704,7 +4743,7 @@ function renderJsonFilter(params) {
|
|
|
4704
4743
|
return tests.length === 0 ? guard : `(${[guard, ...tests].join(" AND ")})`;
|
|
4705
4744
|
}
|
|
4706
4745
|
default:
|
|
4707
|
-
throw
|
|
4746
|
+
throw internalError4({ message: `json op "${expr.op}" leaked past supportedJsonOps on sqlite` });
|
|
4708
4747
|
}
|
|
4709
4748
|
}
|
|
4710
4749
|
const column = quoteCol(dialect, expr.column);
|
|
@@ -4762,11 +4801,11 @@ function renderPositionalRow(params) {
|
|
|
4762
4801
|
const position = join.select.rowNumber;
|
|
4763
4802
|
const positioned = position !== undefined && json.order === "positionSlot" && join.agg === "jsonArray" && position.partitionBy.length === 0 && position.orderBy.length > 0;
|
|
4764
4803
|
if (join.select.columns.length === 0 || join.select.selectExpr !== undefined || (join.select.aggregates?.length ?? 0) > 0 || position !== undefined && !positioned) {
|
|
4765
|
-
throw
|
|
4804
|
+
throw internalError4({ message: "positional lateral rows require an explicit column-only projection" });
|
|
4766
4805
|
}
|
|
4767
4806
|
const names = [...join.select.columns.map((column) => projectionOutputName({ column })), ...positioned ? [position.alias] : []];
|
|
4768
4807
|
if (new Set(names).size !== names.length) {
|
|
4769
|
-
throw
|
|
4808
|
+
throw internalError4({ message: "positional lateral projection has duplicate output names" });
|
|
4770
4809
|
}
|
|
4771
4810
|
const values = names.map((name) => `${subAlias}.${dialect.quoteIdent({ name })}`);
|
|
4772
4811
|
const chunks = [];
|
|
@@ -4774,14 +4813,14 @@ function renderPositionalRow(params) {
|
|
|
4774
4813
|
chunks.push(`${json.rowArray}(${values.slice(index, index + JSON_TRANSPORT.chunkSize).join(", ")})`);
|
|
4775
4814
|
}
|
|
4776
4815
|
if (chunks.length > JSON_TRANSPORT.chunkSize) {
|
|
4777
|
-
throw
|
|
4816
|
+
throw internalError4({ message: "positional lateral projection exceeds the transport width limit" });
|
|
4778
4817
|
}
|
|
4779
4818
|
return chunks.length === 1 ? chunks[0] : `${json.rowArray}(${chunks.join(", ")})`;
|
|
4780
4819
|
}
|
|
4781
4820
|
function lateralJsonOf(params) {
|
|
4782
4821
|
const json = params.dialect.lateralJson;
|
|
4783
4822
|
if (json === null)
|
|
4784
|
-
throw
|
|
4823
|
+
throw internalError4({ message: "lateral JSON rendering on a dialect without LATERAL" });
|
|
4785
4824
|
return json;
|
|
4786
4825
|
}
|
|
4787
4826
|
function renderLateralJoin(params) {
|
|
@@ -4789,7 +4828,7 @@ function renderLateralJoin(params) {
|
|
|
4789
4828
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
4790
4829
|
if (join.agg === "count") {
|
|
4791
4830
|
if (join.rowFormat === "positional" || join.select.columns.length !== 0 || join.select.selectExpr?.kind !== "countAll" || join.select.selectExpr.alias !== join.columnAlias || join.select.groupBy !== undefined || join.select.having !== undefined || join.select.limit !== undefined || join.select.offset !== undefined || join.select.distinctOn !== undefined || join.select.distinct !== undefined) {
|
|
4792
|
-
throw
|
|
4831
|
+
throw internalError4({ message: "count lateral must return one ungrouped COUNT(*) under its column alias" });
|
|
4793
4832
|
}
|
|
4794
4833
|
return `LEFT JOIN LATERAL (${inner.text}) AS ${dialect.quoteIdent({ name: join.alias })} ON true`;
|
|
4795
4834
|
}
|
|
@@ -4814,7 +4853,7 @@ function renderLateralJoin(params) {
|
|
|
4814
4853
|
}
|
|
4815
4854
|
}
|
|
4816
4855
|
if (join.rowFormat !== "positional" && json.order !== "inAggregate") {
|
|
4817
|
-
throw
|
|
4856
|
+
throw unsupportedCapability28({ dialect: dialect.name, feature: "object-format lateral rows", hint: 'lateral relation rows render positional (rowFormat "positional") on this dialect' });
|
|
4818
4857
|
}
|
|
4819
4858
|
const rowJson = join.rowFormat === "positional" ? renderPositionalRow({ dialect, join, subAlias }) : `to_json(${subAlias}.*)`;
|
|
4820
4859
|
const projection = join.agg === "jsonArray" ? `COALESCE(${json.arrayAgg}(${rowJson}${aggOrder}), ${json.emptyArray}) AS ${columnAlias}` : `${rowJson} AS ${columnAlias}`;
|
|
@@ -4825,7 +4864,7 @@ function renderCountSubquery(params) {
|
|
|
4825
4864
|
const { dialect, subquery, collector } = params;
|
|
4826
4865
|
const select = subquery.select;
|
|
4827
4866
|
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.distinct !== undefined || select.rowLock !== undefined || select.countSubqueries !== undefined) {
|
|
4828
|
-
throw
|
|
4867
|
+
throw internalError4({ message: "count subquery must be one ungrouped COUNT(*)" });
|
|
4829
4868
|
}
|
|
4830
4869
|
const inner = renderSelect({ dialect, statement: select, collector });
|
|
4831
4870
|
return `(${inner.text}) AS ${dialect.quoteIdent({ name: subquery.alias })}`;
|
|
@@ -4855,27 +4894,27 @@ function renderKeyTableJoin(params) {
|
|
|
4855
4894
|
const postgresMember = (member) => member.kind === "citext" || member.kind === "text" || member.kind === "integer";
|
|
4856
4895
|
const admitted = dialect.name === "mysql" ? join.members.every((member) => member.kind !== "citext" && member.kind !== "text") : dialect.name === "postgres" && join.members.every(postgresMember);
|
|
4857
4896
|
if (!admitted) {
|
|
4858
|
-
throw
|
|
4897
|
+
throw unsupportedCapability28({
|
|
4859
4898
|
dialect: dialect.name,
|
|
4860
4899
|
feature: "relation key table",
|
|
4861
4900
|
hint: "the key-table join is mysql's relation association \u2014 this dialect compares relation keys by value"
|
|
4862
4901
|
});
|
|
4863
4902
|
}
|
|
4864
4903
|
if (join.columns.length === 0 || join.columns.length !== join.members.length) {
|
|
4865
|
-
throw
|
|
4904
|
+
throw internalError4({ message: `key table needs one member per column, got ${join.members.length} for ${join.columns.length}` });
|
|
4866
4905
|
}
|
|
4867
4906
|
if (join.rows.length === 0)
|
|
4868
|
-
throw
|
|
4907
|
+
throw internalError4({ message: "key table needs at least one key row" });
|
|
4869
4908
|
for (const member of join.members) {
|
|
4870
4909
|
if (member.kind === "decimal" && !(Number.isInteger(member.scale) && member.scale >= 0 && member.scale <= 30)) {
|
|
4871
|
-
throw
|
|
4910
|
+
throw internalError4({ message: `key table decimal scale must be an integer 0-30, got ${String(member.scale)}` });
|
|
4872
4911
|
}
|
|
4873
4912
|
}
|
|
4874
4913
|
const alias = dialect.quoteIdent({ name: join.alias });
|
|
4875
4914
|
const memberNames = join.members.map((_, index) => dialect.quoteIdent({ name: `__vibe_k${index}` }));
|
|
4876
4915
|
const rows = join.rows.map((row, ordinal) => {
|
|
4877
4916
|
if (row.length !== join.members.length) {
|
|
4878
|
-
throw
|
|
4917
|
+
throw internalError4({ message: `key table row has ${row.length} values for ${join.members.length} members` });
|
|
4879
4918
|
}
|
|
4880
4919
|
const cells = row.map((value, index) => {
|
|
4881
4920
|
const placeholder = collector.add(value);
|
|
@@ -4905,10 +4944,10 @@ function renderKeyTableJoin(params) {
|
|
|
4905
4944
|
function renderLeftJoin(params) {
|
|
4906
4945
|
const { dialect, join, collector } = params;
|
|
4907
4946
|
if (join.on.length === 0) {
|
|
4908
|
-
throw
|
|
4947
|
+
throw internalError4({ message: "left join needs at least one ON pair" });
|
|
4909
4948
|
}
|
|
4910
4949
|
if (join.select.columns.length === 0 || join.select.columns.some((column) => column.alias === undefined)) {
|
|
4911
|
-
throw
|
|
4950
|
+
throw internalError4({ message: "a left join's derived select must alias every projected column" });
|
|
4912
4951
|
}
|
|
4913
4952
|
const inner = renderSelect({ dialect, statement: join.select, collector });
|
|
4914
4953
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
@@ -4917,10 +4956,10 @@ function renderLeftJoin(params) {
|
|
|
4917
4956
|
function renderSelect(params) {
|
|
4918
4957
|
const { dialect, statement, collector } = params;
|
|
4919
4958
|
if (statement.selectExpr !== undefined && statement.columns.length > 0 && statement.selectExpr.kind !== "countAll") {
|
|
4920
|
-
throw
|
|
4959
|
+
throw internalError4({ message: "only countAll can combine with a column list" });
|
|
4921
4960
|
}
|
|
4922
4961
|
if (statement.selectExpr !== undefined && statement.aggregates !== undefined && statement.aggregates.length > 0) {
|
|
4923
|
-
throw
|
|
4962
|
+
throw internalError4({ message: "selectExpr and aggregates cannot combine" });
|
|
4924
4963
|
}
|
|
4925
4964
|
const columnList = statement.columns.map((column) => quoteProjection(dialect, column));
|
|
4926
4965
|
if (statement.selectExpr !== undefined) {
|
|
@@ -4941,7 +4980,7 @@ function renderSelect(params) {
|
|
|
4941
4980
|
if (statement.rowNumber !== undefined) {
|
|
4942
4981
|
const rowNumber = statement.rowNumber;
|
|
4943
4982
|
if (rowNumber.partitionBy.length === 0 && rowNumber.orderBy.length === 0) {
|
|
4944
|
-
throw
|
|
4983
|
+
throw internalError4({ message: "rowNumber needs a partition column or an order" });
|
|
4945
4984
|
}
|
|
4946
4985
|
const partition = rowNumber.partitionBy.length === 0 ? "" : `PARTITION BY ${rowNumber.partitionBy.map((column) => quoteCol(dialect, column)).join(", ")}`;
|
|
4947
4986
|
const order = rowNumber.orderBy.length === 0 ? "" : `${partition === "" ? "" : " "}ORDER BY ${rowNumber.orderBy.map((term) => renderOrderTerm({ dialect, term, collector })).join(", ")}`;
|
|
@@ -4949,11 +4988,11 @@ function renderSelect(params) {
|
|
|
4949
4988
|
}
|
|
4950
4989
|
let distinctPrefix = statement.distinct === true ? "DISTINCT " : "";
|
|
4951
4990
|
if (statement.distinct === true && statement.distinctOn !== undefined) {
|
|
4952
|
-
throw
|
|
4991
|
+
throw internalError4({ message: "distinct and distinctOn cannot combine" });
|
|
4953
4992
|
}
|
|
4954
4993
|
if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
|
|
4955
4994
|
if (!dialect.capabilities.distinctOn) {
|
|
4956
|
-
throw
|
|
4995
|
+
throw unsupportedCapability28({
|
|
4957
4996
|
dialect: dialect.name,
|
|
4958
4997
|
feature: "SELECT DISTINCT ON",
|
|
4959
4998
|
hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
|
|
@@ -4969,12 +5008,12 @@ function renderSelect(params) {
|
|
|
4969
5008
|
from = `(${renderUnionPage({ dialect, statement, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
4970
5009
|
} else if (statement.keyPage !== undefined) {
|
|
4971
5010
|
if (statement.fromSelect !== undefined || statement.table.alias !== undefined) {
|
|
4972
|
-
throw
|
|
5011
|
+
throw internalError4({ message: "keyPage uses table.name as the derived-table alias \u2014 fromSelect and table.alias must be unset" });
|
|
4973
5012
|
}
|
|
4974
5013
|
from = `(${renderKeyPage({ dialect, page: statement.keyPage, collector })}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
4975
5014
|
} else if (statement.fromSelect !== undefined) {
|
|
4976
5015
|
if (statement.table.alias !== undefined) {
|
|
4977
|
-
throw
|
|
5016
|
+
throw internalError4({ message: "fromSelect uses table.name as the subquery alias \u2014 table.alias must be unset" });
|
|
4978
5017
|
}
|
|
4979
5018
|
const inner = renderSelect({ dialect, statement: statement.fromSelect, collector });
|
|
4980
5019
|
from = `(${inner.text}) AS ${dialect.quoteIdent({ name: statement.table.name })}`;
|
|
@@ -4985,7 +5024,7 @@ function renderSelect(params) {
|
|
|
4985
5024
|
if (statement.innerJoins !== undefined && statement.innerJoins.length > 0) {
|
|
4986
5025
|
for (const join of statement.innerJoins) {
|
|
4987
5026
|
if (join.on.length === 0) {
|
|
4988
|
-
throw
|
|
5027
|
+
throw internalError4({ message: "inner join needs at least one ON pair" });
|
|
4989
5028
|
}
|
|
4990
5029
|
const on = join.on.map((pair) => `${quoteCol(dialect, pair.left)} = ${quoteCol(dialect, pair.right)}`).join(" AND ");
|
|
4991
5030
|
text += ` INNER JOIN ${renderTable(dialect, join.table)} ON ${on}`;
|
|
@@ -4996,7 +5035,7 @@ function renderSelect(params) {
|
|
|
4996
5035
|
}
|
|
4997
5036
|
if (statement.leftJoins !== undefined && statement.leftJoins.length > 0) {
|
|
4998
5037
|
if (statement.rowLock !== undefined) {
|
|
4999
|
-
throw
|
|
5038
|
+
throw internalError4({ message: "a locking read cannot carry a left join \u2014 postgres refuses FOR UPDATE on the nullable side of an outer join" });
|
|
5000
5039
|
}
|
|
5001
5040
|
for (const join of statement.leftJoins) {
|
|
5002
5041
|
text += ` ${renderLeftJoin({ dialect, join, collector })}`;
|
|
@@ -5004,7 +5043,7 @@ function renderSelect(params) {
|
|
|
5004
5043
|
}
|
|
5005
5044
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
5006
5045
|
if (!dialect.capabilities.lateralJoin || dialect.lateralJson === null) {
|
|
5007
|
-
throw
|
|
5046
|
+
throw unsupportedCapability28({
|
|
5008
5047
|
dialect: dialect.name,
|
|
5009
5048
|
feature: "LATERAL JOIN relation loading",
|
|
5010
5049
|
hint: 'use the portable "query" relation strategy on this dialect'
|
|
@@ -5053,7 +5092,7 @@ function unnestElement(params) {
|
|
|
5053
5092
|
return null;
|
|
5054
5093
|
if (isJsonParamCell(value)) {
|
|
5055
5094
|
if (jsonStorage === undefined || (value.storage ?? "jsonb") !== jsonStorage) {
|
|
5056
|
-
throw
|
|
5095
|
+
throw internalError4({ message: `unnest insert column "${column}" got a Json cell its declared type does not take` });
|
|
5057
5096
|
}
|
|
5058
5097
|
return value.text;
|
|
5059
5098
|
}
|
|
@@ -5069,24 +5108,24 @@ function unnestElement(params) {
|
|
|
5069
5108
|
return value;
|
|
5070
5109
|
}
|
|
5071
5110
|
}
|
|
5072
|
-
throw
|
|
5111
|
+
throw internalError4({ message: `unnest insert column "${column}" got a cell that is not a plain value (DEFAULT, the database clock, a document) \u2014 such rows keep VALUES` });
|
|
5073
5112
|
}
|
|
5074
5113
|
function renderUnnestRows(params) {
|
|
5075
5114
|
const { dialect, statement, members, collector } = params;
|
|
5076
5115
|
if (dialect.bulkInsertForm !== "unnest") {
|
|
5077
|
-
throw
|
|
5116
|
+
throw unsupportedCapability28({
|
|
5078
5117
|
dialect: dialect.name,
|
|
5079
5118
|
feature: "a bulk insert bound as typed arrays (INSERT \u2026 SELECT FROM unnest)",
|
|
5080
5119
|
hint: "this dialect binds createMany rows as VALUES"
|
|
5081
5120
|
});
|
|
5082
5121
|
}
|
|
5083
5122
|
if (statement.columns.length === 0)
|
|
5084
|
-
throw
|
|
5123
|
+
throw internalError4({ message: "an unnest insert needs at least one column" });
|
|
5085
5124
|
if (members.length !== statement.columns.length) {
|
|
5086
|
-
throw
|
|
5125
|
+
throw internalError4({ message: `unnest insert has ${members.length} column types for ${statement.columns.length} columns` });
|
|
5087
5126
|
}
|
|
5088
5127
|
if (statement.onConflict !== undefined && !("doNothing" in statement.onConflict)) {
|
|
5089
|
-
throw
|
|
5128
|
+
throw internalError4({ message: "an unnest insert takes no DO UPDATE arm \u2014 upserts bind VALUES" });
|
|
5090
5129
|
}
|
|
5091
5130
|
const aliases = statement.columns.map((_column, index) => dialect.quoteIdent({ name: `c${index + 1}` }));
|
|
5092
5131
|
const arrays = members.map((member, index) => {
|
|
@@ -5102,14 +5141,14 @@ function renderUnnestRows(params) {
|
|
|
5102
5141
|
function renderInsert(params) {
|
|
5103
5142
|
const { dialect, statement, collector } = params;
|
|
5104
5143
|
if (statement.rows.length === 0) {
|
|
5105
|
-
throw
|
|
5144
|
+
throw internalError4({ message: "insert requires at least one row" });
|
|
5106
5145
|
}
|
|
5107
5146
|
const defaultValues = statement.columns.length === 0 && dialect.emptyInsert.form === "defaultValues";
|
|
5108
5147
|
if (defaultValues && statement.rows.length !== 1) {
|
|
5109
|
-
throw
|
|
5148
|
+
throw internalError4({ message: `${dialect.name} inserts an all-default row as DEFAULT VALUES, one row per statement` });
|
|
5110
5149
|
}
|
|
5111
5150
|
if (defaultValues && statement.onConflict !== undefined && !(dialect.emptyInsert.form === "defaultValues" && dialect.emptyInsert.conflictClause)) {
|
|
5112
|
-
throw
|
|
5151
|
+
throw unsupportedCapability28({
|
|
5113
5152
|
dialect: dialect.name,
|
|
5114
5153
|
feature: "a conflict clause on an all-default row (DEFAULT VALUES)",
|
|
5115
5154
|
hint: "supply at least one column value, or drop the conflict clause"
|
|
@@ -5117,7 +5156,7 @@ function renderInsert(params) {
|
|
|
5117
5156
|
}
|
|
5118
5157
|
for (const row of statement.rows) {
|
|
5119
5158
|
if (row.length !== statement.columns.length) {
|
|
5120
|
-
throw
|
|
5159
|
+
throw internalError4({
|
|
5121
5160
|
message: `insert row has ${row.length} values but ${statement.columns.length} columns`
|
|
5122
5161
|
});
|
|
5123
5162
|
}
|
|
@@ -5135,7 +5174,7 @@ function renderInsert(params) {
|
|
|
5135
5174
|
if (!isSqlDefaultCell(value))
|
|
5136
5175
|
return collector.add(value);
|
|
5137
5176
|
if (!dialect.rowDefaultKeyword) {
|
|
5138
|
-
throw
|
|
5177
|
+
throw internalError4({
|
|
5139
5178
|
message: `${dialect.name} cannot render a per-row DEFAULT cell \u2014 rows must be grouped by column set`
|
|
5140
5179
|
});
|
|
5141
5180
|
}
|
|
@@ -5151,7 +5190,7 @@ function renderInsert(params) {
|
|
|
5151
5190
|
let target = "";
|
|
5152
5191
|
if (onConflict.constraint !== undefined) {
|
|
5153
5192
|
if (dialect.name !== "postgres") {
|
|
5154
|
-
throw
|
|
5193
|
+
throw unsupportedCapability28({
|
|
5155
5194
|
dialect: dialect.name,
|
|
5156
5195
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
5157
5196
|
hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
|
|
@@ -5167,7 +5206,7 @@ function renderInsert(params) {
|
|
|
5167
5206
|
text += ` ON CONFLICT${target} DO NOTHING`;
|
|
5168
5207
|
} else {
|
|
5169
5208
|
if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
|
|
5170
|
-
throw
|
|
5209
|
+
throw unsupportedCapability28({
|
|
5171
5210
|
dialect: dialect.name,
|
|
5172
5211
|
feature: "a refined conflict target (constraint / partial-index predicate) on ON DUPLICATE KEY UPDATE",
|
|
5173
5212
|
hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
|
|
@@ -5175,52 +5214,69 @@ function renderInsert(params) {
|
|
|
5175
5214
|
}
|
|
5176
5215
|
const keepName = onConflict.keepColumn ?? statement.columns[0];
|
|
5177
5216
|
if (keepName === undefined) {
|
|
5178
|
-
throw
|
|
5217
|
+
throw internalError4({ message: "a skip-duplicates insert of an all-default row must name its keepColumn" });
|
|
5179
5218
|
}
|
|
5180
5219
|
const keep = dialect.quoteIdent({ name: keepName });
|
|
5181
5220
|
const current = `${dialect.quoteIdent({ name: statement.table.name })}.${keep}`;
|
|
5182
5221
|
const flag = onConflict.duplicateFlag;
|
|
5183
5222
|
if (flag !== undefined && !/^\d{1,20}$/.test(flag)) {
|
|
5184
|
-
throw
|
|
5223
|
+
throw internalError4({ message: `duplicateFlag must be a decimal literal, got ${JSON.stringify(flag)}` });
|
|
5185
5224
|
}
|
|
5186
5225
|
text += flag === undefined ? ` ON DUPLICATE KEY UPDATE ${keep} = ${current}` : ` ON DUPLICATE KEY UPDATE ${keep} = IF(LAST_INSERT_ID(${flag}), ${current}, ${current})`;
|
|
5187
5226
|
}
|
|
5188
5227
|
} else {
|
|
5189
5228
|
const updates = Object.entries(onConflict.update);
|
|
5190
5229
|
if (updates.length === 0) {
|
|
5191
|
-
throw
|
|
5230
|
+
throw internalError4({ message: "upsert update must set at least one column" });
|
|
5192
5231
|
}
|
|
5193
5232
|
const selfRef = dialect.quoteIdent({ name: statement.table.name });
|
|
5194
5233
|
const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true, tableName: statement.table.name });
|
|
5195
5234
|
if (dialect.upsertForm === "onConflict") {
|
|
5196
5235
|
if (onConflict.lastInsertIdColumn !== undefined) {
|
|
5197
|
-
throw
|
|
5236
|
+
throw internalError4({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
|
|
5198
5237
|
}
|
|
5199
5238
|
if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
|
|
5200
|
-
throw
|
|
5239
|
+
throw unsupportedCapability28({
|
|
5201
5240
|
dialect: dialect.name,
|
|
5202
5241
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
5203
5242
|
hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
|
|
5204
5243
|
});
|
|
5205
5244
|
}
|
|
5206
5245
|
if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
|
|
5207
|
-
throw
|
|
5246
|
+
throw internalError4({
|
|
5208
5247
|
message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
|
|
5209
5248
|
});
|
|
5210
5249
|
}
|
|
5211
5250
|
const partial = onConflict.conflictWhere === undefined ? "" : ` WHERE ${onConflict.conflictWhere}`;
|
|
5212
5251
|
const target = onConflict.constraint !== undefined ? ` ON CONSTRAINT ${dialect.quoteIdent({ name: onConflict.constraint })}` : ` (${onConflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")})${partial}`;
|
|
5213
5252
|
text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
|
|
5253
|
+
if (onConflict.updateWhere !== undefined) {
|
|
5254
|
+
if (!dialect.capabilities.conflictUpdateWhere) {
|
|
5255
|
+
throw unsupportedCapability28({
|
|
5256
|
+
dialect: dialect.name,
|
|
5257
|
+
feature: "a guarded conflict update (ON CONFLICT \u2026 DO UPDATE \u2026 WHERE)",
|
|
5258
|
+
hint: "run the guarded upsert as a find-then-write in a transaction instead"
|
|
5259
|
+
});
|
|
5260
|
+
}
|
|
5261
|
+
text += ` WHERE ${renderExpr({ dialect, expr: onConflict.updateWhere, collector })}`;
|
|
5262
|
+
}
|
|
5214
5263
|
} else {
|
|
5264
|
+
if (onConflict.updateWhere !== undefined) {
|
|
5265
|
+
throw unsupportedCapability28({
|
|
5266
|
+
dialect: dialect.name,
|
|
5267
|
+
feature: "a guarded conflict update (ON DUPLICATE KEY UPDATE has no WHERE)",
|
|
5268
|
+
hint: "run the guarded upsert as a find-then-write in a transaction instead \u2014 the runtime does this when the dialect lacks conflictUpdateWhere"
|
|
5269
|
+
});
|
|
5270
|
+
}
|
|
5215
5271
|
if (onConflict.constraint !== undefined) {
|
|
5216
|
-
throw
|
|
5272
|
+
throw unsupportedCapability28({
|
|
5217
5273
|
dialect: dialect.name,
|
|
5218
5274
|
feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
|
|
5219
5275
|
hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
|
|
5220
5276
|
});
|
|
5221
5277
|
}
|
|
5222
5278
|
if (onConflict.conflictWhere !== undefined) {
|
|
5223
|
-
throw
|
|
5279
|
+
throw unsupportedCapability28({
|
|
5224
5280
|
dialect: dialect.name,
|
|
5225
5281
|
feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
|
|
5226
5282
|
hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
|
|
@@ -5238,7 +5294,7 @@ function renderUpdate(params) {
|
|
|
5238
5294
|
const { dialect, statement, collector } = params;
|
|
5239
5295
|
const assignments = Object.entries(statement.set);
|
|
5240
5296
|
if (assignments.length === 0) {
|
|
5241
|
-
throw
|
|
5297
|
+
throw internalError4({ message: "update must set at least one column" });
|
|
5242
5298
|
}
|
|
5243
5299
|
const setSql = renderAssignments({ dialect, entries: assignments, collector, ...params.earlier === undefined ? {} : { steps: params.earlier } });
|
|
5244
5300
|
let text = `UPDATE ${renderTable(dialect, statement.table)} SET ${setSql}`;
|
|
@@ -5261,18 +5317,18 @@ function renderAssignments(params) {
|
|
|
5261
5317
|
const read = selfRef === undefined ? column : `${selfRef}.${column}`;
|
|
5262
5318
|
if (isStepColumnRef(value)) {
|
|
5263
5319
|
if (params.steps === undefined || !params.steps.has(value.step)) {
|
|
5264
|
-
throw
|
|
5320
|
+
throw internalError4({ message: `an UPDATE set value reads step "${value.step}", which is not an earlier chain step` });
|
|
5265
5321
|
}
|
|
5266
5322
|
return `${column} = (SELECT ${dialect.quoteIdent({ name: value.column })} FROM ${dialect.quoteIdent({ name: value.step })})`;
|
|
5267
5323
|
}
|
|
5268
5324
|
if (isSqlFragment(value)) {
|
|
5269
5325
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5270
|
-
throw
|
|
5326
|
+
throw internalError4({ message: "SQL assignment fragments require a conflict update" });
|
|
5271
5327
|
return `${column} = ${renderSqlFragment({ dialect, fragment: value, collector, conflict: { table: params.tableName } })}`;
|
|
5272
5328
|
}
|
|
5273
5329
|
if (isExcludedRef(value)) {
|
|
5274
5330
|
if (params.allowExcluded !== true) {
|
|
5275
|
-
throw
|
|
5331
|
+
throw internalError4({
|
|
5276
5332
|
message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
|
|
5277
5333
|
});
|
|
5278
5334
|
}
|
|
@@ -5280,7 +5336,7 @@ function renderAssignments(params) {
|
|
|
5280
5336
|
}
|
|
5281
5337
|
if (isSqlKeepCell(value)) {
|
|
5282
5338
|
if (params.allowExcluded !== true || selfRef === undefined)
|
|
5283
|
-
throw
|
|
5339
|
+
throw internalError4({ message: "a keep-column cell is only meaningful inside an upsert's update arm" });
|
|
5284
5340
|
return `${column} = ${read}`;
|
|
5285
5341
|
}
|
|
5286
5342
|
if (isJsonParamCell(value)) {
|
|
@@ -5318,19 +5374,19 @@ var CHAIN_ONE_ALIAS = "__vibe_one";
|
|
|
5318
5374
|
function renderChain(params) {
|
|
5319
5375
|
const { dialect, statement, collector } = params;
|
|
5320
5376
|
if (dialect.statementChain === null) {
|
|
5321
|
-
throw
|
|
5377
|
+
throw unsupportedCapability28({
|
|
5322
5378
|
dialect: dialect.name,
|
|
5323
5379
|
feature: "a statement chain (data-modifying WITH steps)",
|
|
5324
5380
|
hint: "this dialect accepts only SELECT inside WITH \u2014 gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
5325
5381
|
});
|
|
5326
5382
|
}
|
|
5327
5383
|
if (statement.steps.length === 0)
|
|
5328
|
-
throw
|
|
5384
|
+
throw internalError4({ message: "a statement chain needs at least one step" });
|
|
5329
5385
|
const defined = new Set;
|
|
5330
5386
|
const steps = [];
|
|
5331
5387
|
for (const step of statement.steps) {
|
|
5332
5388
|
if (step.name === CHAIN_ROWS_ALIAS || step.name === CHAIN_ONE_ALIAS || defined.has(step.name)) {
|
|
5333
|
-
throw
|
|
5389
|
+
throw internalError4({ message: `chain step name "${step.name}" is reserved or repeated` });
|
|
5334
5390
|
}
|
|
5335
5391
|
const body = renderChainStep({ dialect, statement: step.statement, collector, earlier: defined });
|
|
5336
5392
|
steps.push(`${dialect.quoteIdent({ name: step.name })} AS (${body})`);
|
|
@@ -5338,11 +5394,11 @@ function renderChain(params) {
|
|
|
5338
5394
|
}
|
|
5339
5395
|
const result = statement.result;
|
|
5340
5396
|
if (result.rowsFrom.length === 0 || result.columns.length === 0) {
|
|
5341
|
-
throw
|
|
5397
|
+
throw internalError4({ message: "a chain result needs at least one source step and one column" });
|
|
5342
5398
|
}
|
|
5343
5399
|
for (const name of result.rowsFrom) {
|
|
5344
5400
|
if (!defined.has(name))
|
|
5345
|
-
throw
|
|
5401
|
+
throw internalError4({ message: `chain result reads undefined step "${name}"` });
|
|
5346
5402
|
}
|
|
5347
5403
|
const columnList = result.columns.map((name) => dialect.quoteIdent({ name })).join(", ");
|
|
5348
5404
|
const union = result.rowsFrom.map((name) => `SELECT ${columnList} FROM ${dialect.quoteIdent({ name })}`).join(" UNION ALL ");
|
|
@@ -5351,7 +5407,7 @@ function renderChain(params) {
|
|
|
5351
5407
|
const extra = [];
|
|
5352
5408
|
for (const alias of [result.verdict?.alias, result.tally?.alias]) {
|
|
5353
5409
|
if (alias !== undefined && result.columns.includes(alias)) {
|
|
5354
|
-
throw
|
|
5410
|
+
throw internalError4({ message: `chain result alias "${alias}" collides with a result column` });
|
|
5355
5411
|
}
|
|
5356
5412
|
}
|
|
5357
5413
|
const rows = dialect.quoteIdent({ name: CHAIN_ROWS_ALIAS });
|
|
@@ -5388,21 +5444,21 @@ function renderSelectCell(params) {
|
|
|
5388
5444
|
if (isDbNowMarker(value))
|
|
5389
5445
|
return dialect.renderDbNow({ offsetSeconds: value.offsetSeconds, collector });
|
|
5390
5446
|
if (isSqlDefaultCell(value)) {
|
|
5391
|
-
throw
|
|
5447
|
+
throw internalError4({ message: "DEFAULT is not allowed in an INSERT \u2026 SELECT list \u2014 omit the column instead" });
|
|
5392
5448
|
}
|
|
5393
5449
|
return collector.add(value);
|
|
5394
5450
|
}
|
|
5395
5451
|
function renderRowSource(params) {
|
|
5396
5452
|
const { dialect, source, collector } = params;
|
|
5397
5453
|
if (source.columns.length === 0 || source.rows.length === 0) {
|
|
5398
|
-
throw
|
|
5454
|
+
throw internalError4({ message: "a row source needs at least one column and one row" });
|
|
5399
5455
|
}
|
|
5400
5456
|
const typedBy = renderTable(dialect, source.typedBy);
|
|
5401
5457
|
const columns = source.columns.map((name) => dialect.quoteIdent({ name }));
|
|
5402
5458
|
const rows = [`(${columns.map((column) => `(SELECT ${column} FROM ${typedBy} WHERE FALSE)`).join(", ")})`];
|
|
5403
5459
|
for (const row of source.rows) {
|
|
5404
5460
|
if (row.length !== source.columns.length) {
|
|
5405
|
-
throw
|
|
5461
|
+
throw internalError4({ message: `row source row has ${row.length} values but ${source.columns.length} columns` });
|
|
5406
5462
|
}
|
|
5407
5463
|
rows.push(`(${row.map((value) => renderSelectCell({ dialect, value, collector })).join(", ")})`);
|
|
5408
5464
|
}
|
|
@@ -5414,12 +5470,12 @@ function renderTallyColumn(params) {
|
|
|
5414
5470
|
const { dialect, result, defined, collector } = params;
|
|
5415
5471
|
const tally = result.tally;
|
|
5416
5472
|
if (tally === undefined)
|
|
5417
|
-
throw
|
|
5473
|
+
throw internalError4({ message: "a chain veto needs a tally column to guard" });
|
|
5418
5474
|
if (tally.steps.length === 0)
|
|
5419
|
-
throw
|
|
5475
|
+
throw internalError4({ message: "a chain tally needs at least one step" });
|
|
5420
5476
|
const step = (name) => {
|
|
5421
5477
|
if (!defined.has(name))
|
|
5422
|
-
throw
|
|
5478
|
+
throw internalError4({ message: `chain tally or veto reads undefined step "${name}"` });
|
|
5423
5479
|
return dialect.quoteIdent({ name });
|
|
5424
5480
|
};
|
|
5425
5481
|
const count = (name) => `(SELECT count(*) FROM ${step(name)})`;
|
|
@@ -5429,12 +5485,12 @@ function renderTallyColumn(params) {
|
|
|
5429
5485
|
return `${sum} AS ${dialect.quoteIdent({ name: tally.alias })}`;
|
|
5430
5486
|
const arms = vetoes.map((veto) => {
|
|
5431
5487
|
if (!CHAIN_VETO_TOKEN_PATTERN.test(veto.token))
|
|
5432
|
-
throw
|
|
5488
|
+
throw internalError4({ message: `chain veto token "${veto.token}" is not a lower-case word` });
|
|
5433
5489
|
if (typeof veto.nonce !== "string" || veto.nonce === "")
|
|
5434
|
-
throw
|
|
5490
|
+
throw internalError4({ message: `chain veto "${veto.token}" needs a non-empty nonce` });
|
|
5435
5491
|
const minRows = veto.minRows ?? 1;
|
|
5436
5492
|
if (!Number.isSafeInteger(minRows) || minRows < 1)
|
|
5437
|
-
throw
|
|
5493
|
+
throw internalError4({ message: `chain veto "${veto.token}" needs a positive integer minRows` });
|
|
5438
5494
|
const when = minRows === 1 ? `WHEN NOT EXISTS (SELECT 1 FROM ${step(veto.step)})` : `WHEN ${count(veto.step)} < ${minRows}`;
|
|
5439
5495
|
return `${when} THEN CAST('${CHAIN_VETO_PREFIX}${veto.token}:' || ${collector.add(veto.nonce)} || ':' || ${count(veto.step)} AS integer)`;
|
|
5440
5496
|
});
|
|
@@ -5443,25 +5499,25 @@ function renderTallyColumn(params) {
|
|
|
5443
5499
|
function renderInsertSelect(params) {
|
|
5444
5500
|
const { dialect, statement, collector } = params;
|
|
5445
5501
|
if (statement.columns.length === 0)
|
|
5446
|
-
throw
|
|
5502
|
+
throw internalError4({ message: "insert requires at least one column" });
|
|
5447
5503
|
if (statement.cells.length !== statement.columns.length) {
|
|
5448
|
-
throw
|
|
5504
|
+
throw internalError4({
|
|
5449
5505
|
message: `insert row has ${statement.cells.length} values but ${statement.columns.length} columns`
|
|
5450
5506
|
});
|
|
5451
5507
|
}
|
|
5452
5508
|
const from = statement.from ?? [];
|
|
5453
5509
|
for (const name of from) {
|
|
5454
5510
|
if (!params.earlier.has(name))
|
|
5455
|
-
throw
|
|
5511
|
+
throw internalError4({ message: `INSERT \u2026 SELECT reads step "${name}" before it is defined` });
|
|
5456
5512
|
}
|
|
5457
5513
|
const source = statement.source;
|
|
5458
5514
|
if (source !== undefined && (params.earlier.has(source.alias) || from.includes(source.alias))) {
|
|
5459
|
-
throw
|
|
5515
|
+
throw internalError4({ message: `row source alias "${source.alias}" collides with a chain step` });
|
|
5460
5516
|
}
|
|
5461
5517
|
const cells = statement.cells.map((value) => {
|
|
5462
5518
|
if (isStepColumnRef(value)) {
|
|
5463
5519
|
if (!from.includes(value.step) && value.step !== source?.alias) {
|
|
5464
|
-
throw
|
|
5520
|
+
throw internalError4({ message: `cell reads step "${value.step}", which the INSERT \u2026 SELECT does not name in FROM` });
|
|
5465
5521
|
}
|
|
5466
5522
|
return `${dialect.quoteIdent({ name: value.step })}.${dialect.quoteIdent({ name: value.column })}`;
|
|
5467
5523
|
}
|
|
@@ -5480,9 +5536,9 @@ function renderInsertSelect(params) {
|
|
|
5480
5536
|
const conflict = statement.conflictDoNothing;
|
|
5481
5537
|
if (conflict !== undefined) {
|
|
5482
5538
|
if (conflict.columns.length === 0)
|
|
5483
|
-
throw
|
|
5539
|
+
throw internalError4({ message: "an INSERT \u2026 SELECT conflict clause needs at least one column" });
|
|
5484
5540
|
if (dialect.doNothingForm !== "onConflictDoNothing") {
|
|
5485
|
-
throw
|
|
5541
|
+
throw unsupportedCapability28({
|
|
5486
5542
|
dialect: dialect.name,
|
|
5487
5543
|
feature: "ON CONFLICT DO NOTHING on an INSERT \u2026 SELECT chain step",
|
|
5488
5544
|
hint: "gate on SqlDialect.statementChain and keep the multi-statement path"
|
|
@@ -5493,7 +5549,7 @@ function renderInsertSelect(params) {
|
|
|
5493
5549
|
return text + applyReturning({ dialect, requested: statement.returning }).suffix;
|
|
5494
5550
|
}
|
|
5495
5551
|
// packages/sql/src/dialects/postgres-type-identity.ts
|
|
5496
|
-
import { unsupportedCapability as
|
|
5552
|
+
import { unsupportedCapability as unsupportedCapability29 } from "@vibeorm/schema";
|
|
5497
5553
|
var IDENTIFIER = '(?:"(?:[^"\\x00]|"")+"|[a-zA-Z_][a-zA-Z_0-9$]*)';
|
|
5498
5554
|
var MODIFIER_VALUE = `(?:[+-]?\\d+|${IDENTIFIER}|'(?:[^'\\x00]|'')*')`;
|
|
5499
5555
|
var TYPE_NAME = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*(\\(\\s*${MODIFIER_VALUE}(?:\\s*,\\s*${MODIFIER_VALUE})*\\s*\\))?\\s*((?:\\[\\s*\\]\\s*)*)$`);
|
|
@@ -5520,13 +5576,13 @@ function parsePostgresTypeName(params) {
|
|
|
5520
5576
|
}
|
|
5521
5577
|
function renderPostgresTypeName(params) {
|
|
5522
5578
|
if (params.dialect.name !== "postgres")
|
|
5523
|
-
throw
|
|
5579
|
+
throw unsupportedCapability29({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
5524
5580
|
const { dialect, type } = params;
|
|
5525
5581
|
return `${type.namespace === undefined ? "" : `${dialect.quoteIdent({ name: type.namespace })}.`}${dialect.quoteIdent({ name: type.name })}${type.modifier}${type.array ? "[]" : ""}`;
|
|
5526
5582
|
}
|
|
5527
5583
|
function renderPostgresTypeIdentity(params) {
|
|
5528
5584
|
if (params.dialect.name !== "postgres")
|
|
5529
|
-
throw
|
|
5585
|
+
throw unsupportedCapability29({ dialect: params.dialect.name, feature: "postgresTypeIdentity" });
|
|
5530
5586
|
const collector = new ParamCollector(params.dialect);
|
|
5531
5587
|
const { source } = params;
|
|
5532
5588
|
let text;
|
|
@@ -5539,11 +5595,11 @@ function renderPostgresTypeIdentity(params) {
|
|
|
5539
5595
|
return { text, values: collector.values, returning: "none" };
|
|
5540
5596
|
}
|
|
5541
5597
|
// packages/sql/src/dialects/postgres-default-identity.ts
|
|
5542
|
-
import { unsupportedCapability as
|
|
5598
|
+
import { unsupportedCapability as unsupportedCapability30 } from "@vibeorm/schema";
|
|
5543
5599
|
function renderPostgresDefaultFunctionIdentity(params) {
|
|
5544
5600
|
const { dialect, table, column } = params;
|
|
5545
5601
|
if (dialect.name !== "postgres")
|
|
5546
|
-
throw
|
|
5602
|
+
throw unsupportedCapability30({ dialect: dialect.name, feature: "defaultFunctionIdentity" });
|
|
5547
5603
|
const collector = new ParamCollector(dialect);
|
|
5548
5604
|
const namespace = collector.add(table.namespace);
|
|
5549
5605
|
const name = collector.add(table.name);
|
|
@@ -5567,28 +5623,28 @@ WHERE n.nspname = ${namespace} AND c.relname = ${name} AND a.attname = ${field}
|
|
|
5567
5623
|
};
|
|
5568
5624
|
}
|
|
5569
5625
|
// packages/sql/src/dialects/constraint-owned-index.ts
|
|
5570
|
-
import { unsupportedCapability as
|
|
5626
|
+
import { unsupportedCapability as unsupportedCapability31 } from "@vibeorm/schema";
|
|
5571
5627
|
function renderConstraintOwnedIndexDrop(params) {
|
|
5572
5628
|
if (params.dialect.name !== "postgres")
|
|
5573
|
-
throw
|
|
5629
|
+
throw unsupportedCapability31({ dialect: params.dialect.name, feature: "constraintOwnedIndexDrop" });
|
|
5574
5630
|
const quote = (name) => params.dialect.quoteIdent({ name });
|
|
5575
5631
|
return { text: `ALTER TABLE ${quote(params.table)} DROP CONSTRAINT ${quote(params.constraint)};`, values: [], returning: "none" };
|
|
5576
5632
|
}
|
|
5577
5633
|
// packages/sql/src/dialects/mysql-check-probe.ts
|
|
5578
|
-
import { unsupportedCapability as
|
|
5634
|
+
import { unsupportedCapability as unsupportedCapability32 } from "@vibeorm/schema";
|
|
5579
5635
|
function renderMysqlCheckProbe(params) {
|
|
5580
5636
|
if (params.dialect.name !== "mysql")
|
|
5581
|
-
throw
|
|
5637
|
+
throw unsupportedCapability32({ dialect: params.dialect.name, feature: "temporaryCheckProbe" });
|
|
5582
5638
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
5583
5639
|
const probe = q(params.probe);
|
|
5584
5640
|
switch (params.operation) {
|
|
5585
5641
|
case "clone":
|
|
5586
5642
|
if (params.table === undefined)
|
|
5587
|
-
throw
|
|
5643
|
+
throw unsupportedCapability32({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "A source table is required" });
|
|
5588
5644
|
return { text: `CREATE TEMPORARY TABLE ${probe} LIKE ${q(params.table.namespace)}.${q(params.table.name)}`, values: [], returning: "none" };
|
|
5589
5645
|
case "add": {
|
|
5590
5646
|
if (params.expression === undefined || params.definition === undefined)
|
|
5591
|
-
throw
|
|
5647
|
+
throw unsupportedCapability32({ dialect: "mysql", feature: "temporaryCheckProbe", hint: "An authored expression and temporary clone definition are required" });
|
|
5592
5648
|
const parts = tableDefinitionParts({ sql: params.definition, backslashEscapes: true });
|
|
5593
5649
|
return { text: `CREATE TEMPORARY TABLE ${probe} (
|
|
5594
5650
|
${[...parts.clauses, `CONSTRAINT ${probe} CHECK (${params.expression})`].join(`,
|
|
@@ -5634,33 +5690,33 @@ function sqliteTableConstraints(params) {
|
|
|
5634
5690
|
return result;
|
|
5635
5691
|
}
|
|
5636
5692
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
5637
|
-
import { unsupportedCapability as
|
|
5693
|
+
import { unsupportedCapability as unsupportedCapability34 } from "@vibeorm/schema";
|
|
5638
5694
|
|
|
5639
5695
|
// packages/sql/src/dialects/migration-inventory.ts
|
|
5640
|
-
import { unsupportedCapability as
|
|
5696
|
+
import { unsupportedCapability as unsupportedCapability33 } from "@vibeorm/schema";
|
|
5641
5697
|
var MYSQL_NO_ROW_LIMIT = " LIMIT 18446744073709551615";
|
|
5642
5698
|
function renderSqliteColumnInventory(params) {
|
|
5643
5699
|
if (params.dialect.name !== "sqlite")
|
|
5644
|
-
throw
|
|
5700
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
5645
5701
|
const collector = new ParamCollector(params.dialect);
|
|
5646
5702
|
return { text: `SELECT name, hidden, pk, dflt_value AS definition FROM pragma_table_xinfo(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY cid`, values: collector.values, returning: "none" };
|
|
5647
5703
|
}
|
|
5648
5704
|
function renderSqliteForeignKeyInventory(params) {
|
|
5649
5705
|
if (params.dialect.name !== "sqlite")
|
|
5650
|
-
throw
|
|
5706
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
5651
5707
|
const collector = new ParamCollector(params.dialect);
|
|
5652
5708
|
return { text: `SELECT id, seq, "from", "to", "table", on_update, on_delete, match FROM pragma_foreign_key_list(${collector.add(params.table)}, ${collector.add(params.namespace)}) ORDER BY id, seq`, values: collector.values, returning: "none" };
|
|
5653
5709
|
}
|
|
5654
5710
|
function renderSqliteColumnInventoryBatch(params) {
|
|
5655
5711
|
if (params.dialect.name !== "sqlite")
|
|
5656
|
-
throw
|
|
5712
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteColumnInventory" });
|
|
5657
5713
|
const collector = new ParamCollector(params.dialect);
|
|
5658
5714
|
const request = params.dialect.quoteIdent({ name: "vibe_request" });
|
|
5659
5715
|
return { text: `SELECT j.key AS ${request}, x.name AS name, x.hidden AS hidden, x.pk AS pk, x.dflt_value AS definition FROM json_each(${collector.add(JSON.stringify(params.tables))}) j LEFT JOIN pragma_table_xinfo(j.value, ${collector.add(params.namespace)}) x ORDER BY j.key, x.cid`, values: collector.values, returning: "none" };
|
|
5660
5716
|
}
|
|
5661
5717
|
function renderSqliteForeignKeyInventoryBatch(params) {
|
|
5662
5718
|
if (params.dialect.name !== "sqlite")
|
|
5663
|
-
throw
|
|
5719
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteForeignKeyInventory" });
|
|
5664
5720
|
const collector = new ParamCollector(params.dialect);
|
|
5665
5721
|
const request = params.dialect.quoteIdent({ name: "vibe_request" });
|
|
5666
5722
|
return { text: `SELECT j.key AS ${request}, f.id AS id, f.seq AS seq, f."from" AS "from", f."to" AS "to", f."table" AS "table", f.on_update AS on_update, f.on_delete AS on_delete, f.match AS match FROM json_each(${collector.add(JSON.stringify(params.tables))}) j LEFT JOIN pragma_foreign_key_list(j.value, ${collector.add(params.namespace)}) f ORDER BY j.key, f.id, f.seq`, values: collector.values, returning: "none" };
|
|
@@ -5673,15 +5729,15 @@ var SQLITE_INTROSPECTION_PRAGMA_ORDER = Object.freeze({
|
|
|
5673
5729
|
});
|
|
5674
5730
|
function renderSqliteIntrospectionPragmaBatch(params) {
|
|
5675
5731
|
if (params.dialect.name !== "sqlite")
|
|
5676
|
-
throw
|
|
5732
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteIntrospectionPragmaBatch" });
|
|
5677
5733
|
const order = SQLITE_INTROSPECTION_PRAGMA_ORDER[params.pragma];
|
|
5678
5734
|
if (order === undefined)
|
|
5679
|
-
throw
|
|
5735
|
+
throw unsupportedCapability33({ dialect: "sqlite", feature: `introspectionPragma:${String(params.pragma)}` });
|
|
5680
5736
|
return { text: `SELECT j.key AS ${params.dialect.quoteIdent({ name: "vibe_request" })}, p.* FROM json_each(${params.dialect.quoteStringLiteral({ value: JSON.stringify(params.names) })}) j LEFT JOIN pragma_${params.pragma}(j.value) p ORDER BY j.key, ${order}`, values: [], returning: "none" };
|
|
5681
5737
|
}
|
|
5682
5738
|
function renderSqliteRebuildObjects(params) {
|
|
5683
5739
|
if (params.dialect.name !== "sqlite")
|
|
5684
|
-
throw
|
|
5740
|
+
throw unsupportedCapability33({ dialect: params.dialect.name, feature: "sqliteRebuildObjects" });
|
|
5685
5741
|
return { text: "SELECT name, tbl_name, type, sql FROM main.sqlite_schema WHERE type IN ('index', 'trigger') AND sql IS NOT NULL ORDER BY type, name", values: [], returning: "none" };
|
|
5686
5742
|
}
|
|
5687
5743
|
function renderMigrationInventory(params) {
|
|
@@ -5723,13 +5779,13 @@ ORDER BY kind, parent, name${MYSQL_NO_ROW_LIMIT}`, values: collector.values, ret
|
|
|
5723
5779
|
// packages/sql/src/dialects/migration-visibility.ts
|
|
5724
5780
|
function renderMigrationVisibility(params) {
|
|
5725
5781
|
if (params.dialect.name !== "mysql")
|
|
5726
|
-
throw
|
|
5782
|
+
throw unsupportedCapability34({ dialect: params.dialect.name, feature: "mysqlCatalogVisibility" });
|
|
5727
5783
|
const collector = new ParamCollector(params.dialect);
|
|
5728
5784
|
const grantee = "CONCAT(QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', 1)), '@', QUOTE(SUBSTRING_INDEX(CURRENT_USER(), '@', -1)))";
|
|
5729
5785
|
return { text: `SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.USER_PRIVILEGES WHERE GRANTEE = ${grantee} UNION SELECT PRIVILEGE_TYPE AS privilege FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE = ${grantee} AND ${collector.add(params.namespace)} LIKE TABLE_SCHEMA${MYSQL_NO_ROW_LIMIT}`, values: collector.values, returning: "none" };
|
|
5730
5786
|
}
|
|
5731
5787
|
// packages/sql/src/dialects/migration-objects.ts
|
|
5732
|
-
import { internalError as
|
|
5788
|
+
import { internalError as internalError5, unsupportedCapability as unsupportedCapability35 } from "@vibeorm/schema";
|
|
5733
5789
|
function renderMigrationObjectQuery(params) {
|
|
5734
5790
|
return objectQuery({ ...params, collector: new ParamCollector(params.dialect) });
|
|
5735
5791
|
}
|
|
@@ -5737,7 +5793,7 @@ function objectQuery(params) {
|
|
|
5737
5793
|
const { dialect, identity: object, collector } = params;
|
|
5738
5794
|
if (object.kind === "namespace") {
|
|
5739
5795
|
if (dialect.name !== "postgres")
|
|
5740
|
-
throw
|
|
5796
|
+
throw unsupportedCapability35({ dialect: dialect.name, feature: "fixedNamespaces" });
|
|
5741
5797
|
return { text: `SELECT n.nspname::text AS "name" FROM pg_catalog.pg_namespace n WHERE n.nspname = ${collector.add(object.name)}`, values: collector.values, returning: "none" };
|
|
5742
5798
|
}
|
|
5743
5799
|
const physical = "table" in object ? object.table : object.name;
|
|
@@ -5785,18 +5841,18 @@ function objectQuery(params) {
|
|
|
5785
5841
|
}
|
|
5786
5842
|
} else if (dialect.name === "mysql") {
|
|
5787
5843
|
if (object.kind === "constraint" && object.constraintType === "exclusion")
|
|
5788
|
-
throw
|
|
5844
|
+
throw unsupportedCapability35({ dialect: "mysql", feature: "exclusionConstraints" });
|
|
5789
5845
|
const mysql = mysqlFactsText({ dialect, identity: object, namespace, name, collector });
|
|
5790
5846
|
if (mysql === undefined)
|
|
5791
|
-
throw
|
|
5847
|
+
throw unsupportedCapability35({ dialect: "mysql", feature: `inspect:${object.kind}` });
|
|
5792
5848
|
text = mysql;
|
|
5793
5849
|
} else {
|
|
5794
5850
|
if (physical.namespace !== "main" && physical.namespace !== "temp")
|
|
5795
|
-
throw
|
|
5851
|
+
throw unsupportedCapability35({ dialect: "sqlite", feature: "attachedSchemaInspection" });
|
|
5796
5852
|
const catalog = `${dialect.quoteIdent({ name: physical.namespace })}.sqlite_schema`;
|
|
5797
5853
|
const kind = object.kind === "table" || object.kind === "view" ? object.kind : object.kind === "trigger" ? "trigger" : object.kind === "index" ? "index" : "table";
|
|
5798
5854
|
if (["function", "sequence", "postgresExtension", "enum"].includes(object.kind) || object.kind === "constraint" && object.constraintType === "exclusion")
|
|
5799
|
-
throw
|
|
5855
|
+
throw unsupportedCapability35({ dialect: "sqlite", feature: `inspect:${object.kind}` });
|
|
5800
5856
|
const sqliteParams = new ParamCollector(dialect);
|
|
5801
5857
|
if (object.kind === "column" || object.kind === "columnDefault" || object.kind === "generatedColumn") {
|
|
5802
5858
|
text = `SELECT x.type AS type, x."notnull" AS "notNull", x.pk AS primaryKeyPosition, x.hidden AS generated, x.dflt_value AS definition, (SELECT sql FROM ${catalog} WHERE name = ${sqliteParams.add(physical.name)} AND type = 'table') AS tableDefinition FROM pragma_table_xinfo(${sqliteParams.add(physical.name)}, ${sqliteParams.add(physical.namespace)}) x WHERE x.name = ${sqliteParams.add(object.column)}`;
|
|
@@ -5871,7 +5927,7 @@ function mysqlBatches(params) {
|
|
|
5871
5927
|
function renderMysqlObjectBatch(params) {
|
|
5872
5928
|
const { dialect } = params;
|
|
5873
5929
|
if (dialect.name !== "mysql")
|
|
5874
|
-
throw
|
|
5930
|
+
throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
5875
5931
|
const position = dialect.quoteIdent({ name: "vibe_position" });
|
|
5876
5932
|
const groups = new Map;
|
|
5877
5933
|
for (const [index, identity] of params.identities.entries()) {
|
|
@@ -5891,7 +5947,7 @@ function renderMysqlObjectBatch(params) {
|
|
|
5891
5947
|
function renderMysqlObjectMembersBatch(params) {
|
|
5892
5948
|
const { dialect } = params;
|
|
5893
5949
|
if (dialect.name !== "mysql")
|
|
5894
|
-
throw
|
|
5950
|
+
throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
5895
5951
|
const position = dialect.quoteIdent({ name: "vibe_position" });
|
|
5896
5952
|
const ordinal = dialect.quoteIdent({ name: "vibe_ordinal" });
|
|
5897
5953
|
const groups = new Map;
|
|
@@ -5911,7 +5967,7 @@ function renderMigrationDependencies(params) {
|
|
|
5911
5967
|
}
|
|
5912
5968
|
function dependencyQuery(params) {
|
|
5913
5969
|
if (params.dialect.name !== "postgres")
|
|
5914
|
-
throw
|
|
5970
|
+
throw unsupportedCapability35({ dialect: params.dialect.name, feature: "catalogDependencies" });
|
|
5915
5971
|
const object = params.identity;
|
|
5916
5972
|
const collector = params.collector;
|
|
5917
5973
|
if (object.kind === "namespace") {
|
|
@@ -5968,7 +6024,7 @@ function inspectionReferences(params) {
|
|
|
5968
6024
|
function renderMigrationObjectBatch(params) {
|
|
5969
6025
|
const { dialect } = params;
|
|
5970
6026
|
if (dialect.name !== "postgres")
|
|
5971
|
-
throw
|
|
6027
|
+
throw unsupportedCapability35({ dialect: dialect.name, feature: "batchedMigrationInspection" });
|
|
5972
6028
|
const groups = new Map;
|
|
5973
6029
|
for (const [position, identity] of params.identities.entries()) {
|
|
5974
6030
|
const kind = identity.kind === "constraint" ? `${identity.kind}:${identity.constraintType}` : identity.kind === "generatedColumn" ? "columnDefault" : identity.kind;
|
|
@@ -5981,7 +6037,7 @@ function renderMigrationObjectBatch(params) {
|
|
|
5981
6037
|
const facts = objectQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
5982
6038
|
const dependencies = dependencyQuery({ dialect, identity, collector: inspectionReferences({ dialect }) });
|
|
5983
6039
|
if (JSON.stringify(dependencies.values) !== JSON.stringify(facts.values))
|
|
5984
|
-
throw
|
|
6040
|
+
throw internalError5({ message: "Batched catalog queries bind their identity in different orders", meta: { kind: identity.kind } });
|
|
5985
6041
|
const columns = facts.values.map((value, index) => `${dialect.quoteIdent({ name: `p${index + 1}` })} ${Array.isArray(value) ? "text[]" : "text"}`);
|
|
5986
6042
|
const requests = group.map(({ identity, position }) => ({ position, ...Object.fromEntries(renderMigrationObjectQuery({ dialect, identity }).values.map((value, index) => [`p${index + 1}`, value])) }));
|
|
5987
6043
|
const collector = new ParamCollector(dialect);
|
|
@@ -6001,16 +6057,16 @@ function renderMigrationNamespace(params) {
|
|
|
6001
6057
|
}
|
|
6002
6058
|
function renderCheckProbe(params) {
|
|
6003
6059
|
if (params.dialect.name !== "postgres")
|
|
6004
|
-
throw
|
|
6060
|
+
throw unsupportedCapability35({ dialect: params.dialect.name, feature: "rollbackCheckProbe" });
|
|
6005
6061
|
const q = (name) => params.dialect.quoteIdent({ name });
|
|
6006
6062
|
return { text: `ALTER TABLE ${q(params.table.namespace)}.${q(params.table.name)} ADD CONSTRAINT ${q(params.name)} CHECK (${params.expression}) NOT VALID`, values: [], returning: "none" };
|
|
6007
6063
|
}
|
|
6008
6064
|
// packages/sql/src/dialects/postgres-migration.ts
|
|
6009
|
-
import { VibeError as
|
|
6065
|
+
import { VibeError as VibeError24, unsupportedCapability as unsupportedCapability36 } from "@vibeorm/schema";
|
|
6010
6066
|
var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
|
|
6011
6067
|
function requirePostgres2(params) {
|
|
6012
6068
|
if (params.dialect.name !== "postgres") {
|
|
6013
|
-
throw
|
|
6069
|
+
throw unsupportedCapability36({ dialect: params.dialect.name, feature: params.feature });
|
|
6014
6070
|
}
|
|
6015
6071
|
}
|
|
6016
6072
|
function renderMigrationSessionRead(params) {
|
|
@@ -6025,7 +6081,7 @@ function renderMigrationSessionSet(params) {
|
|
|
6025
6081
|
requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
|
|
6026
6082
|
const { milliseconds, setting } = params;
|
|
6027
6083
|
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
|
|
6028
|
-
throw new
|
|
6084
|
+
throw new VibeError24({
|
|
6029
6085
|
code: "VIBE_MIGRATION",
|
|
6030
6086
|
message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
|
|
6031
6087
|
meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
|
|
@@ -6196,6 +6252,7 @@ export {
|
|
|
6196
6252
|
isSqlParam,
|
|
6197
6253
|
isSqlQuery,
|
|
6198
6254
|
isStepColumnRef,
|
|
6255
|
+
isTimeZoneName,
|
|
6199
6256
|
jsonListParameter,
|
|
6200
6257
|
jsonParamCell,
|
|
6201
6258
|
keyListBindsUntypedRows,
|
|
@@ -6222,6 +6279,7 @@ export {
|
|
|
6222
6279
|
renderColumnStateQuery,
|
|
6223
6280
|
renderConstraintOwnedIndexDrop,
|
|
6224
6281
|
renderConstraintStateQuery,
|
|
6282
|
+
renderDateTruncation,
|
|
6225
6283
|
renderDiagnostic,
|
|
6226
6284
|
renderExclusionCatalog,
|
|
6227
6285
|
renderExclusionDdl,
|
|
@@ -6322,4 +6380,4 @@ export {
|
|
|
6322
6380
|
toPgArrayLiteral
|
|
6323
6381
|
};
|
|
6324
6382
|
|
|
6325
|
-
//# debugId=
|
|
6383
|
+
//# debugId=DB7441716F57EC8864756E2164756E21
|