@vibeorm/sql 2.0.0-alpha.6 → 2.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -42,64 +42,33 @@ function jsonParamCell(params) {
42
42
  function isJsonParamCell(value) {
43
43
  return typeof value === "object" && value !== null && value.__jsonParam === true;
44
44
  }
45
+ function excludedRef(params) {
46
+ return { __excluded: true, column: params.column };
47
+ }
48
+ function isExcludedRef(value) {
49
+ return typeof value === "object" && value !== null && value.__excluded === true;
50
+ }
45
51
  // packages/sql/src/capabilities.ts
46
- var POSTGRES_CAPABILITIES = {
47
- returning: true,
48
- scalarArrays: true,
49
- nativeEnums: true,
50
- ilike: true,
51
- lateralJoin: true,
52
- distinctOn: true,
53
- partialIndexes: true,
54
- conflictTargetConstraint: true,
55
- transactionalDdl: true,
56
- createManyAndReturn: true,
57
- jsonColumnType: true,
58
- orderByNulls: true,
59
- fullTextEngines: ["bm25", "tsvector"]
60
- };
61
- var SQLITE_CAPABILITIES = {
62
- returning: true,
63
- scalarArrays: false,
64
- nativeEnums: false,
65
- ilike: false,
66
- lateralJoin: false,
67
- distinctOn: false,
68
- partialIndexes: true,
69
- conflictTargetConstraint: false,
70
- transactionalDdl: true,
71
- createManyAndReturn: true,
72
- jsonColumnType: false,
73
- orderByNulls: true,
74
- fullTextEngines: ["fts5"]
75
- };
76
- var MYSQL_CAPABILITIES = {
77
- returning: false,
78
- scalarArrays: false,
79
- nativeEnums: true,
80
- ilike: false,
81
- lateralJoin: false,
82
- distinctOn: false,
83
- partialIndexes: false,
84
- conflictTargetConstraint: false,
85
- transactionalDdl: false,
86
- createManyAndReturn: false,
87
- jsonColumnType: true,
88
- orderByNulls: false,
89
- fullTextEngines: ["fulltext"]
90
- };
91
- var DIALECT_CAPABILITIES = {
92
- postgres: POSTGRES_CAPABILITIES,
93
- sqlite: SQLITE_CAPABILITIES,
94
- mysql: MYSQL_CAPABILITIES
95
- };
52
+ import {
53
+ DIALECT_CAPABILITIES,
54
+ POSTGRES_CAPABILITIES,
55
+ SQLITE_CAPABILITIES,
56
+ MYSQL_CAPABILITIES
57
+ } from "@vibeorm/schema";
96
58
 
97
59
  // packages/sql/src/dialects/postgres.ts
98
60
  var postgresDialect = {
99
61
  name: "postgres",
100
62
  capabilities: POSTGRES_CAPABILITIES,
101
63
  inArrayStrategy: "any",
64
+ tupleInStrategy: "rowValue",
102
65
  upsertForm: "onConflict",
66
+ conflictKeyEquality: {
67
+ foldingScalars: ["Decimal"],
68
+ textIsBinaryByDefault: true,
69
+ duplicatesInOneStatement: "reject"
70
+ },
71
+ excludedRowRef: { form: "keyword", name: "excluded" },
103
72
  doNothingForm: "onConflictDoNothing",
104
73
  insensitiveLike: "ilike",
105
74
  strictMatchStrategy: "native",
@@ -138,7 +107,14 @@ var sqliteDialect = {
138
107
  name: "sqlite",
139
108
  capabilities: SQLITE_CAPABILITIES,
140
109
  inArrayStrategy: "expand",
110
+ tupleInStrategy: "rowValue",
141
111
  upsertForm: "onConflict",
112
+ conflictKeyEquality: {
113
+ foldingScalars: [],
114
+ textIsBinaryByDefault: true,
115
+ duplicatesInOneStatement: "lastWriteWins"
116
+ },
117
+ excludedRowRef: { form: "keyword", name: "excluded" },
142
118
  doNothingForm: "onConflictDoNothing",
143
119
  insensitiveLike: "lower",
144
120
  strictMatchStrategy: "glob",
@@ -177,7 +153,15 @@ var mysqlDialect = {
177
153
  name: "mysql",
178
154
  capabilities: MYSQL_CAPABILITIES,
179
155
  inArrayStrategy: "expand",
156
+ tupleInStrategy: "rowValue",
180
157
  upsertForm: "onDuplicateKey",
158
+ conflictKeyEquality: {
159
+ foldingScalars: ["Decimal"],
160
+ defaultStringMaxLength: 191,
161
+ textIsBinaryByDefault: false,
162
+ duplicatesInOneStatement: "lastWriteWins"
163
+ },
164
+ excludedRowRef: { form: "rowAlias", alias: "new" },
181
165
  doNothingForm: "insertIgnore",
182
166
  insensitiveLike: "lower",
183
167
  strictMatchStrategy: "binary",
@@ -221,7 +205,27 @@ function getSqlDialect(params) {
221
205
  return SQL_DIALECTS[params.dialect];
222
206
  }
223
207
  // packages/sql/src/codecs.ts
224
- import { VibeError } from "@vibeorm/schema";
208
+ import { VibeError, invalidStoredData } from "@vibeorm/schema";
209
+ function enumMembershipCodec(params) {
210
+ const { model, field, enum: enumName } = params;
211
+ const members = new Set(params.members);
212
+ return {
213
+ encode: (value) => {
214
+ if (typeof value === "string" && members.has(value))
215
+ return value;
216
+ throw new VibeError({
217
+ code: "VIBE_VALIDATION",
218
+ message: `Invalid value for enum ${enumName} on ${model}.${field}`,
219
+ meta: { model, field, enum: enumName }
220
+ });
221
+ },
222
+ decode: (value) => {
223
+ if (typeof value === "string" && members.has(value))
224
+ return value;
225
+ throw invalidStoredData({ model, field, enum: enumName, value });
226
+ }
227
+ };
228
+ }
225
229
  function decodeIsNoop(params) {
226
230
  const { scalarType, wire } = params;
227
231
  if (wire === undefined)
@@ -367,8 +371,435 @@ var DIALECT_CODECS = {
367
371
  sqlite: SQLITE_CODECS,
368
372
  mysql: MYSQL_CODECS
369
373
  };
374
+ // packages/sql/src/dialects/diagnostics.ts
375
+ import { unsupportedCapability } from "@vibeorm/schema";
376
+ var QUERIES = Object.freeze({
377
+ postgresJsonWire: { dialect: "postgres", text: `SELECT '{"__vibeorm":1}'::jsonb AS "probe"` },
378
+ sqliteTriggers: { dialect: "sqlite", text: "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'trigger'" }
379
+ });
380
+ function renderDiagnostic(params) {
381
+ const query = QUERIES[params.query];
382
+ if (params.dialect.name !== query.dialect) {
383
+ throw unsupportedCapability({ dialect: params.dialect.name, feature: params.query, hint: `this diagnostic requires ${query.dialect}` });
384
+ }
385
+ return { text: query.text, values: [], returning: "none" };
386
+ }
387
+ function isFixedDiagnostic(params) {
388
+ return params.query === "postgresJsonWire" || params.query === "sqliteTriggers";
389
+ }
390
+
391
+ // packages/sql/src/dialects/postgres-diagnostics.ts
392
+ import { VibeError as VibeError2, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
393
+
394
+ // packages/sql/src/params.ts
395
+ class ParamCollector {
396
+ dialect;
397
+ values;
398
+ constructor(dialect) {
399
+ this.dialect = dialect;
400
+ this.values = [];
401
+ }
402
+ add(value) {
403
+ this.values.push(value);
404
+ return this.dialect.placeholder({ index: this.values.length });
405
+ }
406
+ addMany(values) {
407
+ return values.map((value) => this.add(value));
408
+ }
409
+ }
410
+
411
+ // packages/sql/src/dialects/postgres-diagnostics.ts
412
+ function renderPostgresExplain(params) {
413
+ const { dialect, query, options } = params;
414
+ requirePostgres({ dialect, feature: "query plan inspection (EXPLAIN)" });
415
+ const parts = [];
416
+ if (options?.analyze === true)
417
+ parts.push("ANALYZE");
418
+ parts.push("FORMAT JSON");
419
+ if (options?.verbose === true)
420
+ parts.push("VERBOSE");
421
+ if (options?.buffers === true)
422
+ parts.push("BUFFERS");
423
+ if (options?.settings === true)
424
+ parts.push("SETTINGS");
425
+ return { text: `EXPLAIN (${parts.join(", ")}) ${query.text}`, values: [...query.values], returning: "none" };
426
+ }
427
+ var POSTGRES_DIAGNOSTIC_SQL = Object.freeze({
428
+ planContext: `SELECT current_user::text AS "currentUser", session_user::text AS "sessionUser", r.rolsuper AS "superuser", r.rolbypassrls AS "bypassRls", pg_catalog.current_setting('server_version_num') AS "serverVersionNum", pg_catalog.current_database()::text AS "database" FROM pg_catalog.pg_roles r WHERE r.rolname = current_user`,
429
+ statementExtension: `SELECT (SELECT e.extversion FROM pg_catalog.pg_extension e WHERE e.extname = 'pg_stat_statements') AS "installedVersion", EXISTS (SELECT 1 FROM pg_catalog.pg_available_extensions a WHERE a.name = 'pg_stat_statements') AS "available", (SELECT n.nspname::text FROM pg_catalog.pg_extension e JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace WHERE e.extname = 'pg_stat_statements') AS "schema", pg_catalog.current_setting('server_version_num') AS "serverVersionNum", pg_catalog.pg_has_role(current_user, 'pg_read_all_stats', 'MEMBER') AS "readAllStats", current_user::text AS "currentUser"`,
430
+ statementInfo: `SELECT i.stats_reset::text AS "statsReset" FROM %SCHEMA%.pg_stat_statements_info i`,
431
+ statementSnapshot: `SELECT s.queryid::text AS "queryId", s.query AS "query", s.calls AS "calls", s.total_exec_time AS "totalExecTimeMs", s.mean_exec_time AS "meanExecTimeMs", s.min_exec_time AS "minExecTimeMs", s.max_exec_time AS "maxExecTimeMs", s.rows AS "rows", s.shared_blks_hit AS "sharedBlocksHit", s.shared_blks_read AS "sharedBlocksRead", s.temp_blks_read AS "tempBlocksRead", s.temp_blks_written AS "tempBlocksWritten" FROM %SCHEMA%.pg_stat_statements s JOIN pg_catalog.pg_database d ON d.oid = s.dbid WHERE d.datname = pg_catalog.current_database() ORDER BY s.total_exec_time DESC LIMIT %LIMIT%`,
432
+ indexes: `SELECT i.relname::text AS "index", pg_catalog.pg_get_indexdef(x.indexrelid) AS "definition", x.indisunique AS "unique", x.indisprimary AS "primary", x.indisvalid AS "valid", pg_catalog.pg_relation_size(x.indexrelid) AS "indexBytes" FROM pg_catalog.pg_index x JOIN pg_catalog.pg_class c ON c.oid = x.indrelid JOIN pg_catalog.pg_class i ON i.oid = x.indexrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = %SCHEMA_PARAM% AND c.relname = %TABLE_PARAM% ORDER BY i.relname`,
433
+ sessionBegin: "BEGIN",
434
+ sessionReadOnly: "SET TRANSACTION READ ONLY",
435
+ sessionCommit: "COMMIT",
436
+ sessionRollback: "ROLLBACK",
437
+ setContext: `SELECT pg_catalog.set_config(%KEY%, %VALUE%, true)`,
438
+ tableStats: `SELECT c.reltuples::float8 AS "estimatedRows", c.relpages::bigint AS "pages", pg_catalog.pg_table_size(c.oid) AS "tableBytes", c.relrowsecurity AS "rlsEnabled", c.relforcerowsecurity AS "rlsForced", (SELECT s.last_analyze::text FROM pg_catalog.pg_stat_all_tables s WHERE s.relid = c.oid) AS "lastAnalyze", (SELECT s.last_autoanalyze::text FROM pg_catalog.pg_stat_all_tables s WHERE s.relid = c.oid) AS "lastAutoAnalyze" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = %SCHEMA_PARAM% AND c.relname = %TABLE_PARAM%`
439
+ });
440
+ var POSTGRES_WORKLOAD_LIMITS = Object.freeze({
441
+ minLimit: 1,
442
+ maxLimit: 1000
443
+ });
444
+ function requirePostgres(params) {
445
+ if (params.dialect.name !== "postgres") {
446
+ throw unsupportedCapability2({
447
+ dialect: params.dialect.name,
448
+ feature: params.feature,
449
+ hint: "plan inspection and workload statistics are PostgreSQL-shaped; portable operation tracing works on every dialect"
450
+ });
451
+ }
452
+ }
453
+ function renderPostgresWorkload(params) {
454
+ const { dialect, query, schema, table, limit } = params;
455
+ requirePostgres({ dialect, feature: `the "${query}" diagnostic read` });
456
+ const collector = new ParamCollector(dialect);
457
+ let text = POSTGRES_DIAGNOSTIC_SQL[query];
458
+ if (query === "statementInfo" || query === "statementSnapshot") {
459
+ text = text.replace("%SCHEMA%", dialect.quoteIdent({ name: requireArg({ value: schema, name: "schema", query }) }));
460
+ }
461
+ if (query === "statementSnapshot") {
462
+ const bounded = limit ?? 20;
463
+ if (!Number.isInteger(bounded) || bounded < POSTGRES_WORKLOAD_LIMITS.minLimit || bounded > POSTGRES_WORKLOAD_LIMITS.maxLimit) {
464
+ throw new VibeError2({
465
+ code: "VIBE_CONFIG",
466
+ message: `workload snapshot limit must be an integer between ${POSTGRES_WORKLOAD_LIMITS.minLimit} and ${POSTGRES_WORKLOAD_LIMITS.maxLimit}, got ${String(limit)}`,
467
+ meta: { limit }
468
+ });
469
+ }
470
+ text = text.replace("%LIMIT%", collector.add(bounded));
471
+ }
472
+ if (query === "setContext") {
473
+ text = text.replace("%KEY%", collector.add(requireArg({ value: params.key, name: "key", query }))).replace("%VALUE%", collector.add(params.value ?? ""));
474
+ }
475
+ if (query === "indexes" || query === "tableStats") {
476
+ text = text.replace("%SCHEMA_PARAM%", collector.add(requireArg({ value: schema, name: "schema", query }))).replace("%TABLE_PARAM%", collector.add(requireArg({ value: table, name: "table", query })));
477
+ }
478
+ return { text, values: collector.values, returning: "none" };
479
+ }
480
+ function requireArg(params) {
481
+ const { value, name, query } = params;
482
+ if (value === undefined || value.length === 0) {
483
+ throw new VibeError2({
484
+ code: "VIBE_CONFIG",
485
+ message: `the "${query}" diagnostic read requires a ${name}`,
486
+ meta: { query, argument: name }
487
+ });
488
+ }
489
+ return value;
490
+ }
491
+
492
+ // packages/sql/src/dialects/postgres-rls-readiness.ts
493
+ import { unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
494
+
495
+ // packages/sql/src/dialects/postgres-rls.ts
496
+ import { RlsContextError, VibeError as VibeError3, assertValidSchemaIR, unsupportedCapability as unsupportedCapability3, validateRlsContextIR } from "@vibeorm/schema";
497
+ var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
498
+ var COLUMN_TYPES = {
499
+ String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
500
+ Boolean: { default: "boolean", native: { boolean: "boolean", bool: "boolean" } },
501
+ Int: { default: "integer", native: { integer: "integer", int: "integer", int4: "integer", smallint: "smallint", int2: "smallint" } },
502
+ BigInt: { default: "bigint", native: { bigint: "bigint", int8: "bigint" } },
503
+ Float: { default: "double precision", native: { doubleprecision: "double precision", float8: "double precision", real: "real", float4: "real" } },
504
+ Decimal: { default: "numeric", native: { numeric: "numeric", decimal: "numeric" } },
505
+ DateTime: { default: "timestamptz", native: { timestamptz: "timestamptz", timestampwithtimezone: "timestamptz", timestamp: "timestamp", timestampwithouttimezone: "timestamp", date: "date" } }
506
+ };
507
+ function requireRls(params) {
508
+ if (!params.dialect.capabilities.nativeRowLevelSecurity) {
509
+ throw unsupportedCapability3({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
510
+ }
511
+ }
512
+ function identifier(params) {
513
+ if (typeof params.name !== "string" || params.name.length === 0 || params.name.includes("\x00") || new TextEncoder().encode(params.name).length > 63) {
514
+ throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS identifiers must be nonempty PostgreSQL identifiers of at most 63 bytes" });
515
+ }
516
+ return params.dialect.quoteIdent({ name: params.name });
517
+ }
518
+ function tableRef(params) {
519
+ if (params.schema !== undefined && params.schema !== "public" && params.schema !== "pg_temp") {
520
+ throw unsupportedCapability3({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: "Only public tables and temporary policy probes are supported" });
521
+ }
522
+ return `${identifier({ dialect: params.dialect, name: params.schema ?? NATIVE_RLS.tableSchema })}.${identifier({ dialect: params.dialect, name: params.table })}`;
523
+ }
524
+ function query(params) {
525
+ return { text: params.text, values: params.values ?? [], returning: "none" };
526
+ }
527
+ function renderRlsEnable(params) {
528
+ requireRls(params);
529
+ return query({ text: `ALTER TABLE ${tableRef(params)} ENABLE ROW LEVEL SECURITY` });
530
+ }
531
+ function renderRlsForce(params) {
532
+ requireRls(params);
533
+ return query({ text: `ALTER TABLE ${tableRef(params)} FORCE ROW LEVEL SECURITY` });
534
+ }
535
+ function renderRlsDisable({ dialect, table }) {
536
+ requireRls({ dialect });
537
+ return query({ text: `ALTER TABLE ${tableRef({ dialect, table })} DISABLE ROW LEVEL SECURITY` });
538
+ }
539
+ function renderRlsNoForce({ dialect, table }) {
540
+ requireRls({ dialect });
541
+ return query({ text: `ALTER TABLE ${tableRef({ dialect, table })} NO FORCE ROW LEVEL SECURITY` });
542
+ }
543
+ function renderRlsDropPolicy({ dialect, table, policyName }) {
544
+ requireRls({ dialect });
545
+ const name = identifier({ dialect, name: policyName ?? NATIVE_RLS.policyName });
546
+ return query({ text: `DROP POLICY ${name} ON ${tableRef({ dialect, table })}` });
547
+ }
548
+ function renderRlsColumnType(params) {
549
+ requireRls(params);
550
+ const { field } = params;
551
+ const mapping = typeof field.type === "string" && Object.hasOwn(COLUMN_TYPES, field.type) ? COLUMN_TYPES[field.type] : undefined;
552
+ if (mapping !== undefined && !field.isList && !field.isUnsupported) {
553
+ if (field.nativeType === undefined)
554
+ return mapping.default;
555
+ const match = /^([a-z][a-z0-9 ]*)(?:\(\s*\d+(?:\s*,\s*\d+)?\s*\))?$/i.exec(field.nativeType.trim());
556
+ const base = match?.[1]?.toLowerCase().replace(/\s+/g, "");
557
+ const native = base !== undefined && Object.hasOwn(mapping.native, base) ? mapping.native[base] : undefined;
558
+ if (native !== undefined)
559
+ return native;
560
+ }
561
+ throw unsupportedCapability3({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity", hint: `Unsupported physical ownership type on field ${field.name}` });
562
+ }
563
+ function protectedModel(params) {
564
+ requireRls(params);
565
+ assertValidSchemaIR({ schema: params.schema });
566
+ const model = params.schema.models.find((entry) => entry.name === params.model);
567
+ if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
568
+ throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS policy rendering requires a protected model", meta: { model: params.model } });
569
+ }
570
+ return model;
571
+ }
572
+ function renderRlsPredicate(params) {
573
+ const model = protectedModel(params);
574
+ return predicate({ dialect: params.dialect, schema: params.schema, model });
575
+ }
576
+ function predicate(params) {
577
+ const { dialect, schema, model } = params;
578
+ const rule = model.rls;
579
+ if (rule === "deny")
580
+ return "false";
581
+ if (typeof rule === "object" && "where" in rule) {
582
+ return Object.entries(rule.where).map(([name, ref]) => {
583
+ const field = model.fields.find((entry) => entry.name === name);
584
+ const column = identifier({ dialect, name: field.dbName ?? field.name });
585
+ const physicalType = renderRlsColumnType({ dialect, field });
586
+ return `(${column} = NULLIF(current_setting('${NATIVE_RLS.settingPrefix}${ref.context}', true), '')::${physicalType})`;
587
+ }).join(" AND ");
588
+ }
589
+ if (typeof rule === "object" && "through" in rule) {
590
+ const relation = model.relations.find((entry) => entry.name === rule.through);
591
+ const parent = schema.models.find((entry) => entry.name === relation.targetModel);
592
+ const alias = identifier({ dialect, name: NATIVE_RLS.parentAlias });
593
+ const child = identifier({ dialect, name: model.dbName ?? model.name });
594
+ const join = relation.fields.map((name, index) => {
595
+ const childField = model.fields.find((field) => field.name === name);
596
+ const parentField = parent.fields.find((field) => field.name === relation.references[index]);
597
+ return `(${alias}.${identifier({ dialect, name: parentField.dbName ?? parentField.name })} = ${child}.${identifier({ dialect, name: childField.dbName ?? childField.name })})`;
598
+ }).join(" AND ");
599
+ return `EXISTS (SELECT 1 FROM ${tableRef({ dialect, table: parent.dbName ?? parent.name })} AS ${alias} WHERE ${join})`;
600
+ }
601
+ throw new VibeError3({ code: "VIBE_SCHEMA_INVALID", message: "Native RLS requires a protected policy rule" });
602
+ }
603
+ function renderRlsPolicy(params) {
604
+ const model = protectedModel(params);
605
+ const expression = predicate({ dialect: params.dialect, schema: params.schema, model });
606
+ const name = identifier({ dialect: params.dialect, name: params.policyName ?? NATIVE_RLS.policyName });
607
+ const table = tableRef({ dialect: params.dialect, table: model.dbName ?? model.name, schema: params.targetSchema });
608
+ return query({ text: `CREATE POLICY ${name} ON ${table} AS PERMISSIVE FOR ALL TO PUBLIC USING (${expression}) WITH CHECK (${expression})` });
609
+ }
610
+ function renderRlsContext(params) {
611
+ requireRls(params);
612
+ if (validateRlsContextIR({ context: params.context }).length !== 0 || Object.keys(params.context).length === 0 || typeof params.values !== "object" || params.values === null || Array.isArray(params.values)) {
613
+ throw new RlsContextError({ reason: "invalid" });
614
+ }
615
+ const collector = new ParamCollector(params.dialect);
616
+ const parts = [];
617
+ for (const [key, slot] of Object.entries(params.context)) {
618
+ if (!Object.hasOwn(params.values, key))
619
+ throw new RlsContextError({ reason: "missing", key });
620
+ const value = params.values[key];
621
+ if (value === null && !slot.nullable || value !== null && (typeof value !== "string" || value.length === 0 || value.includes("\x00"))) {
622
+ throw new RlsContextError({ reason: "invalid", key });
623
+ }
624
+ parts.push(`set_config(${collector.add(`${NATIVE_RLS.settingPrefix}${key}`)}, ${collector.add(value ?? "")}, true)`);
625
+ }
626
+ for (const key of Reflect.ownKeys(params.values)) {
627
+ if (typeof key !== "string" || !Object.hasOwn(params.context, key))
628
+ throw new RlsContextError({ reason: "invalid" });
629
+ }
630
+ return query({ text: `SELECT ${parts.join(", ")}`, values: collector.values });
631
+ }
632
+ function renderRlsRole(params) {
633
+ requireRls(params);
634
+ if (typeof params.role !== "string" || params.role.trim().length === 0 || params.role.includes("\x00")) {
635
+ throw new VibeError3({ code: "VIBE_CONFIG", message: "PGlite native RLS requires a trusted nonempty runtime role name" });
636
+ }
637
+ const collector = new ParamCollector(params.dialect);
638
+ return query({ text: `SELECT set_config('role', ${collector.add(params.role)}, true)`, values: collector.values });
639
+ }
640
+
641
+ // packages/sql/src/dialects/postgres-rls-readiness.ts
642
+ var NATIVE_RLS_PROBE_POLICY = "vibeorm_probe";
643
+ var RLS_READINESS_SQL = Object.freeze({
644
+ identity: `SELECT current_user::text AS "currentUser",
645
+ session_user::text AS "sessionUser",
646
+ r.rolsuper AS "super",
647
+ r.rolbypassrls AS "bypassRls",
648
+ r.rolcreatedb AS "createDb",
649
+ r.rolcreaterole AS "createRole",
650
+ pg_catalog.current_setting('server_version_num') AS "serverVersionNum",
651
+ pg_catalog.pg_backend_pid()::text AS "backendPid",
652
+ pg_catalog.pg_postmaster_start_time()::text AS "instanceStart",
653
+ (SELECT a.backend_start::text FROM pg_catalog.pg_stat_activity a WHERE a.pid = pg_catalog.pg_backend_pid()) AS "backendStart",
654
+ pg_catalog.has_database_privilege(current_user, pg_catalog.current_database(), 'TEMP') AS "tempPrivilege"
655
+ FROM pg_catalog.pg_roles r
656
+ WHERE r.rolname = current_user`,
657
+ elevatedRoles: `SELECT r.rolname::text AS "role",
658
+ r.rolsuper AS "super",
659
+ r.rolbypassrls AS "bypassRls",
660
+ r.rolcreaterole AS "createRole",
661
+ r.rolcreatedb AS "createDb"
662
+ FROM pg_catalog.pg_roles r
663
+ WHERE r.rolname <> current_user
664
+ AND pg_catalog.pg_has_role(current_user, r.oid, 'MEMBER')
665
+ AND (r.rolsuper OR r.rolbypassrls OR r.rolcreaterole OR r.rolcreatedb OR r.rolname LIKE 'pg\\_%')
666
+ ORDER BY r.rolname`,
667
+ schemaControl: `SELECT n.nspname::text AS "schema",
668
+ pg_catalog.pg_get_userbyid(n.nspowner)::text AS "owner",
669
+ pg_catalog.pg_has_role(current_user, n.nspowner, 'MEMBER') AS "ownerAccess",
670
+ EXISTS (
671
+ SELECT 1 FROM pg_catalog.pg_roles r
672
+ WHERE pg_catalog.pg_has_role(current_user, r.oid, 'MEMBER')
673
+ AND pg_catalog.has_schema_privilege(r.oid, n.oid, 'CREATE')
674
+ ) AS "create",
675
+ pg_catalog.current_database()::text AS "database",
676
+ pg_catalog.pg_get_userbyid(d.datdba)::text AS "databaseOwner",
677
+ pg_catalog.pg_has_role(current_user, d.datdba, 'MEMBER') AS "databaseOwnerAccess"
678
+ FROM pg_catalog.pg_namespace n
679
+ JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database()
680
+ WHERE n.nspname = $1`,
681
+ tables: `SELECT c.relname::text AS "table",
682
+ c.relrowsecurity AS "rlsEnabled",
683
+ c.relforcerowsecurity AS "rlsForced",
684
+ pg_catalog.pg_get_userbyid(c.relowner)::text AS "owner",
685
+ pg_catalog.pg_has_role(current_user, c.relowner, 'MEMBER') AS "ownerAccess",
686
+ EXISTS (
687
+ SELECT 1 FROM pg_catalog.pg_roles r
688
+ WHERE pg_catalog.pg_has_role(current_user, r.oid, 'MEMBER')
689
+ AND pg_catalog.has_table_privilege(r.oid, c.oid, 'TRUNCATE')
690
+ ) AS "truncate"
691
+ FROM pg_catalog.pg_class c
692
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
693
+ WHERE n.nspname = $1 AND c.relkind IN ('r', 'p')
694
+ ORDER BY c.relname`,
695
+ policies: `SELECT c.relname::text AS "table",
696
+ p.polname::text AS "name",
697
+ p.polpermissive AS "permissive",
698
+ p.polcmd::text AS "command",
699
+ (p.polroles = '{0}'::oid[]) AS "toPublic",
700
+ (p.polqual IS NOT NULL) AS "hasUsing",
701
+ (p.polwithcheck IS NOT NULL) AS "hasWithCheck"
702
+ FROM pg_catalog.pg_policy p
703
+ JOIN pg_catalog.pg_class c ON c.oid = p.polrelid
704
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
705
+ WHERE n.nspname = $1
706
+ ORDER BY c.relname, p.polname`,
707
+ expressions: `SELECT c.relname::text AS "table",
708
+ p.polname::text AS "name",
709
+ (n.nspname <> $1) AS "probe",
710
+ pg_catalog.pg_get_expr(p.polqual, p.polrelid) AS "using",
711
+ pg_catalog.pg_get_expr(p.polwithcheck, p.polrelid) AS "withCheck"
712
+ FROM pg_catalog.pg_policy p
713
+ JOIN pg_catalog.pg_class c ON c.oid = p.polrelid
714
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
715
+ WHERE (n.nspname = $1 AND p.polname = $2)
716
+ OR (c.relnamespace = pg_catalog.pg_my_temp_schema() AND p.polname = $3)
717
+ ORDER BY c.relname, p.polname`,
718
+ views: `SELECT n.nspname::text AS "schema",
719
+ c.relname::text AS "name",
720
+ pg_catalog.pg_get_userbyid(c.relowner)::text AS "owner",
721
+ (c.relkind = 'm') AS "materialized",
722
+ (COALESCE((
723
+ SELECT o.option_value FROM pg_catalog.pg_options_to_table(c.reloptions) AS o
724
+ WHERE o.option_name = 'security_invoker'
725
+ ), 'false') = 'true') AS "securityInvoker"
726
+ FROM pg_catalog.pg_class c
727
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
728
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
729
+ AND c.relkind IN ('v', 'm')
730
+ AND EXISTS (
731
+ SELECT 1 FROM pg_catalog.pg_roles r
732
+ WHERE pg_catalog.pg_has_role(current_user, r.oid, 'MEMBER')
733
+ AND pg_catalog.has_schema_privilege(r.oid, n.oid, 'USAGE')
734
+ AND (pg_catalog.has_table_privilege(r.oid, c.oid, 'SELECT')
735
+ OR pg_catalog.has_any_column_privilege(r.oid, c.oid, 'SELECT'))
736
+ )
737
+ ORDER BY n.nspname, c.relname`,
738
+ routines: `SELECT n.nspname::text AS "schema",
739
+ p.proname::text AS "name",
740
+ pg_catalog.pg_get_userbyid(p.proowner)::text AS "owner"
741
+ FROM pg_catalog.pg_proc p
742
+ JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
743
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
744
+ AND p.prosecdef
745
+ AND EXISTS (
746
+ SELECT 1 FROM pg_catalog.pg_roles r
747
+ WHERE pg_catalog.pg_has_role(current_user, r.oid, 'MEMBER')
748
+ AND pg_catalog.has_schema_privilege(r.oid, n.oid, 'USAGE')
749
+ AND pg_catalog.has_function_privilege(r.oid, p.oid, 'EXECUTE')
750
+ )
751
+ ORDER BY n.nspname, p.proname`,
752
+ triggers: `SELECT c.relname::text AS "table",
753
+ t.tgname::text AS "name",
754
+ f.proname::text AS "function",
755
+ pg_catalog.pg_get_userbyid(f.proowner)::text AS "owner",
756
+ f.prosecdef AS "securityDefiner"
757
+ FROM pg_catalog.pg_trigger t
758
+ JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
759
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
760
+ JOIN pg_catalog.pg_proc f ON f.oid = t.tgfoid
761
+ WHERE n.nspname = $1
762
+ AND NOT t.tgisinternal
763
+ AND f.prosecdef
764
+ ORDER BY c.relname, t.tgname`,
765
+ readSearchPath: `SELECT pg_catalog.current_setting('search_path') AS "searchPath"`,
766
+ setSearchPath: `SELECT pg_catalog.set_config('search_path', $1, true)`
767
+ });
768
+ function requireRls2(params) {
769
+ if (!params.dialect.capabilities.nativeRowLevelSecurity) {
770
+ throw unsupportedCapability4({ dialect: params.dialect.name, feature: "nativeRowLevelSecurity" });
771
+ }
772
+ }
773
+ function renderRlsReadinessQuery(params) {
774
+ requireRls2(params);
775
+ const values = params.query === "setSearchPath" ? [params.searchPath ?? ""] : [NATIVE_RLS.tableSchema, NATIVE_RLS.policyName, NATIVE_RLS_PROBE_POLICY];
776
+ const collector = new ParamCollector(params.dialect);
777
+ const slots = new Map;
778
+ const text = RLS_READINESS_SQL[params.query].replace(/\$(\d+)/g, (slot, index) => {
779
+ let placeholder = slots.get(slot);
780
+ if (placeholder === undefined) {
781
+ placeholder = collector.add(values[Number(index) - 1]);
782
+ slots.set(slot, placeholder);
783
+ }
784
+ return placeholder;
785
+ });
786
+ return { text, values: collector.values, returning: "none" };
787
+ }
788
+ function renderRlsProbeTable(params) {
789
+ requireRls2(params);
790
+ const name = params.dialect.quoteIdent({ name: params.table });
791
+ const schema = params.dialect.quoteIdent({ name: params.action === "create" ? NATIVE_RLS.tableSchema : "pg_temp" });
792
+ const text = params.action === "create" ? `CREATE TEMP TABLE ${name} (LIKE ${schema}.${name}) ON COMMIT DROP` : `DROP TABLE ${schema}.${name}`;
793
+ return { text, values: [], returning: "none" };
794
+ }
795
+ function renderRlsPolicyPath(params) {
796
+ requireRls2(params);
797
+ const text = params.stage === "save" ? "SELECT pg_catalog.set_config('vibeorm.rls_saved_search_path', pg_catalog.current_setting('search_path'), true)" : params.stage === "empty" ? "SELECT pg_catalog.set_config('search_path', '', true)" : "SELECT pg_catalog.set_config('search_path', pg_catalog.current_setting('vibeorm.rls_saved_search_path'), true)";
798
+ return { text, values: [], returning: "none" };
799
+ }
800
+
370
801
  // packages/sql/src/render.ts
371
- import { internalError, unsupportedCapability } from "@vibeorm/schema";
802
+ import { internalError, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
372
803
 
373
804
  // packages/sql/src/like.ts
374
805
  function escapeLikePattern(params) {
@@ -395,28 +826,23 @@ function strictMatchPattern(params) {
395
826
  return op === "contains" ? containsPattern({ raw: value }) : op === "startsWith" ? startsWithPattern({ raw: value }) : endsWithPattern({ raw: value });
396
827
  }
397
828
 
398
- // packages/sql/src/params.ts
399
- class ParamCollector {
400
- dialect;
401
- values;
402
- constructor(dialect) {
403
- this.dialect = dialect;
404
- this.values = [];
405
- }
406
- add(value) {
407
- this.values.push(value);
408
- return this.dialect.placeholder({ index: this.values.length });
409
- }
410
- addMany(values) {
411
- return values.map((value) => this.add(value));
412
- }
413
- }
414
-
415
829
  // packages/sql/src/render.ts
416
830
  function renderStatement(params) {
417
831
  const { dialect, statement } = params;
418
832
  const collector = new ParamCollector(dialect);
419
833
  switch (statement.kind) {
834
+ case "diagnostic": {
835
+ if (isFixedDiagnostic(statement))
836
+ return renderDiagnostic({ dialect, query: statement.query });
837
+ if (statement.query === "postgresExplain") {
838
+ return renderPostgresExplain({
839
+ dialect,
840
+ query: renderStatement({ dialect, statement: statement.statement }),
841
+ ...statement.options === undefined ? {} : { options: statement.options }
842
+ });
843
+ }
844
+ return renderPostgresWorkload({ dialect, ...statement });
845
+ }
420
846
  case "select":
421
847
  return renderSelect({ dialect, statement, collector });
422
848
  case "insert":
@@ -425,6 +851,28 @@ function renderStatement(params) {
425
851
  return renderUpdate({ dialect, statement, collector });
426
852
  case "delete":
427
853
  return renderDelete({ dialect, statement, collector });
854
+ case "rlsReadiness":
855
+ return renderRlsReadinessQuery({ dialect, ...statement });
856
+ case "rlsProbeTable":
857
+ return renderRlsProbeTable({ dialect, ...statement });
858
+ case "rlsPolicyPath":
859
+ return renderRlsPolicyPath({ dialect, ...statement });
860
+ case "rlsEnable":
861
+ return renderRlsEnable({ dialect, table: statement.table });
862
+ case "rlsForce":
863
+ return renderRlsForce({ dialect, table: statement.table });
864
+ case "rlsDisable":
865
+ return renderRlsDisable({ dialect, table: statement.table });
866
+ case "rlsNoForce":
867
+ return renderRlsNoForce({ dialect, table: statement.table });
868
+ case "rlsDropPolicy":
869
+ return renderRlsDropPolicy({ dialect, table: statement.table, policyName: statement.policyName });
870
+ case "rlsPolicy":
871
+ return renderRlsPolicy({ dialect, ...statement });
872
+ case "rlsContext":
873
+ return renderRlsContext({ dialect, context: statement.context, values: statement.values });
874
+ case "rlsRole":
875
+ return renderRlsRole({ dialect, role: statement.role });
428
876
  }
429
877
  }
430
878
  function quoteCol(dialect, column) {
@@ -494,7 +942,7 @@ function renderAggRef(dialect, agg) {
494
942
  function assertScalarArrays(dialect, feature) {
495
943
  if (dialect.capabilities.scalarArrays)
496
944
  return;
497
- throw unsupportedCapability({
945
+ throw unsupportedCapability5({
498
946
  dialect: dialect.name,
499
947
  feature,
500
948
  hint: "only postgres has native array columns \u2014 model the list as a related table instead"
@@ -534,6 +982,33 @@ function renderExpr(params) {
534
982
  const placeholders = collector.addMany(expr.values);
535
983
  return `${column} ${expr.negate === true ? "NOT IN" : "IN"} (${placeholders.join(", ")})`;
536
984
  }
985
+ case "tupleIn": {
986
+ if (expr.columns.length === 0) {
987
+ throw internalError({ message: "tupleIn needs at least one column" });
988
+ }
989
+ for (const row of expr.rows) {
990
+ if (row.length !== expr.columns.length) {
991
+ throw internalError({
992
+ message: `tupleIn row has ${row.length} values for ${expr.columns.length} columns`
993
+ });
994
+ }
995
+ }
996
+ if (expr.rows.length === 0) {
997
+ return expr.negate === true ? "1 = 1" : "1 = 0";
998
+ }
999
+ const columns = expr.columns.map((column) => quoteCol(dialect, column));
1000
+ if (dialect.tupleInStrategy === "rowValue") {
1001
+ const tuples = expr.rows.map((row) => `(${collector.addMany(row).join(", ")})`);
1002
+ const base2 = `(${columns.join(", ")}) IN (${tuples.join(", ")})`;
1003
+ return expr.negate === true ? `NOT (${base2})` : base2;
1004
+ }
1005
+ const conjunctions = expr.rows.map((row) => {
1006
+ const terms = row.map((value, index) => `${columns[index]} = ${collector.add(value)}`);
1007
+ return terms.length === 1 ? terms[0] : `(${terms.join(" AND ")})`;
1008
+ });
1009
+ const base = conjunctions.length === 1 ? conjunctions[0] : `(${conjunctions.join(" OR ")})`;
1010
+ return expr.negate === true ? `NOT ${base.startsWith("(") ? base : `(${base})`}` : base;
1011
+ }
537
1012
  case "like": {
538
1013
  const column = quoteCol(dialect, expr.column);
539
1014
  const placeholder = collector.add(expr.pattern);
@@ -675,7 +1150,7 @@ function renderJsonLike(params) {
675
1150
  function renderJsonFilter(params) {
676
1151
  const { dialect, expr, collector } = params;
677
1152
  if (!dialect.supportedJsonOps.includes(expr.op)) {
678
- throw unsupportedCapability({
1153
+ throw unsupportedCapability5({
679
1154
  dialect: dialect.name,
680
1155
  feature: `the json filter "${expr.op}"`,
681
1156
  hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
@@ -730,7 +1205,7 @@ function renderJsonFilter(params) {
730
1205
  const elements = Array.isArray(expr.value) ? expr.value : [expr.value];
731
1206
  const tests = elements.map((element) => {
732
1207
  if (element === null || typeof element === "object" && element !== null) {
733
- throw unsupportedCapability({
1208
+ throw unsupportedCapability5({
734
1209
  dialect: dialect.name,
735
1210
  feature: "json array_contains with object/array/null elements",
736
1211
  hint: "sqlite expresses scalar-element containment only \u2014 deep structural containment needs postgres or mysql"
@@ -853,7 +1328,7 @@ function renderSelect(params) {
853
1328
  let distinctPrefix = "";
854
1329
  if (statement.distinctOn !== undefined && statement.distinctOn.length > 0) {
855
1330
  if (!dialect.capabilities.distinctOn) {
856
- throw unsupportedCapability({
1331
+ throw unsupportedCapability5({
857
1332
  dialect: dialect.name,
858
1333
  feature: "SELECT DISTINCT ON",
859
1334
  hint: "postgres-only \u2014 the runtime emulates distinct client-side on this dialect"
@@ -884,7 +1359,7 @@ function renderSelect(params) {
884
1359
  }
885
1360
  if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
886
1361
  if (!dialect.capabilities.lateralJoin) {
887
- throw unsupportedCapability({
1362
+ throw unsupportedCapability5({
888
1363
  dialect: dialect.name,
889
1364
  feature: "LATERAL JOIN relation loading",
890
1365
  hint: 'use the portable "query" relation strategy on this dialect'
@@ -958,13 +1433,16 @@ function renderInsert(params) {
958
1433
  return "DEFAULT";
959
1434
  }).join(", ")})`).join(", ");
960
1435
  let text = `${insertKeyword} ${renderTable(dialect, statement.table)} (${columnList}) VALUES ${rowsSql}`;
1436
+ if (onConflict !== undefined && !("doNothing" in onConflict) && dialect.excludedRowRef.form === "rowAlias" && Object.values(onConflict.update).some((value) => isExcludedRef(value))) {
1437
+ text += ` AS ${dialect.quoteIdent({ name: dialect.excludedRowRef.alias })}`;
1438
+ }
961
1439
  if (onConflict !== undefined) {
962
1440
  if ("doNothing" in onConflict) {
963
1441
  if (dialect.doNothingForm === "onConflictDoNothing") {
964
1442
  let target = "";
965
1443
  if (onConflict.constraint !== undefined) {
966
1444
  if (dialect.name !== "postgres") {
967
- throw unsupportedCapability({
1445
+ throw unsupportedCapability5({
968
1446
  dialect: dialect.name,
969
1447
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
970
1448
  hint: "only postgres names constraints in conflict targets \u2014 use a column-list target"
@@ -979,7 +1457,7 @@ function renderInsert(params) {
979
1457
  }
980
1458
  text += ` ON CONFLICT${target} DO NOTHING`;
981
1459
  } else if (onConflict.constraint !== undefined || onConflict.conflictWhere !== undefined) {
982
- throw unsupportedCapability({
1460
+ throw unsupportedCapability5({
983
1461
  dialect: dialect.name,
984
1462
  feature: "a refined conflict target (constraint / partial-index predicate) on INSERT IGNORE",
985
1463
  hint: "mysql skips on any duplicate key \u2014 drop the refinement or gate the call per dialect"
@@ -991,28 +1469,41 @@ function renderInsert(params) {
991
1469
  throw internalError({ message: "upsert update must set at least one column" });
992
1470
  }
993
1471
  const selfRef = dialect.quoteIdent({ name: statement.table.name });
994
- const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef });
1472
+ const assignments = renderAssignments({ dialect, entries: updates, collector, selfRef, allowExcluded: true });
995
1473
  if (dialect.upsertForm === "onConflict") {
996
1474
  if (onConflict.lastInsertIdColumn !== undefined) {
997
1475
  throw internalError({ message: "lastInsertIdColumn is a mysql (onDuplicateKey) construct" });
998
1476
  }
999
1477
  if (onConflict.constraint !== undefined && dialect.name !== "postgres") {
1000
- throw unsupportedCapability({
1478
+ throw unsupportedCapability5({
1001
1479
  dialect: dialect.name,
1002
1480
  feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
1003
1481
  hint: "sqlite conflict targets are column lists \u2014 name the indexed columns instead"
1004
1482
  });
1005
1483
  }
1006
- const target = onConflict.constraint !== undefined ? ` ON CONSTRAINT ${dialect.quoteIdent({ name: onConflict.constraint })}` : ` (${onConflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")})`;
1484
+ if (onConflict.constraint !== undefined && onConflict.conflictWhere !== undefined) {
1485
+ throw internalError({
1486
+ message: "a named-constraint conflict target already identifies the index \u2014 it cannot carry a partial predicate too"
1487
+ });
1488
+ }
1489
+ const partial = onConflict.conflictWhere === undefined ? "" : ` WHERE ${onConflict.conflictWhere}`;
1490
+ const target = onConflict.constraint !== undefined ? ` ON CONSTRAINT ${dialect.quoteIdent({ name: onConflict.constraint })}` : ` (${onConflict.columns.map((name) => dialect.quoteIdent({ name })).join(", ")})${partial}`;
1007
1491
  text += ` ON CONFLICT${target} DO UPDATE SET ${assignments}`;
1008
1492
  } else {
1009
1493
  if (onConflict.constraint !== undefined) {
1010
- throw unsupportedCapability({
1494
+ throw unsupportedCapability5({
1011
1495
  dialect: dialect.name,
1012
1496
  feature: "a named-constraint conflict target on ON DUPLICATE KEY UPDATE",
1013
1497
  hint: "mysql's ODKU fires on any duplicate key \u2014 no target can be named"
1014
1498
  });
1015
1499
  }
1500
+ if (onConflict.conflictWhere !== undefined) {
1501
+ throw unsupportedCapability5({
1502
+ dialect: dialect.name,
1503
+ feature: "a partial-index conflict target on ON DUPLICATE KEY UPDATE",
1504
+ hint: "mysql's ODKU fires on any duplicate key \u2014 a partial arbiter cannot be selected; gate the call per dialect"
1505
+ });
1506
+ }
1016
1507
  const trick = onConflict.lastInsertIdColumn === undefined ? "" : `, ${dialect.quoteIdent({ name: onConflict.lastInsertIdColumn })} = LAST_INSERT_ID(${dialect.quoteIdent({ name: onConflict.lastInsertIdColumn })})`;
1017
1508
  text += ` ON DUPLICATE KEY UPDATE ${assignments}${trick}`;
1018
1509
  }
@@ -1035,11 +1526,25 @@ function renderUpdate(params) {
1035
1526
  const returning = applyReturning({ dialect, requested: statement.returning });
1036
1527
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
1037
1528
  }
1529
+ function renderIncomingRowColumn(params) {
1530
+ const { dialect, column } = params;
1531
+ const ref = dialect.excludedRowRef;
1532
+ const qualifier = ref.form === "keyword" ? ref.name : dialect.quoteIdent({ name: ref.alias });
1533
+ return `${qualifier}.${dialect.quoteIdent({ name: column })}`;
1534
+ }
1038
1535
  function renderAssignments(params) {
1039
1536
  const { dialect, entries, collector, selfRef } = params;
1040
1537
  return entries.map(([name, value]) => {
1041
1538
  const column = dialect.quoteIdent({ name });
1042
1539
  const read = selfRef === undefined ? column : `${selfRef}.${column}`;
1540
+ if (isExcludedRef(value)) {
1541
+ if (params.allowExcluded !== true) {
1542
+ throw internalError({
1543
+ message: `an incoming-row reference (${value.column}) is only meaningful inside an upsert's update arm`
1544
+ });
1545
+ }
1546
+ return `${column} = ${renderIncomingRowColumn({ dialect, column: value.column })}`;
1547
+ }
1043
1548
  if (isJsonParamCell(value)) {
1044
1549
  return `${column} = ${collector.add(value.text)}${dialect.jsonParamCast}`;
1045
1550
  }
@@ -1068,13 +1573,143 @@ function renderDelete(params) {
1068
1573
  const returning = applyReturning({ dialect, requested: statement.returning });
1069
1574
  return { text: text + returning.suffix, values: collector.values, returning: returning.status };
1070
1575
  }
1576
+ // packages/sql/src/dialects/postgres-migration.ts
1577
+ import { VibeError as VibeError4, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
1578
+ var SETTING_VALUE = /^[0-9]+(?:\.[0-9]+)?\s*(?:us|ms|s|min|h|d)?$/;
1579
+ function requirePostgres2(params) {
1580
+ if (params.dialect.name !== "postgres") {
1581
+ throw unsupportedCapability6({ dialect: params.dialect.name, feature: params.feature });
1582
+ }
1583
+ }
1584
+ function renderMigrationSessionRead(params) {
1585
+ requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
1586
+ return {
1587
+ text: `SELECT pg_catalog.current_setting('lock_timeout') AS "lockTimeout", pg_catalog.current_setting('statement_timeout') AS "statementTimeout"`,
1588
+ values: [],
1589
+ returning: "none"
1590
+ };
1591
+ }
1592
+ function renderMigrationSessionSet(params) {
1593
+ requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
1594
+ const { milliseconds, setting } = params;
1595
+ if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) {
1596
+ throw new VibeError4({
1597
+ code: "VIBE_MIGRATION",
1598
+ message: `${setting} must be a non-negative integer number of milliseconds (got ${String(milliseconds)})`,
1599
+ meta: { reason: "execution-metadata-invalid", setting, value: milliseconds }
1600
+ });
1601
+ }
1602
+ return { text: `SET ${params.local === true ? "LOCAL " : ""}${setting} = '${milliseconds}ms'`, values: [], returning: "none" };
1603
+ }
1604
+ function renderMigrationSessionRestore(params) {
1605
+ requirePostgres2({ dialect: params.dialect, feature: "migrationSessionSettings" });
1606
+ const trimmed = params.value.trim();
1607
+ const text = SETTING_VALUE.test(trimmed) ? `SET ${params.setting} = '${trimmed}'` : `RESET ${params.setting}`;
1608
+ return { text, values: [], returning: "none" };
1609
+ }
1610
+ function renderIndexStateQuery(params) {
1611
+ requirePostgres2({ dialect: params.dialect, feature: "concurrentIndexRecovery" });
1612
+ const collector = new ParamCollector(params.dialect);
1613
+ const name = collector.add(params.index);
1614
+ return {
1615
+ text: `SELECT c.relname::text AS "index",
1616
+ t.relname::text AS "table",
1617
+ am.amname::text AS "method",
1618
+ i.indisunique AS "unique",
1619
+ i.indisvalid AS "valid",
1620
+ i.indisready AS "ready",
1621
+ pg_catalog.pg_get_expr(i.indpred, i.indrelid) AS "predicate",
1622
+ pg_catalog.pg_get_indexdef(i.indexrelid) AS "definition",
1623
+ (SELECT string_agg(pg_catalog.pg_get_indexdef(i.indexrelid, k.k, true), E'\\n' ORDER BY k.k)
1624
+ FROM pg_catalog.generate_series(1, i.indnkeyatts) AS k(k)) AS "columns"
1625
+ FROM pg_catalog.pg_class c
1626
+ JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
1627
+ JOIN pg_catalog.pg_class t ON t.oid = i.indrelid
1628
+ JOIN pg_catalog.pg_am am ON am.oid = c.relam
1629
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1630
+ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${name}`,
1631
+ values: collector.values,
1632
+ returning: "none"
1633
+ };
1634
+ }
1635
+ function renderConstraintStateQuery(params) {
1636
+ requirePostgres2({ dialect: params.dialect, feature: "stagedConstraints" });
1637
+ const collector = new ParamCollector(params.dialect);
1638
+ const table = collector.add(params.table);
1639
+ const constraint = collector.add(params.constraint);
1640
+ return {
1641
+ text: `SELECT con.conname::text AS "constraint",
1642
+ rel.relname::text AS "table",
1643
+ con.contype::text AS "type",
1644
+ con.convalidated AS "validated",
1645
+ pg_catalog.pg_get_constraintdef(con.oid) AS "definition"
1646
+ FROM pg_catalog.pg_constraint con
1647
+ JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
1648
+ JOIN pg_catalog.pg_namespace n ON n.oid = rel.relnamespace
1649
+ WHERE n.nspname = pg_catalog.current_schema() AND rel.relname = ${table} AND con.conname = ${constraint}`,
1650
+ values: collector.values,
1651
+ returning: "none"
1652
+ };
1653
+ }
1654
+ function renderRelationStateQuery(params) {
1655
+ requirePostgres2({ dialect: params.dialect, feature: "renameRecovery" });
1656
+ const collector = new ParamCollector(params.dialect);
1657
+ const table = collector.add(params.table);
1658
+ return {
1659
+ text: `SELECT c.relname::text AS "table", c.relkind::text AS "kind"
1660
+ FROM pg_catalog.pg_class c
1661
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1662
+ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table}`,
1663
+ values: collector.values,
1664
+ returning: "none"
1665
+ };
1666
+ }
1667
+ function renderColumnStateQuery(params) {
1668
+ requirePostgres2({ dialect: params.dialect, feature: "renameRecovery" });
1669
+ const collector = new ParamCollector(params.dialect);
1670
+ const table = collector.add(params.table);
1671
+ const column = collector.add(params.column);
1672
+ return {
1673
+ text: `SELECT a.attname::text AS "column"
1674
+ FROM pg_catalog.pg_attribute a
1675
+ JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
1676
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1677
+ WHERE n.nspname = pg_catalog.current_schema() AND c.relname = ${table} AND a.attname = ${column}
1678
+ AND a.attnum > 0 AND NOT a.attisdropped`,
1679
+ values: collector.values,
1680
+ returning: "none"
1681
+ };
1682
+ }
1071
1683
  export {
1072
1684
  strictMatchPattern,
1073
1685
  startsWithPattern,
1074
1686
  sqliteDialect,
1075
1687
  renderStatement,
1688
+ renderRlsRole,
1689
+ renderRlsReadinessQuery,
1690
+ renderRlsProbeTable,
1691
+ renderRlsPredicate,
1692
+ renderRlsPolicyPath,
1693
+ renderRlsPolicy,
1694
+ renderRlsNoForce,
1695
+ renderRlsForce,
1696
+ renderRlsEnable,
1697
+ renderRlsDropPolicy,
1698
+ renderRlsDisable,
1699
+ renderRlsContext,
1700
+ renderRlsColumnType,
1701
+ renderRelationStateQuery,
1702
+ renderPostgresWorkload,
1703
+ renderPostgresExplain,
1704
+ renderMigrationSessionSet,
1705
+ renderMigrationSessionRestore,
1706
+ renderMigrationSessionRead,
1707
+ renderIndexStateQuery,
1076
1708
  renderFragment,
1077
1709
  renderExpr,
1710
+ renderDiagnostic,
1711
+ renderConstraintStateQuery,
1712
+ renderColumnStateQuery,
1078
1713
  postgresDialect,
1079
1714
  mysqlDialect,
1080
1715
  jsonParamCell,
@@ -1082,10 +1717,14 @@ export {
1082
1717
  isSetOperation,
1083
1718
  isListSetOperation,
1084
1719
  isJsonParamCell,
1720
+ isFixedDiagnostic,
1721
+ isExcludedRef,
1085
1722
  isDbNowMarker,
1086
1723
  getSqlDialect,
1724
+ excludedRef,
1087
1725
  escapeLikePattern,
1088
1726
  escapeGlobPattern,
1727
+ enumMembershipCodec,
1089
1728
  endsWithPattern,
1090
1729
  decodeIsNoop,
1091
1730
  dbNowMarker,
@@ -1094,13 +1733,18 @@ export {
1094
1733
  SQL_DEFAULT_CELL,
1095
1734
  SQLITE_CODECS,
1096
1735
  SQLITE_CAPABILITIES,
1736
+ RLS_READINESS_SQL,
1097
1737
  ParamCollector,
1738
+ POSTGRES_WORKLOAD_LIMITS,
1739
+ POSTGRES_DIAGNOSTIC_SQL,
1098
1740
  POSTGRES_CODECS,
1099
1741
  POSTGRES_CAPABILITIES,
1742
+ NATIVE_RLS_PROBE_POLICY,
1743
+ NATIVE_RLS,
1100
1744
  MYSQL_CODECS,
1101
1745
  MYSQL_CAPABILITIES,
1102
1746
  DIALECT_CODECS,
1103
1747
  DIALECT_CAPABILITIES
1104
1748
  };
1105
1749
 
1106
- //# debugId=758C970CC76B2F4F64756E2164756E21
1750
+ //# debugId=BFF5BB4016D0E8C364756E2164756E21