@dbsp/adapter-pgsql 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1100,9 +1100,8 @@ function nullConstNode() {
1100
1100
  function columnRef(column, table, schema, naming = identityNaming) {
1101
1101
  const fields = [];
1102
1102
  if (schema) {
1103
- const dbSchema = naming.toDatabase(schema);
1104
- validateIdentifier(dbSchema, "schema");
1105
- fields.push(stringNode(dbSchema));
1103
+ validateIdentifier(schema, "schema");
1104
+ fields.push(stringNode(schema));
1106
1105
  }
1107
1106
  if (table) {
1108
1107
  const dbTable = naming.toDatabase(table);
@@ -1133,9 +1132,8 @@ function rangeVar(table, alias, schema, naming = identityNaming) {
1133
1132
  relpersistence: "p"
1134
1133
  };
1135
1134
  if (schema) {
1136
- const dbSchema = naming.toDatabase(schema);
1137
- validateIdentifier(dbSchema, "schema");
1138
- rv.schemaname = dbSchema;
1135
+ validateIdentifier(schema, "schema");
1136
+ rv.schemaname = schema;
1139
1137
  }
1140
1138
  if (alias) {
1141
1139
  const dbAlias = naming.toDatabase(alias);
@@ -1334,7 +1332,8 @@ function insertStmt(options) {
1334
1332
  relpersistence: "p"
1335
1333
  };
1336
1334
  if (options.schema) {
1337
- relation.schemaname = naming.toDatabase(options.schema);
1335
+ validateIdentifier(options.schema, "schema");
1336
+ relation.schemaname = options.schema;
1338
1337
  }
1339
1338
  const stmt = {
1340
1339
  relation
@@ -1364,7 +1363,8 @@ function updateStmt(options) {
1364
1363
  relpersistence: "p"
1365
1364
  };
1366
1365
  if (options.schema) {
1367
- relation.schemaname = naming.toDatabase(options.schema);
1366
+ validateIdentifier(options.schema, "schema");
1367
+ relation.schemaname = options.schema;
1368
1368
  }
1369
1369
  const stmt = {
1370
1370
  relation,
@@ -1392,7 +1392,8 @@ function deleteStmt(options) {
1392
1392
  relpersistence: "p"
1393
1393
  };
1394
1394
  if (options.schema) {
1395
- relation.schemaname = naming.toDatabase(options.schema);
1395
+ validateIdentifier(options.schema, "schema");
1396
+ relation.schemaname = options.schema;
1396
1397
  }
1397
1398
  const stmt = {
1398
1399
  relation
@@ -4634,7 +4635,7 @@ function resolveColumnPgType(columnName, ctx) {
4634
4635
  const column = table.columns.find((c) => c.name === columnName);
4635
4636
  if (!column) return void 0;
4636
4637
  if (column.originalDbType) {
4637
- const targetSchema = ctx.schema !== void 0 ? ctx.naming.toDatabase(ctx.schema) : void 0;
4638
+ const targetSchema = ctx.schema;
4638
4639
  const typeName = renderColumnDbType(column, targetSchema).trim();
4639
4640
  validateDbType(typeName);
4640
4641
  return dbTypeCastTarget(typeName);
@@ -12060,7 +12061,7 @@ function quoteRoleName(name) {
12060
12061
  function qualifyTableIdent(tableName, schemaName, naming) {
12061
12062
  const table = quoteIdent2(naming.toDatabase(tableName), "table");
12062
12063
  if (schemaName) {
12063
- return `${quoteIdent2(naming.toDatabase(schemaName), "schema")}.${table}`;
12064
+ return `${quoteIdent2(schemaName, "schema")}.${table}`;
12064
12065
  }
12065
12066
  return table;
12066
12067
  }
@@ -12118,6 +12119,72 @@ function generateCommentsPhase(ctx) {
12118
12119
  return statements;
12119
12120
  }
12120
12121
 
12122
+ // src/check-constraint-name.ts
12123
+ var CheckConstraintNameCollisionError = class extends Error {
12124
+ constructor(table, firstAuthoredName, secondAuthoredName, databaseName) {
12125
+ super(
12126
+ `CHECK constraint name collision on table "${table}": authored constraints "${firstAuthoredName}" and "${secondAuthoredName}" both resolve to physical name "${databaseName}". Rename one of the constraints.`
12127
+ );
12128
+ this.table = table;
12129
+ this.firstAuthoredName = firstAuthoredName;
12130
+ this.secondAuthoredName = secondAuthoredName;
12131
+ this.databaseName = databaseName;
12132
+ this.name = "CheckConstraintNameCollisionError";
12133
+ }
12134
+ table;
12135
+ firstAuthoredName;
12136
+ secondAuthoredName;
12137
+ databaseName;
12138
+ };
12139
+ function getCheckConstraintDatabaseName(check, naming) {
12140
+ return naming.toDatabase(check.name);
12141
+ }
12142
+ function assertNoCheckConstraintNameCollisions(table, naming) {
12143
+ const seen = /* @__PURE__ */ new Map();
12144
+ for (const check of table.checkConstraints ?? []) {
12145
+ const databaseName = getCheckConstraintDatabaseName(check, naming);
12146
+ const previous = seen.get(databaseName);
12147
+ if (previous !== void 0) {
12148
+ throw new CheckConstraintNameCollisionError(
12149
+ table.name,
12150
+ previous,
12151
+ check.name,
12152
+ databaseName
12153
+ );
12154
+ }
12155
+ seen.set(databaseName, check.name);
12156
+ }
12157
+ }
12158
+
12159
+ // src/check-expression.ts
12160
+ var CHECK_NOT_VALID_SUFFIX = /^(?<clause>\s*CHECK\s*\(.*\)(?:\s+NO\s+INHERIT)?)\s+NOT\s+VALID\s*$/isu;
12161
+ function toCheckConstraintClause(expression) {
12162
+ const trimmed = stripNotValidSuffix(expression.trim()).trim();
12163
+ if (/^CHECK\s*\(/iu.test(trimmed)) {
12164
+ return trimmed;
12165
+ }
12166
+ return `CHECK (${trimmed})`;
12167
+ }
12168
+ function splitCheckConstraintState(check) {
12169
+ return {
12170
+ expression: toCheckConstraintClause(check.expression),
12171
+ notValid: check.notValid !== void 0 ? check.notValid : hasNotValidSuffix(check.expression)
12172
+ };
12173
+ }
12174
+ function renderCheckConstraintClause(check) {
12175
+ const state = splitCheckConstraintState(check);
12176
+ return state.notValid ? `${state.expression} NOT VALID` : state.expression;
12177
+ }
12178
+ function isCheckConstraintNotValid(check) {
12179
+ return splitCheckConstraintState(check).notValid;
12180
+ }
12181
+ function stripNotValidSuffix(expression) {
12182
+ return expression.replace(CHECK_NOT_VALID_SUFFIX, "$<clause>");
12183
+ }
12184
+ function hasNotValidSuffix(expression) {
12185
+ return CHECK_NOT_VALID_SUFFIX.test(expression);
12186
+ }
12187
+
12121
12188
  // src/ddl/phases/constraints.ts
12122
12189
  init_validate();
12123
12190
  function generateConstraintsPhase(ctx) {
@@ -12132,10 +12199,13 @@ function generateConstraintsPhase(ctx) {
12132
12199
  }
12133
12200
  if (sup(caps, caps?.supportsDDLCheckConstraints)) {
12134
12201
  for (const table of tables) {
12202
+ assertNoCheckConstraintNameCollisions(table, naming);
12135
12203
  for (const check of table.checkConstraints ?? []) {
12136
12204
  const qualifiedTable = qualifyTableIdent(table.name, schemaName, naming);
12137
- const constraintName = quoteIdent2(check.name);
12138
- const expression = check.expression;
12205
+ const constraintName = quoteIdent2(
12206
+ getCheckConstraintDatabaseName(check, naming)
12207
+ );
12208
+ const expression = renderCheckConstraintClause(check);
12139
12209
  validateCheckExpression(expression, "check constraint expression");
12140
12210
  statements.push(
12141
12211
  `ALTER TABLE ${qualifiedTable} ADD CONSTRAINT ${constraintName} ${expression};`
@@ -12162,13 +12232,13 @@ function generateDropStatementsPhase(ctx) {
12162
12232
 
12163
12233
  // src/ddl/phases/enum-types.ts
12164
12234
  function generateEnumTypesPhase(ctx) {
12165
- const { schema, schemaName, naming, caps } = ctx;
12235
+ const { schema, schemaName, caps } = ctx;
12166
12236
  if (!schema.enums || !sup(caps, caps?.supportsDDLEnumTypes)) {
12167
12237
  return [];
12168
12238
  }
12169
12239
  const statements = [];
12170
12240
  for (const [, enumDef] of schema.enums) {
12171
- const effectiveSchema = schemaName !== void 0 ? naming.toDatabase(schemaName) : enumDef.schema;
12241
+ const effectiveSchema = schemaName !== void 0 ? schemaName : enumDef.schema;
12172
12242
  const enumName = effectiveSchema && effectiveSchema !== "public" && effectiveSchema !== "pg_catalog" ? `${quoteIdent2(effectiveSchema, "schema")}.${quoteIdent2(enumDef.name, "table")}` : quoteIdent2(enumDef.name, "table");
12173
12243
  const values = enumDef.values.map((v) => {
12174
12244
  validateEnumLabel(v, "enum value");
@@ -12191,12 +12261,36 @@ function generateExtensionsPhase(ctx) {
12191
12261
  }
12192
12262
 
12193
12263
  // src/ddl/schema-diff.ts
12264
+ import { validateSchemaIndexOptions } from "@dbsp/core";
12194
12265
  init_db_type();
12195
12266
  init_naming_plugin();
12196
- import { validateSchemaIndexOptions } from "@dbsp/core";
12267
+ var ExpressionCanonicalizationUnavailableError = class extends Error {
12268
+ constructor(surfaces) {
12269
+ super(
12270
+ `compareSchemata strict expression canonicalization was requested, but this pure compile-only path cannot ask PostgreSQL to canonicalise raw SQL expression surfaces. Use comparePgsqlDatabaseSchema() for live CHECK constraint canonicalization; index predicates and index expressions are not covered by the live canonicalizer. Omit requireExpressionCanonicalization for best-effort raw string comparison. Surfaces: ${surfaces.join(", ")}`
12271
+ );
12272
+ this.surfaces = surfaces;
12273
+ this.name = "ExpressionCanonicalizationUnavailableError";
12274
+ }
12275
+ surfaces;
12276
+ };
12197
12277
  function compareSchemata(schema, db, options) {
12278
+ const caps = options?.dialectCapabilities;
12279
+ const sup2 = (flag) => !caps || flag === true;
12280
+ const supportsCheckConstraints = sup2(caps?.supportsDDLCheckConstraints);
12281
+ if (options?.requireExpressionCanonicalization) {
12282
+ assertNoExpressionSurfaces(schema, db, {
12283
+ includeCheckConstraints: supportsCheckConstraints
12284
+ });
12285
+ }
12198
12286
  const changes = [];
12199
- const plugin = options?.dbCasing !== void 0 ? getNamingPluginForDbCasing(options.dbCasing) : void 0;
12287
+ const schemaNaming = options?.dbCasing !== void 0 ? getNamingPluginForDbCasing(options.dbCasing) : identityNaming;
12288
+ if (supportsCheckConstraints) {
12289
+ for (const table of schema.tables.values()) {
12290
+ assertNoCheckConstraintNameCollisions(table, schemaNaming);
12291
+ }
12292
+ }
12293
+ const plugin = options?.dbCasing !== void 0 ? schemaNaming : void 0;
12200
12294
  const schemaTables = plugin ? normalizeTableMap(schema.tables, plugin) : new Map(schema.tables);
12201
12295
  const dbTables = new Map(db.tables);
12202
12296
  const externalTables = new Set(
@@ -12204,8 +12298,6 @@ function compareSchemata(schema, db, options) {
12204
12298
  (name) => plugin ? plugin.toDatabase(name) : name
12205
12299
  )
12206
12300
  );
12207
- const caps = options?.dialectCapabilities;
12208
- const sup2 = (flag) => !caps || flag === true;
12209
12301
  if (sup2(caps?.supportsDDLEnumTypes)) {
12210
12302
  compareEnums(schema, db, changes);
12211
12303
  }
@@ -12244,7 +12336,7 @@ function compareSchemata(schema, db, options) {
12244
12336
  meta: { index: idx }
12245
12337
  });
12246
12338
  }
12247
- if (sup2(caps?.supportsDDLCheckConstraints)) {
12339
+ if (supportsCheckConstraints) {
12248
12340
  for (const check of schemaTable.checkConstraints ?? []) {
12249
12341
  changes.push({
12250
12342
  kind: "add_check_constraint",
@@ -12281,7 +12373,7 @@ function compareSchemata(schema, db, options) {
12281
12373
  comparePrimaryKeys(schemaTable, dbTable, changes);
12282
12374
  compareForeignKeys(schemaTable, dbTable, changes);
12283
12375
  compareIndexes(schemaTable, dbTable, changes);
12284
- if (sup2(caps?.supportsDDLCheckConstraints)) {
12376
+ if (supportsCheckConstraints) {
12285
12377
  compareCheckConstraints(schemaTable, dbTable, changes);
12286
12378
  }
12287
12379
  if (sup2(caps?.supportsDDLComments)) {
@@ -12308,6 +12400,47 @@ function compareSchemata(schema, db, options) {
12308
12400
  summary: buildSummary(changes)
12309
12401
  };
12310
12402
  }
12403
+ function assertNoExpressionSurfaces(schema, db, options) {
12404
+ const surfaces = [
12405
+ ...collectExpressionSurfaces("schema", schema, options),
12406
+ ...collectExpressionSurfaces("database", db, options)
12407
+ ];
12408
+ if (surfaces.length > 0) {
12409
+ throw new ExpressionCanonicalizationUnavailableError(surfaces);
12410
+ }
12411
+ }
12412
+ function collectExpressionSurfaces(label, model, options) {
12413
+ const includeCheckConstraints = options?.includeCheckConstraints ?? true;
12414
+ const includeIndexPredicates = options?.includeIndexPredicates ?? true;
12415
+ const includeIndexExpressions = options?.includeIndexExpressions ?? true;
12416
+ const surfaces = [];
12417
+ for (const table of model.tables.values()) {
12418
+ if (includeCheckConstraints) {
12419
+ for (const check of table.checkConstraints ?? []) {
12420
+ surfaces.push(
12421
+ `${label}.${table.name}.CHECK(${formatConstraintName(check)})`
12422
+ );
12423
+ }
12424
+ }
12425
+ for (const index of table.indexes) {
12426
+ const indexName = index.name ?? `<unnamed:${index.columns.join(",")}>`;
12427
+ if (includeIndexPredicates && index.where !== void 0) {
12428
+ surfaces.push(`${label}.${table.name}.INDEX(${indexName}).WHERE`);
12429
+ }
12430
+ if (includeIndexExpressions) {
12431
+ for (let i = 0; i < (index.expressions?.length ?? 0); i++) {
12432
+ surfaces.push(
12433
+ `${label}.${table.name}.INDEX(${indexName}).EXPRESSION[${i}]`
12434
+ );
12435
+ }
12436
+ }
12437
+ }
12438
+ }
12439
+ return surfaces;
12440
+ }
12441
+ function formatConstraintName(check) {
12442
+ return check.name || "<unnamed>";
12443
+ }
12311
12444
  function normalizeTableMap(tables, plugin) {
12312
12445
  const result = /* @__PURE__ */ new Map();
12313
12446
  for (const [_key, table] of tables) {
@@ -12351,6 +12484,12 @@ function normalizeTable(table, plugin) {
12351
12484
  )
12352
12485
  } : {}
12353
12486
  })),
12487
+ ...table.checkConstraints !== void 0 ? {
12488
+ checkConstraints: table.checkConstraints.map((check) => ({
12489
+ ...check,
12490
+ name: getCheckConstraintDatabaseName(check, plugin)
12491
+ }))
12492
+ } : {},
12354
12493
  ...table.partition ? {
12355
12494
  partition: {
12356
12495
  strategy: table.partition.strategy,
@@ -12855,9 +12994,14 @@ function comparePartitions(schema, db, changes) {
12855
12994
  function compareCheckConstraints(schema, db, changes) {
12856
12995
  const schemaChecks = schema.checkConstraints ?? [];
12857
12996
  const dbChecks = db.checkConstraints ?? [];
12858
- const schemaMap = new Map(schemaChecks.map((c) => [c.name, c]));
12859
- const dbMap = new Map(dbChecks.map((c) => [c.name, c]));
12860
- for (const [name, check] of schemaMap) {
12997
+ const schemaMap = new Map(
12998
+ schemaChecks.map((check) => [check.name, normalizeCheckForDiff(check)])
12999
+ );
13000
+ const dbMap = new Map(
13001
+ dbChecks.map((check) => [check.name, normalizeCheckForDiff(check)])
13002
+ );
13003
+ for (const [name, checkEntry] of schemaMap) {
13004
+ const { check } = checkEntry;
12861
13005
  if (!dbMap.has(name)) {
12862
13006
  changes.push({
12863
13007
  kind: "add_check_constraint",
@@ -12867,25 +13011,25 @@ function compareCheckConstraints(schema, db, changes) {
12867
13011
  meta: { check }
12868
13012
  });
12869
13013
  } else {
12870
- const dbCheck = dbMap.get(name);
12871
- if (check.expression !== dbCheck.expression) {
13014
+ const dbCheckEntry = dbMap.get(name);
13015
+ if (checkEntry.expression !== dbCheckEntry.expression) {
12872
13016
  changes.push({
12873
13017
  kind: "drop_check_constraint",
12874
13018
  table: schema.name,
12875
13019
  destructive: true,
12876
13020
  details: `Drop CHECK constraint "${name}" (expression changed)`,
12877
- meta: { check: dbCheck }
13021
+ meta: { check: dbCheckEntry.check }
12878
13022
  });
12879
13023
  changes.push({
12880
13024
  kind: "add_check_constraint",
12881
13025
  table: schema.name,
12882
- destructive: false,
13026
+ destructive: true,
12883
13027
  details: `Add CHECK constraint "${name}" ${check.expression}`,
12884
13028
  meta: { check }
12885
13029
  });
12886
13030
  } else {
12887
- const dbNotValid = dbCheck.notValid ?? false;
12888
- const schemaNotValid = check.notValid ?? false;
13031
+ const dbNotValid = dbCheckEntry.notValid;
13032
+ const schemaNotValid = checkEntry.notValid;
12889
13033
  if (dbNotValid && !schemaNotValid) {
12890
13034
  changes.push({
12891
13035
  kind: "validate_constraint",
@@ -12894,22 +13038,45 @@ function compareCheckConstraints(schema, db, changes) {
12894
13038
  details: `Validate CHECK constraint "${name}"`,
12895
13039
  meta: { check }
12896
13040
  });
13041
+ } else if (!dbNotValid && schemaNotValid) {
13042
+ changes.push({
13043
+ kind: "drop_check_constraint",
13044
+ table: schema.name,
13045
+ destructive: true,
13046
+ details: `Drop CHECK constraint "${name}" to re-add NOT VALID`,
13047
+ meta: { check: dbCheckEntry.check }
13048
+ });
13049
+ changes.push({
13050
+ kind: "add_check_constraint",
13051
+ table: schema.name,
13052
+ destructive: true,
13053
+ details: `Re-add CHECK constraint "${name}" ${check.expression} NOT VALID`,
13054
+ meta: { check }
13055
+ });
12897
13056
  }
12898
13057
  }
12899
13058
  }
12900
13059
  }
12901
- for (const [name, check] of dbMap) {
13060
+ for (const [name, checkEntry] of dbMap) {
12902
13061
  if (!schemaMap.has(name)) {
12903
13062
  changes.push({
12904
13063
  kind: "drop_check_constraint",
12905
13064
  table: schema.name,
12906
13065
  destructive: true,
12907
13066
  details: `Drop CHECK constraint "${name}"`,
12908
- meta: { check }
13067
+ meta: { check: checkEntry.check }
12909
13068
  });
12910
13069
  }
12911
13070
  }
12912
13071
  }
13072
+ function normalizeCheckForDiff(check) {
13073
+ const state = splitCheckConstraintState(check);
13074
+ return {
13075
+ check,
13076
+ expression: state.expression,
13077
+ notValid: state.notValid
13078
+ };
13079
+ }
12913
13080
  function compareEnums(schema, db, changes) {
12914
13081
  const schemaEnums = schema.enums ?? /* @__PURE__ */ new Map();
12915
13082
  const dbEnums = db.enums ?? /* @__PURE__ */ new Map();
@@ -13147,6 +13314,7 @@ function buildSummary(changes) {
13147
13314
  constraints.dropped++;
13148
13315
  break;
13149
13316
  case "alter_foreign_key":
13317
+ case "validate_constraint":
13150
13318
  constraints.altered++;
13151
13319
  break;
13152
13320
  case "create_index":
@@ -13509,8 +13677,8 @@ function failSafeUnknownDownChange(kind) {
13509
13677
  destructive: true
13510
13678
  };
13511
13679
  }
13512
- function isChangeSupported(kind, caps) {
13513
- switch (kind) {
13680
+ function isChangeSupported(change, caps) {
13681
+ switch (change.kind) {
13514
13682
  case "create_enum":
13515
13683
  case "drop_enum":
13516
13684
  case "alter_enum_add_value":
@@ -13525,6 +13693,8 @@ function isChangeSupported(kind, caps) {
13525
13693
  case "add_check_constraint":
13526
13694
  case "drop_check_constraint":
13527
13695
  return caps.supportsDDLCheckConstraints === true;
13696
+ case "validate_constraint":
13697
+ return isValidateConstraintSupported(change, caps);
13528
13698
  case "add_comment":
13529
13699
  case "drop_comment":
13530
13700
  return caps.supportsDDLComments === true;
@@ -13541,11 +13711,20 @@ function isChangeSupported(kind, caps) {
13541
13711
  return true;
13542
13712
  }
13543
13713
  }
13714
+ function isValidateConstraintSupported(change, caps) {
13715
+ if (change.meta?.check !== void 0) {
13716
+ return caps.supportsDDLCheckConstraints === true;
13717
+ }
13718
+ if (change.meta?.fk !== void 0) {
13719
+ return caps.supportsDDLForeignKeys !== false;
13720
+ }
13721
+ return true;
13722
+ }
13544
13723
  function changesAppliedByUp(diff, options) {
13545
13724
  const includeDestructive = options?.includeDestructive ?? true;
13546
13725
  const filteredChanges = includeDestructive ? diff.changes : diff.changes.filter((c) => !c.destructive);
13547
13726
  const caps = options?.dialectCapabilities;
13548
- return caps ? filteredChanges.filter((c) => isChangeSupported(c.kind, caps)) : filteredChanges;
13727
+ return caps ? filteredChanges.filter((c) => isChangeSupported(c, caps)) : filteredChanges;
13549
13728
  }
13550
13729
  function generateMigrationSQL(diff, options) {
13551
13730
  const schemaName = options?.schemaName;
@@ -13814,11 +13993,10 @@ function upDropIndex(change, schemaName) {
13814
13993
  function upAddCheckConstraint(change, schemaName) {
13815
13994
  const check = change.meta?.check;
13816
13995
  if (!check) return void 0;
13817
- const expression = check.expression;
13818
- const notValid = check.notValid ? " NOT VALID" : "";
13996
+ const expression = renderCheckConstraintClause(check);
13819
13997
  validateCheckExpression(expression, "migration check constraint expression");
13820
13998
  return buildDoBlock(
13821
- "BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + expression + notValid + "; EXCEPTION WHEN duplicate_object THEN NULL; END"
13999
+ "BEGIN ALTER TABLE " + qualifyTable(change.table, schemaName) + " ADD CONSTRAINT " + quoteIdent2(check.name, "alias") + " " + expression + "; EXCEPTION WHEN duplicate_object THEN NULL; END"
13822
14000
  );
13823
14001
  }
13824
14002
  function upValidateConstraint(change, schemaName) {
@@ -14215,7 +14393,7 @@ ${add}`, destructive: false };
14215
14393
  case "drop_check_constraint": {
14216
14394
  const check = change.meta?.check;
14217
14395
  if (!check) return { sql: void 0, destructive: true };
14218
- const expression = check.expression;
14396
+ const expression = renderCheckConstraintClause(check);
14219
14397
  validateCheckExpression(expression, "migration check constraint (down)");
14220
14398
  return {
14221
14399
  sql: buildDoBlock(
@@ -14567,13 +14745,13 @@ function formatDefault(value) {
14567
14745
 
14568
14746
  // src/ddl/phases/sequences.ts
14569
14747
  function generateSequencesPhase(ctx) {
14570
- const { schema, schemaName, naming, caps } = ctx;
14748
+ const { schema, schemaName, caps } = ctx;
14571
14749
  if (!schema.sequences || !sup(caps, caps?.supportsDDLSequences)) {
14572
14750
  return [];
14573
14751
  }
14574
14752
  const statements = [];
14575
14753
  for (const [, seq] of schema.sequences) {
14576
- const seqName = schemaName ? `${quoteIdent2(naming.toDatabase(schemaName), "schema")}.${quoteIdent2(seq.name, "table")}` : quoteIdent2(seq.name, "table");
14754
+ const seqName = schemaName ? `${quoteIdent2(schemaName, "schema")}.${quoteIdent2(seq.name, "table")}` : quoteIdent2(seq.name, "table");
14577
14755
  statements.push(buildSequenceClause("CREATE SEQUENCE", seqName, seq));
14578
14756
  }
14579
14757
  return statements;
@@ -14643,11 +14821,10 @@ function quoteIdentifier(name) {
14643
14821
  validateIdentifier(name, "alias");
14644
14822
  return `"${name.replace(/"/g, '""')}"`;
14645
14823
  }
14646
- function qualifyTable2(tableName, schemaName, naming, transformSchema = true) {
14824
+ function qualifyTable2(tableName, schemaName, naming) {
14647
14825
  const table = quoteIdentifier(naming.toDatabase(tableName));
14648
14826
  if (schemaName) {
14649
- const schema = transformSchema ? naming.toDatabase(schemaName) : schemaName;
14650
- return `${quoteIdentifier(schema)}.${table}`;
14827
+ return `${quoteIdentifier(schemaName)}.${table}`;
14651
14828
  }
14652
14829
  return table;
14653
14830
  }
@@ -14657,10 +14834,9 @@ function generateDropTable(tableName, schemaName, naming) {
14657
14834
  }
14658
14835
  function generateCreateTable(table, schemaName, naming) {
14659
14836
  const qualifiedTable = qualifyTable2(table.name, schemaName, naming);
14660
- const targetSchema = schemaName ? naming.toDatabase(schemaName) : void 0;
14661
14837
  const elements = [];
14662
14838
  for (const col of table.columns) {
14663
- elements.push(generateColumnDef(col, naming, targetSchema));
14839
+ elements.push(generateColumnDef(col, naming, schemaName));
14664
14840
  }
14665
14841
  if (table.primaryKey !== void 0) {
14666
14842
  const rawPk = table.primaryKey;
@@ -14719,7 +14895,7 @@ function generateAlterTableAddFK(tableName, fk, schemaName, naming) {
14719
14895
  `fk_${tableName}_${fk.columns.join("_")}`
14720
14896
  );
14721
14897
  const fkCols = fk.columns.map((col) => quoteIdentifier(naming.toDatabase(col))).join(", ");
14722
- const refTable = fk.references.schema !== void 0 ? qualifyTable2(fk.references.table, fk.references.schema, naming, false) : qualifyTable2(fk.references.table, schemaName, naming);
14898
+ const refTable = fk.references.schema !== void 0 ? qualifyTable2(fk.references.table, fk.references.schema, naming) : qualifyTable2(fk.references.table, schemaName, naming);
14723
14899
  const refCols = fk.references.columns.map((col) => quoteIdentifier(naming.toDatabase(col))).join(", ");
14724
14900
  const onDelete = fk.onDelete ? ` ON DELETE ${mapOnDeleteAction(fk.onDelete)}` : "";
14725
14901
  const onUpdate = fk.onUpdate ? ` ON UPDATE ${mapOnDeleteAction(fk.onUpdate)}` : "";
@@ -14820,6 +14996,494 @@ function generateCreatePolicy(tableName, policy, schemaName, naming) {
14820
14996
  return `CREATE POLICY ${policyName} ON ${qualifiedTable}${forClause}${asClause}${toClause}${usingClause}${withCheckClause};`;
14821
14997
  }
14822
14998
 
14999
+ // src/expression-canonicalizer.ts
15000
+ import { randomUUID } from "crypto";
15001
+ import { ModelIRImpl } from "@dbsp/core";
15002
+ init_naming_plugin();
15003
+ init_validate();
15004
+ var CheckConstraintCanonicalizationError = class extends Error {
15005
+ constructor(table, constraints, cause) {
15006
+ super(
15007
+ `Could not canonicalize CHECK constraint expression(s) for table "${table}" (${constraints.map((name) => `"${name}"`).join(", ")}), and strict expression canonicalization was requested: ${errorMessage(cause)}`
15008
+ );
15009
+ this.table = table;
15010
+ this.constraints = constraints;
15011
+ this.cause = cause;
15012
+ this.name = "CheckConstraintCanonicalizationError";
15013
+ }
15014
+ table;
15015
+ constraints;
15016
+ cause;
15017
+ };
15018
+ async function canonicalizeCheckConstraints(adapter, desired, dbModel, options) {
15019
+ const targets = collectCheckConstraintTargets(desired, dbModel, options);
15020
+ if (targets.length === 0) {
15021
+ return desired;
15022
+ }
15023
+ const canonicalChecksByTable = /* @__PURE__ */ new Map();
15024
+ const names = createCheckCanonicalizationNameScope();
15025
+ let workError;
15026
+ try {
15027
+ try {
15028
+ await createMissingDesiredEnumTypes(adapter, desired, dbModel, options);
15029
+ for (let i = 0; i < targets.length; i++) {
15030
+ const target = targets[i];
15031
+ const canonicalChecks = await canonicalizeTableChecksBestEffort(
15032
+ adapter,
15033
+ target,
15034
+ i,
15035
+ options,
15036
+ names
15037
+ );
15038
+ if (canonicalChecks !== void 0) {
15039
+ canonicalChecksByTable.set(target.modelKey, canonicalChecks);
15040
+ }
15041
+ }
15042
+ } catch (error) {
15043
+ workError = error;
15044
+ }
15045
+ } catch (error) {
15046
+ workError = error;
15047
+ }
15048
+ if (workError !== void 0) {
15049
+ if (options?.requireCanonicalization) {
15050
+ throw workError;
15051
+ }
15052
+ for (const target of targets) {
15053
+ if (!canonicalChecksByTable.has(target.modelKey)) {
15054
+ reportCanonicalizationFailure(target, workError, options);
15055
+ }
15056
+ }
15057
+ }
15058
+ return cloneModelWithCanonicalChecks(desired, canonicalChecksByTable);
15059
+ }
15060
+ function collectCheckConstraintTargets(desired, dbModel, options) {
15061
+ const plugin = options?.dbCasing !== void 0 ? getNamingPluginForDbCasing(options.dbCasing) : identityNaming;
15062
+ const targets = [];
15063
+ for (const [modelKey, table] of desired.tables) {
15064
+ const checks = table.checkConstraints ?? [];
15065
+ if (checks.length === 0) continue;
15066
+ const dbTableName = plugin.toDatabase(table.name);
15067
+ const dbTable = dbModel.tables.get(dbTableName);
15068
+ targets.push({
15069
+ modelKey,
15070
+ modelTable: table,
15071
+ ...dbTable !== void 0 ? { dbTable } : {},
15072
+ dbTableName,
15073
+ checks,
15074
+ dbCheckNames: checks.map(
15075
+ (check) => getCheckConstraintDatabaseName(check, plugin)
15076
+ )
15077
+ });
15078
+ }
15079
+ return targets;
15080
+ }
15081
+ async function canonicalizeTableChecksBestEffort(adapter, target, targetIndex, options, names) {
15082
+ try {
15083
+ return await adapter.transaction(
15084
+ (tx) => canonicalizeTableChecks(tx, target, targetIndex, options, names)
15085
+ );
15086
+ } catch (error) {
15087
+ if (error instanceof CheckConstraintCanonicalizationError) {
15088
+ throw error;
15089
+ }
15090
+ if (options?.requireCanonicalization) {
15091
+ throw new CheckConstraintCanonicalizationError(
15092
+ target.dbTableName,
15093
+ target.dbCheckNames,
15094
+ error
15095
+ );
15096
+ }
15097
+ reportCanonicalizationFailure(target, error, options);
15098
+ return void 0;
15099
+ }
15100
+ }
15101
+ async function canonicalizeTableChecks(adapter, target, targetIndex, options, names) {
15102
+ const tempTableName = `${names.tempPrefix}_${targetIndex}`;
15103
+ const tempTable = quoteIdent2(tempTableName, "table");
15104
+ const naming = namingForOptions(options);
15105
+ const targetSchema = options?.schemaName;
15106
+ const dbColumnsByName = new Map(
15107
+ (target.dbTable?.columns ?? []).map((column) => [column.name, column])
15108
+ );
15109
+ const columnDefs = target.modelTable.columns.map(
15110
+ (column) => generateScratchColumnDef(column, dbColumnsByName, naming, targetSchema)
15111
+ );
15112
+ await adapter.executeRaw(
15113
+ `CREATE TEMP TABLE ${tempTable} (${columnDefs.join(", ")}) ON COMMIT DROP`
15114
+ );
15115
+ const tempConstraintNamesByIndex = /* @__PURE__ */ new Map();
15116
+ const canonicalChecks = [...target.checks];
15117
+ for (let i = 0; i < target.checks.length; i++) {
15118
+ const check = target.checks[i];
15119
+ const tempConstraintName = `${names.tempPrefix}_${targetIndex}_${i}`;
15120
+ try {
15121
+ await adapter.transaction(async (tx) => {
15122
+ const expression = renderCheckConstraintClause(check);
15123
+ validateCheckExpression(expression, "canonicalized check constraint");
15124
+ await tx.executeRaw(
15125
+ `ALTER TABLE ${tempTable} ADD CONSTRAINT ${quoteIdent2(tempConstraintName, "alias")} ${expression}`
15126
+ );
15127
+ });
15128
+ tempConstraintNamesByIndex.set(i, tempConstraintName);
15129
+ } catch (error) {
15130
+ if (options?.requireCanonicalization) {
15131
+ throw new CheckConstraintCanonicalizationError(
15132
+ target.dbTableName,
15133
+ [target.dbCheckNames[i]],
15134
+ error
15135
+ );
15136
+ }
15137
+ reportConstraintCanonicalizationFailure(target, i, error, options);
15138
+ }
15139
+ }
15140
+ const tempConstraintNames = [...tempConstraintNamesByIndex.values()];
15141
+ if (tempConstraintNames.length === 0) {
15142
+ return canonicalChecks;
15143
+ }
15144
+ const rows = await adapter.executeRaw(
15145
+ `SELECT conname AS name,
15146
+ pg_get_constraintdef(oid, false) AS expression
15147
+ FROM pg_constraint
15148
+ WHERE conrelid = $1::regclass
15149
+ AND conname = ANY($2::text[])
15150
+ ORDER BY array_position($2::text[], conname)`,
15151
+ [tempTableName, tempConstraintNames]
15152
+ );
15153
+ const canonicalByTempName = new Map(
15154
+ rows.map((row) => [row.name, stripNotValidSuffix(row.expression)])
15155
+ );
15156
+ for (const [i, tempConstraintName] of tempConstraintNamesByIndex) {
15157
+ const check = target.checks[i];
15158
+ const expression = canonicalByTempName.get(tempConstraintName);
15159
+ if (expression === void 0) {
15160
+ const error = new Error(
15161
+ `PostgreSQL did not return canonical CHECK expression for "${target.modelTable.name}" constraint "${check.name}"`
15162
+ );
15163
+ if (options?.requireCanonicalization) {
15164
+ throw new CheckConstraintCanonicalizationError(
15165
+ target.dbTableName,
15166
+ [target.dbCheckNames[i]],
15167
+ error
15168
+ );
15169
+ }
15170
+ reportConstraintCanonicalizationFailure(target, i, error, options);
15171
+ continue;
15172
+ }
15173
+ const notValid = isCheckConstraintNotValid(check);
15174
+ canonicalChecks[i] = {
15175
+ ...check,
15176
+ expression,
15177
+ ...notValid ? { notValid: true } : {}
15178
+ };
15179
+ }
15180
+ return canonicalChecks;
15181
+ }
15182
+ function generateScratchColumnDef(column, dbColumnsByName, naming, targetSchema) {
15183
+ const dbColumn = dbColumnsByName.get(naming.toDatabase(column.name));
15184
+ const typeColumn = !column.originalDbType?.trim() && dbColumn?.originalDbType?.trim() && column.type === dbColumn.type ? dbColumn : column;
15185
+ const scratchColumn = typeColumn.autoIncrement === true ? { ...typeColumn, autoIncrement: false } : typeColumn;
15186
+ return `${quoteIdent2(naming.toDatabase(column.name), "column")} ${mapColumnType(scratchColumn, targetSchema)}`;
15187
+ }
15188
+ async function createMissingDesiredEnumTypes(adapter, desired, dbModel, options) {
15189
+ const missingEnums = missingDesiredEnums(desired, dbModel);
15190
+ if (missingEnums.size === 0) return;
15191
+ const naming = namingForOptions(options);
15192
+ const enumModel = new ModelIRImpl(/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), missingEnums);
15193
+ const phaseContext = {
15194
+ schema: enumModel,
15195
+ tables: [],
15196
+ schemaName: options?.schemaName,
15197
+ naming,
15198
+ caps: void 0,
15199
+ fkAutoIndex: false,
15200
+ includeDropStatements: false
15201
+ };
15202
+ const statements = generateEnumTypesPhase(phaseContext);
15203
+ for (const statement of statements) {
15204
+ try {
15205
+ await adapter.transaction(async (tx) => {
15206
+ await tx.executeRaw(statement);
15207
+ });
15208
+ } catch {
15209
+ }
15210
+ }
15211
+ }
15212
+ function createCheckCanonicalizationNameScope() {
15213
+ const callId = randomUUID().replace(/-/gu, "").slice(0, 12);
15214
+ return {
15215
+ tempPrefix: `_dbsp_check_canon_${callId}`
15216
+ };
15217
+ }
15218
+ function missingDesiredEnums(desired, dbModel) {
15219
+ const desiredEnums = desired.enums ?? /* @__PURE__ */ new Map();
15220
+ const dbEnums = dbModel.enums ?? /* @__PURE__ */ new Map();
15221
+ const missing = /* @__PURE__ */ new Map();
15222
+ for (const [name, enumDef] of desiredEnums) {
15223
+ if (!dbEnums.has(name)) {
15224
+ missing.set(name, enumDef);
15225
+ }
15226
+ }
15227
+ return missing;
15228
+ }
15229
+ function namingForOptions(options) {
15230
+ return options?.dbCasing !== void 0 ? getNamingPluginForDbCasing(options.dbCasing) : identityNaming;
15231
+ }
15232
+ function reportCanonicalizationFailure(target, cause, options) {
15233
+ for (let i = 0; i < target.checks.length; i++) {
15234
+ reportConstraintCanonicalizationFailure(target, i, cause, options);
15235
+ }
15236
+ }
15237
+ function reportConstraintCanonicalizationFailure(target, checkIndex, cause, options) {
15238
+ const dbCheckName = target.dbCheckNames[checkIndex];
15239
+ const message = `Could not canonicalize CHECK constraint "${target.dbTableName}"."${dbCheckName}" with PostgreSQL; falling back to best-effort raw string comparison. Reason: ${errorMessage(cause)}`;
15240
+ const warning = {
15241
+ table: target.dbTableName,
15242
+ constraint: dbCheckName,
15243
+ message,
15244
+ cause
15245
+ };
15246
+ if (options?.onWarning) {
15247
+ options.onWarning(warning);
15248
+ } else {
15249
+ console.warn(`Warning: ${message}`);
15250
+ }
15251
+ }
15252
+ function errorMessage(error) {
15253
+ return error instanceof Error ? error.message : String(error);
15254
+ }
15255
+ function cloneModelWithCanonicalChecks(model, canonicalChecksByTable) {
15256
+ const tables = /* @__PURE__ */ new Map();
15257
+ for (const [key, table] of model.tables) {
15258
+ const checks = canonicalChecksByTable.get(key);
15259
+ tables.set(
15260
+ key,
15261
+ checks === void 0 ? table : {
15262
+ ...table,
15263
+ checkConstraints: checks
15264
+ }
15265
+ );
15266
+ }
15267
+ return new ModelIRImpl(
15268
+ tables,
15269
+ new Map(model.relations),
15270
+ model.enums === void 0 ? void 0 : new Map(model.enums),
15271
+ model.extensions,
15272
+ model.sequences === void 0 ? void 0 : new Map(model.sequences),
15273
+ model.externalTables
15274
+ );
15275
+ }
15276
+
15277
+ // src/ddl/live-diff.ts
15278
+ var NonConvergentSchemaDiffError = class extends Error {
15279
+ constructor(table, constraint, desiredExpression, databaseExpression) {
15280
+ super(
15281
+ `Non-convergent CHECK constraint diff for "${table}"."${constraint}": the same expression drift was reported after applying the previous diff. Desired expression: ${desiredExpression}. Database expression: ${databaseExpression}.`
15282
+ );
15283
+ this.table = table;
15284
+ this.constraint = constraint;
15285
+ this.desiredExpression = desiredExpression;
15286
+ this.databaseExpression = databaseExpression;
15287
+ this.name = "NonConvergentSchemaDiffError";
15288
+ }
15289
+ table;
15290
+ constraint;
15291
+ desiredExpression;
15292
+ databaseExpression;
15293
+ };
15294
+ var CheckConstraintNewEnumValueError = class extends Error {
15295
+ constructor(table, constraint, addedEnumValues) {
15296
+ const candidates = addedEnumValues.map(({ enumName, value }) => `"${value}" (enum "${enumName}")`).join(", ");
15297
+ super(
15298
+ `PostgreSQL could not canonicalise CHECK constraint "${table}"."${constraint}", and this migration also adds enum value(s): ${candidates}. PostgreSQL cannot use an enum value in the transaction that adds it, and dbsp applies each migration in one transaction, so this constraint cannot be proven applicable. Apply the enum change on its own first, then add or update the constraint.`
15299
+ );
15300
+ this.table = table;
15301
+ this.constraint = constraint;
15302
+ this.addedEnumValues = addedEnumValues;
15303
+ this.name = "CheckConstraintNewEnumValueError";
15304
+ }
15305
+ table;
15306
+ constraint;
15307
+ addedEnumValues;
15308
+ };
15309
+ async function comparePgsqlDatabaseSchema(adapter, desired, options) {
15310
+ const dbModel = await adapter.introspect(toIntrospectionOptions(options));
15311
+ const compareCheckConstraints2 = supportsDDLCheckConstraints(options);
15312
+ const useCanonicalizer = compareCheckConstraints2 && (options?.canonicalizeExpressions ?? true);
15313
+ const rawCheckExpressionSurfaces = /* @__PURE__ */ new Set();
15314
+ const desiredForCompare = useCanonicalizer ? await adapter.withScratchScope(
15315
+ (scratch) => canonicalizeCheckConstraints(
15316
+ scratch,
15317
+ desired,
15318
+ dbModel,
15319
+ toCanonicalizerOptions(options, rawCheckExpressionSurfaces)
15320
+ )
15321
+ ) : desired;
15322
+ if (options?.requireExpressionCanonicalization && (useCanonicalizer || !compareCheckConstraints2)) {
15323
+ assertNoUncanonicalizedLiveExpressionSurfaces(desiredForCompare, dbModel);
15324
+ }
15325
+ const diff = compareSchemata(
15326
+ desiredForCompare,
15327
+ dbModel,
15328
+ toCompareOptions(options, {
15329
+ delegateExpressionCanonicalization: options?.requireExpressionCanonicalization === true && !useCanonicalizer && compareCheckConstraints2
15330
+ })
15331
+ );
15332
+ if (useCanonicalizer && rawCheckExpressionSurfaces.size > 0) {
15333
+ assertNoCheckFallbackUsesAddedEnumValue(diff, rawCheckExpressionSurfaces);
15334
+ }
15335
+ if (options?.previouslyAppliedDiff !== void 0) {
15336
+ if (!useCanonicalizer) {
15337
+ assertNoRepeatedExpressionSurfaceDrift(
15338
+ options.previouslyAppliedDiff,
15339
+ diff
15340
+ );
15341
+ } else if (rawCheckExpressionSurfaces.size > 0) {
15342
+ assertNoRepeatedExpressionSurfaceDrift(
15343
+ options.previouslyAppliedDiff,
15344
+ diff,
15345
+ rawCheckExpressionSurfaces
15346
+ );
15347
+ }
15348
+ }
15349
+ return diff;
15350
+ }
15351
+ function assertNoUncanonicalizedLiveExpressionSurfaces(desired, dbModel) {
15352
+ const surfaces = [
15353
+ ...collectExpressionSurfaces("schema", desired, {
15354
+ includeCheckConstraints: false
15355
+ }),
15356
+ ...collectExpressionSurfaces("database", dbModel, {
15357
+ includeCheckConstraints: false
15358
+ })
15359
+ ];
15360
+ if (surfaces.length > 0) {
15361
+ throw new ExpressionCanonicalizationUnavailableError(surfaces);
15362
+ }
15363
+ }
15364
+ function assertNoRepeatedExpressionSurfaceDrift(previouslyAppliedDiff, currentDiff, rawCheckExpressionSurfaces) {
15365
+ const previousDrifts = collectCheckExpressionDrifts(previouslyAppliedDiff);
15366
+ if (previousDrifts.size === 0) return;
15367
+ for (const [key, current] of collectCheckExpressionDrifts(currentDiff)) {
15368
+ if (rawCheckExpressionSurfaces !== void 0 && !rawCheckExpressionSurfaces.has(key)) {
15369
+ continue;
15370
+ }
15371
+ const previous = previousDrifts.get(key);
15372
+ if (previous !== void 0 && previous.desiredExpression === current.desiredExpression && previous.databaseExpression === current.databaseExpression) {
15373
+ throw new NonConvergentSchemaDiffError(
15374
+ current.table,
15375
+ current.constraint,
15376
+ current.desiredExpression,
15377
+ current.databaseExpression
15378
+ );
15379
+ }
15380
+ }
15381
+ }
15382
+ function collectCheckExpressionDrifts(diff) {
15383
+ const dropped = /* @__PURE__ */ new Map();
15384
+ const drifts = /* @__PURE__ */ new Map();
15385
+ for (const change of diff.changes) {
15386
+ if (change.kind === "drop_check_constraint") {
15387
+ const check = checkFromChange(change);
15388
+ if (check === void 0) continue;
15389
+ dropped.set(driftKey(change.table, check.name), check.expression);
15390
+ continue;
15391
+ }
15392
+ if (change.kind === "add_check_constraint") {
15393
+ const check = checkFromChange(change);
15394
+ if (check === void 0) continue;
15395
+ const key = driftKey(change.table, check.name);
15396
+ const databaseExpression = dropped.get(key);
15397
+ if (databaseExpression === void 0) continue;
15398
+ drifts.set(key, {
15399
+ table: change.table,
15400
+ constraint: check.name,
15401
+ desiredExpression: check.expression,
15402
+ databaseExpression
15403
+ });
15404
+ }
15405
+ }
15406
+ return drifts;
15407
+ }
15408
+ function checkFromChange(change) {
15409
+ const check = change.meta?.check;
15410
+ if (check === void 0 || typeof check.name !== "string" || typeof check.expression !== "string") {
15411
+ return void 0;
15412
+ }
15413
+ return check;
15414
+ }
15415
+ function driftKey(table, constraint) {
15416
+ return JSON.stringify([table, constraint]);
15417
+ }
15418
+ function assertNoCheckFallbackUsesAddedEnumValue(diff, rawCheckExpressionSurfaces) {
15419
+ const addedEnumValues = collectAddedEnumValues(diff);
15420
+ if (addedEnumValues.length === 0) return;
15421
+ for (const change of diff.changes) {
15422
+ if (change.kind !== "add_check_constraint") continue;
15423
+ const check = checkFromChange(change);
15424
+ if (check === void 0) continue;
15425
+ if (!rawCheckExpressionSurfaces.has(driftKey(change.table, check.name))) {
15426
+ continue;
15427
+ }
15428
+ throw new CheckConstraintNewEnumValueError(
15429
+ change.table,
15430
+ check.name,
15431
+ addedEnumValues
15432
+ );
15433
+ }
15434
+ }
15435
+ function collectAddedEnumValues(diff) {
15436
+ const added = [];
15437
+ for (const change of diff.changes) {
15438
+ if (change.kind !== "alter_enum_add_value") continue;
15439
+ const enumDef = change.meta?.enum;
15440
+ const value = change.meta?.value;
15441
+ if (enumDef === void 0 || typeof value !== "string") continue;
15442
+ added.push({ enumName: formatEnumName(enumDef), value });
15443
+ }
15444
+ return added;
15445
+ }
15446
+ function formatEnumName(enumDef) {
15447
+ return enumDef.schema !== void 0 ? `${enumDef.schema}.${enumDef.name}` : enumDef.name;
15448
+ }
15449
+ function toIntrospectionOptions(options) {
15450
+ return {
15451
+ ...options?.schema !== void 0 ? { schema: options.schema } : {}
15452
+ };
15453
+ }
15454
+ function supportsDDLCheckConstraints(options) {
15455
+ const caps = options?.dialectCapabilities;
15456
+ return caps === void 0 || caps.supportsDDLCheckConstraints === true;
15457
+ }
15458
+ function toCanonicalizerOptions(options, rawCheckExpressionSurfaces) {
15459
+ return {
15460
+ ...options?.schema !== void 0 ? { schemaName: options.schema } : {},
15461
+ ...options?.dbCasing !== void 0 ? { dbCasing: options.dbCasing } : {},
15462
+ ...options?.requireExpressionCanonicalization !== void 0 ? { requireCanonicalization: options.requireExpressionCanonicalization } : {},
15463
+ onWarning: (warning) => {
15464
+ recordRawCheckExpressionSurface(rawCheckExpressionSurfaces, warning);
15465
+ if (options?.onWarning !== void 0) {
15466
+ options.onWarning(warning.message);
15467
+ } else {
15468
+ console.warn(`Warning: ${warning.message}`);
15469
+ }
15470
+ }
15471
+ };
15472
+ }
15473
+ function recordRawCheckExpressionSurface(rawCheckExpressionSurfaces, warning) {
15474
+ rawCheckExpressionSurfaces.add(driftKey(warning.table, warning.constraint));
15475
+ }
15476
+ function toCompareOptions(options, strictness) {
15477
+ return {
15478
+ ...options?.dbCasing !== void 0 ? { dbCasing: options.dbCasing } : {},
15479
+ ...options?.dialectCapabilities !== void 0 ? { dialectCapabilities: options.dialectCapabilities } : {},
15480
+ ...options?.ignoreUnmanagedExtensions !== void 0 ? { ignoreUnmanagedExtensions: options.ignoreUnmanagedExtensions } : {},
15481
+ ...strictness.delegateExpressionCanonicalization ? {
15482
+ requireExpressionCanonicalization: true
15483
+ } : {}
15484
+ };
15485
+ }
15486
+
14823
15487
  // src/ddl/migration-file.ts
14824
15488
  function generateMigrationFile(diff, options) {
14825
15489
  const upStatements = generateMigrationSQL(diff, options);
@@ -15163,9 +15827,12 @@ function vectorDims(column) {
15163
15827
 
15164
15828
  // src/introspection.ts
15165
15829
  init_assert_field();
15166
- init_db_type();
15167
- import { ModelIRImpl } from "@dbsp/core";
15830
+ import { ModelIRImpl as ModelIRImpl2 } from "@dbsp/core";
15168
15831
  import { buildRelationKeyFields } from "@dbsp/types";
15832
+ init_db_type();
15833
+ function isCheckedOutClient(connection) {
15834
+ return typeof connection === "object" && connection !== null && "release" in connection && typeof connection.release === "function";
15835
+ }
15169
15836
  function parseExpressionsList(raw) {
15170
15837
  const results = [];
15171
15838
  let depth = 0;
@@ -15187,34 +15854,22 @@ function parseExpressionsList(raw) {
15187
15854
  return results;
15188
15855
  }
15189
15856
  async function queryAllCatalogs(pool, schema) {
15190
- const [
15191
- columnsResult,
15192
- pksResult,
15193
- fksResult,
15194
- indexesResult,
15195
- uniqueColumnsResult,
15196
- enumsResult,
15197
- commentsResult,
15198
- checksResult,
15199
- partitionsResult,
15200
- extensionsResult,
15201
- sequencesResult,
15202
- rlsResult,
15203
- policiesResult,
15204
- formattedColumnTypesResult
15205
- ] = await Promise.all([
15206
- // 1. Columns (including identity and collation)
15207
- pool.query(
15208
- `SELECT table_name, column_name, data_type, udt_name, is_nullable, column_default,
15857
+ const catalogQueries = [
15858
+ () => (
15859
+ // 1. Columns (including identity and collation)
15860
+ pool.query(
15861
+ `SELECT table_name, column_name, data_type, udt_name, is_nullable, column_default,
15209
15862
  collation_name, is_identity, identity_generation
15210
15863
  FROM information_schema.columns
15211
15864
  WHERE table_schema = $1
15212
15865
  ORDER BY table_name, ordinal_position`,
15213
- [schema]
15866
+ [schema]
15867
+ )
15214
15868
  ),
15215
- // 2. Primary keys
15216
- pool.query(
15217
- `SELECT tc.table_name, kcu.column_name
15869
+ () => (
15870
+ // 2. Primary keys
15871
+ pool.query(
15872
+ `SELECT tc.table_name, kcu.column_name
15218
15873
  FROM information_schema.table_constraints tc
15219
15874
  JOIN information_schema.key_column_usage kcu
15220
15875
  ON tc.constraint_name = kcu.constraint_name
@@ -15222,11 +15877,13 @@ async function queryAllCatalogs(pool, schema) {
15222
15877
  WHERE tc.constraint_type = 'PRIMARY KEY'
15223
15878
  AND tc.table_schema = $1
15224
15879
  ORDER BY tc.table_name, kcu.ordinal_position`,
15225
- [schema]
15880
+ [schema]
15881
+ )
15226
15882
  ),
15227
- // 3. Foreign keys
15228
- pool.query(
15229
- `SELECT
15883
+ () => (
15884
+ // 3. Foreign keys
15885
+ pool.query(
15886
+ `SELECT
15230
15887
  c.conname AS constraint_name,
15231
15888
  source_rel.relname AS source_table,
15232
15889
  source_attr.attname AS source_column,
@@ -15265,18 +15922,20 @@ async function queryAllCatalogs(pool, schema) {
15265
15922
  AND c.conparentid = 0
15266
15923
  AND source_ns.nspname = $1
15267
15924
  ORDER BY c.conname, cols.ord`,
15268
- [schema]
15925
+ [schema]
15926
+ )
15269
15927
  ),
15270
- // 4. Indexes (excluding PK-backing indexes and unique-constraint-backing indexes).
15271
- // Unique constraints created via col.unique / UNIQUE keyword in DDL produce an implicit
15272
- // backing index that is NOT a user-defined index — it is tracked via col.unique on the
15273
- // ColumnIR instead. Including it here would cause spurious drop_index diffs on roundtrip.
15274
- // Enhanced to capture:
15275
- // - INCLUDE columns (PG11+): indkey positions > indnkeyatts
15276
- // - Expression index entries: attnum = 0 in indkey → pg_get_expr(indexprs)
15277
- // - Per-column operator classes: pg_opclass join on indclass, non-default only
15278
- pool.query(
15279
- `SELECT
15928
+ () => (
15929
+ // 4. Indexes (excluding PK-backing indexes and unique-constraint-backing indexes).
15930
+ // Unique constraints created via col.unique / UNIQUE keyword in DDL produce an implicit
15931
+ // backing index that is NOT a user-defined index — it is tracked via col.unique on the
15932
+ // ColumnIR instead. Including it here would cause spurious drop_index diffs on roundtrip.
15933
+ // Enhanced to capture:
15934
+ // - INCLUDE columns (PG11+): indkey positions > indnkeyatts
15935
+ // - Expression index entries: attnum = 0 in indkey → pg_get_expr(indexprs)
15936
+ // - Per-column operator classes: pg_opclass join on indclass, non-default only
15937
+ pool.query(
15938
+ `SELECT
15280
15939
  i.relname AS index_name,
15281
15940
  t.relname AS table_name,
15282
15941
  -- Key columns (attnum != 0 means real column, within key positions)
@@ -15326,11 +15985,13 @@ async function queryAllCatalogs(pool, schema) {
15326
15985
  GROUP BY i.relname, t.relname, ix.indisunique, am.amname,
15327
15986
  ix.indpred, ix.indrelid, ix.indexprs, i.reloptions
15328
15987
  ORDER BY t.relname, i.relname`,
15329
- [schema]
15988
+ [schema]
15989
+ )
15330
15990
  ),
15331
- // 5. Single-column UNIQUE constraints tracked as ColumnIR.unique
15332
- pool.query(
15333
- `SELECT rel.relname AS table_name, att.attname AS column_name, c.conname AS constraint_name
15991
+ () => (
15992
+ // 5. Single-column UNIQUE constraints tracked as ColumnIR.unique
15993
+ pool.query(
15994
+ `SELECT rel.relname AS table_name, att.attname AS column_name, c.conname AS constraint_name
15334
15995
  FROM pg_constraint c
15335
15996
  JOIN pg_class rel ON rel.oid = c.conrelid
15336
15997
  JOIN pg_namespace ns ON ns.oid = rel.relnamespace
@@ -15338,11 +15999,13 @@ async function queryAllCatalogs(pool, schema) {
15338
15999
  WHERE c.contype = 'u'
15339
16000
  AND array_length(c.conkey, 1) = 1
15340
16001
  AND ns.nspname = $1`,
15341
- [schema]
16002
+ [schema]
16003
+ )
15342
16004
  ),
15343
- // 6. ENUM types
15344
- pool.query(
15345
- `SELECT
16005
+ () => (
16006
+ // 6. ENUM types
16007
+ pool.query(
16008
+ `SELECT
15346
16009
  t.typname AS name,
15347
16010
  n.nspname AS schema,
15348
16011
  array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
@@ -15352,11 +16015,13 @@ async function queryAllCatalogs(pool, schema) {
15352
16015
  WHERE t.typtype = 'e'
15353
16016
  AND n.nspname = $1
15354
16017
  GROUP BY t.typname, n.nspname`,
15355
- [schema]
16018
+ [schema]
16019
+ )
15356
16020
  ),
15357
- // 7. Comments (table and column level) from pg_description
15358
- pool.query(
15359
- `SELECT
16021
+ () => (
16022
+ // 7. Comments (table and column level) from pg_description
16023
+ pool.query(
16024
+ `SELECT
15360
16025
  c.relname AS table_name,
15361
16026
  a.attname AS column_name,
15362
16027
  d.description AS comment
@@ -15366,23 +16031,29 @@ async function queryAllCatalogs(pool, schema) {
15366
16031
  LEFT JOIN pg_attribute a ON a.attrelid = d.objoid AND a.attnum = d.objsubid
15367
16032
  WHERE n.nspname = $1
15368
16033
  AND d.objsubid >= 0`,
15369
- [schema]
16034
+ [schema]
16035
+ )
15370
16036
  ),
15371
- // 8. CHECK constraints
15372
- pool.query(
15373
- `SELECT
16037
+ () => (
16038
+ // 8. CHECK constraints
16039
+ pool.query(
16040
+ `SELECT
15374
16041
  c.conname AS name,
15375
16042
  pg_get_constraintdef(c.oid, false) AS expression,
15376
- c.conrelid::regclass::text AS raw_table
16043
+ NOT c.convalidated AS not_valid,
16044
+ r.relname AS raw_table
15377
16045
  FROM pg_constraint c
15378
- JOIN pg_namespace n ON n.oid = c.connamespace
16046
+ JOIN pg_class r ON r.oid = c.conrelid
16047
+ JOIN pg_namespace n ON n.oid = r.relnamespace
15379
16048
  WHERE c.contype = 'c'
15380
16049
  AND n.nspname = $1`,
15381
- [schema]
16050
+ [schema]
16051
+ )
15382
16052
  ),
15383
- // 9. Partition configurations
15384
- pool.query(
15385
- `SELECT
16053
+ () => (
16054
+ // 9. Partition configurations
16055
+ pool.query(
16056
+ `SELECT
15386
16057
  c.relname AS table_name,
15387
16058
  p.partstrat AS strategy,
15388
16059
  array_agg(a.attname ORDER BY pk.n) AS columns
@@ -15393,37 +16064,45 @@ async function queryAllCatalogs(pool, schema) {
15393
16064
  JOIN pg_attribute a ON a.attrelid = p.partrelid AND a.attnum = pk.attnum
15394
16065
  WHERE n.nspname = $1
15395
16066
  GROUP BY c.relname, p.partstrat`,
15396
- [schema]
16067
+ [schema]
16068
+ )
15397
16069
  ),
15398
- // 10. Installed extensions (no schema param — queries globally; skip plpgsql)
15399
- pool.query(
15400
- `SELECT extname AS name
16070
+ () => (
16071
+ // 10. Installed extensions (no schema param — queries globally; skip plpgsql)
16072
+ pool.query(
16073
+ `SELECT extname AS name
15401
16074
  FROM pg_extension
15402
16075
  WHERE extname != 'plpgsql'`
16076
+ )
15403
16077
  ),
15404
- // 11. Sequences not backed by SERIAL
15405
- pool.query(
15406
- `SELECT s.sequencename AS name, s.start_value, s.increment_by, s.min_value, s.max_value, s.cycle
16078
+ () => (
16079
+ // 11. Sequences not backed by SERIAL
16080
+ pool.query(
16081
+ `SELECT s.sequencename AS name, s.start_value, s.increment_by, s.min_value, s.max_value, s.cycle
15407
16082
  FROM pg_sequences s
15408
16083
  LEFT JOIN pg_class c ON c.relname = s.sequencename AND c.relkind = 'S'
15409
16084
  AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = s.schemaname)
15410
16085
  LEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype = 'a'
15411
16086
  WHERE s.schemaname = $1
15412
16087
  AND d.objid IS NULL`,
15413
- [schema]
16088
+ [schema]
16089
+ )
15414
16090
  ),
15415
- // 12. RLS enabled state per table
15416
- pool.query(
15417
- `SELECT c.relname AS table_name, c.relrowsecurity AS rls_enabled
16091
+ () => (
16092
+ // 12. RLS enabled state per table
16093
+ pool.query(
16094
+ `SELECT c.relname AS table_name, c.relrowsecurity AS rls_enabled
15418
16095
  FROM pg_class c
15419
16096
  JOIN pg_namespace n ON n.oid = c.relnamespace
15420
16097
  WHERE n.nspname = $1
15421
16098
  AND c.relkind = 'r'`,
15422
- [schema]
16099
+ [schema]
16100
+ )
15423
16101
  ),
15424
- // 13. Row-Level Security policies
15425
- pool.query(
15426
- `SELECT
16102
+ () => (
16103
+ // 13. Row-Level Security policies
16104
+ pool.query(
16105
+ `SELECT
15427
16106
  c.relname AS table_name,
15428
16107
  p.polname AS policy_name,
15429
16108
  p.polcmd AS cmd,
@@ -15435,19 +16114,21 @@ async function queryAllCatalogs(pool, schema) {
15435
16114
  JOIN pg_class c ON c.oid = p.polrelid
15436
16115
  JOIN pg_namespace n ON n.oid = c.relnamespace
15437
16116
  WHERE n.nspname = $1`,
15438
- [schema]
16117
+ [schema]
16118
+ )
15439
16119
  ),
15440
- // 14. Faithful SQL-facing column types via format_type, for three cases:
15441
- // (a) typmod-bearing columns (atttypmod <> -1) — varchar(120), numeric(10,2),
15442
- // timestamptz(3), bit(8), vector(768) — to preserve modifier fidelity (#261);
15443
- // (b) array columns (typcategory 'A') — so integer[] is stored as `integer[]`
15444
- // rather than the internal `_int4` udt_name;
15445
- // (c) non-pg_catalog scalar types — enums/composites/domains in user schemas.
15446
- // `type_schema` is the type's OWN namespace (pg_type.typnamespace). The IR stores
15447
- // it structurally and keeps `originalDbType` bare, so DDL/cast emission can decide
15448
- // whether the type follows the target schema or remains absolute.
15449
- pool.query(
15450
- `SELECT c.relname AS table_name,
16120
+ () => (
16121
+ // 14. Faithful SQL-facing column types via format_type, for three cases:
16122
+ // (a) typmod-bearing columns (atttypmod <> -1) — varchar(120), numeric(10,2),
16123
+ // timestamptz(3), bit(8), vector(768) — to preserve modifier fidelity (#261);
16124
+ // (b) array columns (typcategory 'A') — so integer[] is stored as `integer[]`
16125
+ // rather than the internal `_int4` udt_name;
16126
+ // (c) non-pg_catalog scalar types — enums/composites/domains in user schemas.
16127
+ // `type_schema` is the type's OWN namespace (pg_type.typnamespace). The IR stores
16128
+ // it structurally and keeps `originalDbType` bare, so DDL/cast emission can decide
16129
+ // whether the type follows the target schema or remains absolute.
16130
+ pool.query(
16131
+ `SELECT c.relname AS table_name,
15451
16132
  a.attname AS column_name,
15452
16133
  format_type(a.atttypid, a.atttypmod) AS db_type,
15453
16134
  tn.nspname AS type_schema
@@ -15465,9 +16146,32 @@ async function queryAllCatalogs(pool, schema) {
15465
16146
  OR t.typcategory = 'A'
15466
16147
  OR t.typnamespace <> 'pg_catalog'::regnamespace
15467
16148
  )`,
15468
- [schema]
16149
+ [schema]
16150
+ )
15469
16151
  )
15470
- ]);
16152
+ ];
16153
+ const results = pool.sequentialCatalogReads ? [] : await Promise.all(catalogQueries.map((query) => query()));
16154
+ if (pool.sequentialCatalogReads) {
16155
+ for (const query of catalogQueries) {
16156
+ results.push(await query());
16157
+ }
16158
+ }
16159
+ const [
16160
+ columnsResult,
16161
+ pksResult,
16162
+ fksResult,
16163
+ indexesResult,
16164
+ uniqueColumnsResult,
16165
+ enumsResult,
16166
+ commentsResult,
16167
+ checksResult,
16168
+ partitionsResult,
16169
+ extensionsResult,
16170
+ sequencesResult,
16171
+ rlsResult,
16172
+ policiesResult,
16173
+ formattedColumnTypesResult
16174
+ ] = results;
15471
16175
  return {
15472
16176
  columns: columnsResult.rows,
15473
16177
  pks: pksResult.rows,
@@ -15503,10 +16207,11 @@ function buildPartitionMap(rows) {
15503
16207
  function buildCheckMap(rows) {
15504
16208
  const result = /* @__PURE__ */ new Map();
15505
16209
  for (const ck of rows) {
15506
- const tableName = ck.raw_table.replace(/^".*"\.|^.*\./u, "");
16210
+ const tableName = ck.raw_table;
15507
16211
  const checkIR = {
15508
16212
  name: ck.name,
15509
- expression: ck.expression
16213
+ expression: stripNotValidSuffix(ck.expression),
16214
+ ...ck.not_valid ? { notValid: true } : {}
15510
16215
  };
15511
16216
  const existing = result.get(tableName);
15512
16217
  if (existing) {
@@ -15782,6 +16487,20 @@ function buildSequenceMap(rows) {
15782
16487
  return result;
15783
16488
  }
15784
16489
  async function introspect(pool, options) {
16490
+ if (isCheckedOutClient(pool)) {
16491
+ throw new Error(
16492
+ "introspect() takes a pg.Pool, and was given a checked-out pg.PoolClient. That client may be sitting inside a transaction that belongs to you, and a catalog query that fails there would abort it. dbsp will not guess whose transaction it is: declare it \u2014 new PgsqlAdapter(client, { borrowedClient: true }).introspect() \u2014 and the declaration is what buys the savepoint protection."
16493
+ );
16494
+ }
16495
+ return introspectWithExecutor(
16496
+ {
16497
+ dbspProtectedCatalogExecutor: true,
16498
+ query: (sql, parameters) => parameters === void 0 ? pool.query(sql) : pool.query(sql, [...parameters])
16499
+ },
16500
+ options
16501
+ );
16502
+ }
16503
+ async function introspectWithExecutor(pool, options) {
15785
16504
  const schema = options?.schema ?? "public";
15786
16505
  const warnings = [];
15787
16506
  const raw = await queryAllCatalogs(pool, schema);
@@ -15839,7 +16558,7 @@ async function introspect(pool, options) {
15839
16558
  externalTables.add(fk.target);
15840
16559
  }
15841
16560
  }
15842
- const modelIR = new ModelIRImpl(
16561
+ const modelIR = new ModelIRImpl2(
15843
16562
  tables,
15844
16563
  relations,
15845
16564
  enumMap,
@@ -16026,18 +16745,18 @@ function mapDeleteRule(rule) {
16026
16745
  }
16027
16746
  }
16028
16747
  function filterTables(tableNames, options) {
16029
- let result = tableNames;
16030
- if (options?.include?.length) {
16031
- result = result.filter(
16032
- (name) => options.include.some((pattern) => matchGlob(pattern, name))
16033
- );
16748
+ return tableNames.filter(
16749
+ (name) => isTableInIntrospectionScope(name, options)
16750
+ );
16751
+ }
16752
+ function isTableInIntrospectionScope(tableName, options) {
16753
+ if (options?.include?.length && !options.include.some((pattern) => matchGlob(pattern, tableName))) {
16754
+ return false;
16034
16755
  }
16035
- if (options?.exclude?.length) {
16036
- result = result.filter(
16037
- (name) => !options.exclude.some((pattern) => matchGlob(pattern, name))
16038
- );
16756
+ if (options?.exclude?.length && options.exclude.some((pattern) => matchGlob(pattern, tableName))) {
16757
+ return false;
16039
16758
  }
16040
- return result;
16759
+ return true;
16041
16760
  }
16042
16761
  function matchGlob(pattern, value) {
16043
16762
  if (!pattern.includes("*")) return pattern === value;
@@ -16388,7 +17107,7 @@ function compileInsertFrom(config, ctx, state) {
16388
17107
  relpersistence: "p"
16389
17108
  };
16390
17109
  if (sourceSchema) {
16391
- sourceRelation.schemaname = naming.toDatabase(sourceSchema);
17110
+ sourceRelation.schemaname = sourceSchema;
16392
17111
  }
16393
17112
  const selectQuery = {
16394
17113
  SelectStmt: {
@@ -16470,7 +17189,7 @@ function compileUpsertFrom(config, ctx, state) {
16470
17189
  relpersistence: "p"
16471
17190
  };
16472
17191
  if (sourceSchema) {
16473
- sourceRelation.schemaname = naming.toDatabase(sourceSchema);
17192
+ sourceRelation.schemaname = sourceSchema;
16474
17193
  }
16475
17194
  const selectQuery = {
16476
17195
  SelectStmt: {
@@ -16855,19 +17574,21 @@ init_naming_plugin();
16855
17574
  init_param_ref();
16856
17575
 
16857
17576
  // src/pgsql-adapter.ts
17577
+ import { randomBytes } from "crypto";
16858
17578
  import { POSTGRESQL_CAPABILITIES as POSTGRESQL_CAPABILITIES2, plan as planFn2 } from "@dbsp/core";
16859
17579
  import { getNqlBindingRefName as getNqlBindingRefName2, isNqlBindingRef as isNqlBindingRef2 } from "@dbsp/types/internal";
16860
17580
 
16861
17581
  // src/adapter-compiler-includes.ts
16862
17582
  init_ast_helpers();
17583
+ import { toColumnList as toColumnList8 } from "@dbsp/types";
16863
17584
  init_deparse();
16864
17585
  init_handlers();
16865
- import { toColumnList as toColumnList8 } from "@dbsp/types";
16866
17586
  function compileSubqueryInclude(info, parentIds, _options, deps) {
16867
17587
  const schemaName = deps.schemaName;
16868
17588
  const state = createCompilerState();
16869
17589
  if (parentIds.length === 0) {
16870
- const tableName = schemaName ? `"${deps.naming.toDatabase(schemaName)}"."${deps.naming.toDatabase(info.targetTable)}"` : `"${deps.naming.toDatabase(info.targetTable)}"`;
17590
+ const dbTargetTable = deps.naming.toDatabase(info.targetTable);
17591
+ const tableName = schemaName ? `${quoteIdent2(schemaName, "schema")}.${quoteIdent2(dbTargetTable, "table")}` : quoteIdent2(dbTargetTable, "table");
16871
17592
  return {
16872
17593
  sql: `SELECT * FROM ${tableName} WHERE FALSE`,
16873
17594
  parameters: []
@@ -16898,7 +17619,7 @@ function compileSubqueryInclude(info, parentIds, _options, deps) {
16898
17619
  inh: true,
16899
17620
  relpersistence: "p",
16900
17621
  ...schemaName && {
16901
- schemaname: deps.naming.toDatabase(schemaName)
17622
+ schemaname: schemaName
16902
17623
  }
16903
17624
  }
16904
17625
  }
@@ -18606,7 +19327,7 @@ function getColumnTypes(tableName, columns, deps) {
18606
19327
  const table = deps.model.getTable(tableName);
18607
19328
  if (!table) return void 0;
18608
19329
  let result;
18609
- const targetSchema = deps.schemaName !== void 0 ? deps.naming.toDatabase(deps.schemaName) : void 0;
19330
+ const targetSchema = deps.schemaName;
18610
19331
  for (const col of columns) {
18611
19332
  const columnIR = table.columns.find((c) => c.name === col);
18612
19333
  if (columnIR) {
@@ -19720,6 +20441,224 @@ function mapFetchDirection(direction) {
19720
20441
  init_validate();
19721
20442
  var MAX_NQL_RUNTIME_BINDING_VALUES_PARAMETERS = 32e3;
19722
20443
  var RESOLVE_TABLE_SCHEMA_SQL = (tableParam) => `(SELECT n.nspname FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = to_regclass(quote_ident(${tableParam})))`;
20444
+ var NO_ACTIVE_SQL_TRANSACTION = "25P01";
20445
+ var IN_FAILED_SQL_TRANSACTION = "25P02";
20446
+ var INVALID_SAVEPOINT_SPECIFICATION = "3B001";
20447
+ var SAVEPOINT_RANDOM_BYTES = 16;
20448
+ var RAW_SQL_TRANSACTION_CONTROL_MESSAGE = "Transaction control through raw SQL inside a scope dbsp is managing is unsupported. `COMMIT`, `ROLLBACK`, and `PREPARE TRANSACTION` end the transaction dbsp is working inside; dbsp detects that and fails loudly, but the data is already whatever your statement made it. Raw savepoint control (`SAVEPOINT`, `RELEASE SAVEPOINT`, `ROLLBACK TO SAVEPOINT`) can alter the savepoint stack before dbsp sees the command tag; dbsp poisons the scope, but it cannot make that command un-run. Manage your transaction outside dbsp's calls.";
20449
+ var RAW_SQL_MULTI_COMMAND_MESSAGE = "dbsp cannot reason about a multi-command raw call inside a transaction it manages. Those commands have already run, and dbsp cannot undo what they already did. Send one command per call, or manage the transaction yourself.";
20450
+ var ABORTED_COMMIT_MESSAGE = "PostgreSQL returned ROLLBACK for COMMIT. The transaction was already aborted, no changes were committed, and dbsp is reporting that failed commit explicitly.";
20451
+ var TRANSACTION_ABORTED_MESSAGE = "PostgreSQL transaction is aborted because a statement failed inside a dbsp-managed scope and that failure was caught. Roll back the surrounding transaction; the swallowed statement failure is the cause worth reporting.";
20452
+ var SAVEPOINT_SCOPE_BUSY_MESSAGE = "Cannot start another dbsp savepoint scope on this PostgreSQL connection because one is already active. Savepoint scopes are single-flight per connection; await the active dbsp operation before starting another.";
20453
+ var SAVEPOINT_SCOPE_OWNER_MESSAGE = "This PostgreSQL connection is currently inside a dbsp-managed scope owned by another transaction adapter. Use the transaction adapter passed to the callback instead of an ancestor adapter.";
20454
+ var TRANSACTION_SCOPE_ENDED_MESSAGE = "This PostgreSQL transaction adapter belongs to a transaction that has ended.";
20455
+ var NESTED_TRANSACTION_NOT_AWAITED_MESSAGE = "Nested transactions must be awaited before the transaction callback returns.";
20456
+ var TRANSACTION_CONTROL_COMMAND_TAGS = /* @__PURE__ */ new Set([
20457
+ "BEGIN",
20458
+ "START",
20459
+ "START TRANSACTION",
20460
+ "COMMIT",
20461
+ "ROLLBACK",
20462
+ "SAVEPOINT",
20463
+ "RELEASE",
20464
+ "PREPARE",
20465
+ "PREPARE TRANSACTION"
20466
+ ]);
20467
+ var PREPARE_COMMAND_TAG = "PREPARE";
20468
+ var pgsqlAdapterInternalOptionsKey = /* @__PURE__ */ Symbol(
20469
+ "dbsp-pgsql-adapter-internal-options"
20470
+ );
20471
+ var activeClientScopes = /* @__PURE__ */ new WeakMap();
20472
+ var PgsqlRawSqlTransactionControlError = class extends Error {
20473
+ dbspRawSqlTransactionControl = true;
20474
+ constructor(cause) {
20475
+ super(RAW_SQL_TRANSACTION_CONTROL_MESSAGE, { cause });
20476
+ this.name = "PgsqlRawSqlTransactionControlError";
20477
+ }
20478
+ };
20479
+ var PgsqlTransactionAbortedCommitError = class extends Error {
20480
+ dbspTransactionAbortedCommit = true;
20481
+ constructor(cause) {
20482
+ super(ABORTED_COMMIT_MESSAGE, { cause });
20483
+ this.name = "PgsqlTransactionAbortedCommitError";
20484
+ }
20485
+ };
20486
+ var PgsqlTransactionAbortedError = class extends Error {
20487
+ dbspTransactionAborted = true;
20488
+ constructor(cause) {
20489
+ super(TRANSACTION_ABORTED_MESSAGE, { cause });
20490
+ this.name = "PgsqlTransactionAbortedError";
20491
+ }
20492
+ };
20493
+ function isPoolClientLike(connection) {
20494
+ return typeof connection === "object" && connection !== null && "release" in connection && typeof connection.release === "function";
20495
+ }
20496
+ function poolClientTransactionOpen(client) {
20497
+ const status = client._txStatus;
20498
+ if (status === "T" || status === "E") return true;
20499
+ if (status === "I") return false;
20500
+ return void 0;
20501
+ }
20502
+ function isPgErrorWithCode(error, code) {
20503
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
20504
+ }
20505
+ function nextSavepointName() {
20506
+ return `dbsp_savepoint_${randomBytes(SAVEPOINT_RANDOM_BYTES).toString("hex")}`;
20507
+ }
20508
+ function createScopeToken() {
20509
+ return /* @__PURE__ */ Symbol("dbsp-pgsql-scope");
20510
+ }
20511
+ function createScopeState(statementLock = { tail: void 0 }) {
20512
+ return {
20513
+ poisoned: void 0,
20514
+ statementLock,
20515
+ children: {
20516
+ open: 0,
20517
+ failure: void 0,
20518
+ transactions: /* @__PURE__ */ new Set()
20519
+ },
20520
+ closing: false,
20521
+ streamFailureRecovery: void 0
20522
+ };
20523
+ }
20524
+ var ObservedChildTransactionPromise = class extends Promise {
20525
+ static get [Symbol.species]() {
20526
+ return Promise;
20527
+ }
20528
+ #markObserved;
20529
+ constructor(promise, markObserved) {
20530
+ super((resolve, reject) => {
20531
+ promise.then(resolve, reject);
20532
+ });
20533
+ this.#markObserved = markObserved;
20534
+ }
20535
+ // biome-ignore lint/suspicious/noThenProperty: await and Promise.all must mark nested transaction promises observed.
20536
+ then(onfulfilled, onrejected) {
20537
+ this.#markObserved();
20538
+ return super.then(onfulfilled, onrejected);
20539
+ }
20540
+ catch(onrejected) {
20541
+ this.#markObserved();
20542
+ return super.catch(onrejected);
20543
+ }
20544
+ finally(onfinally) {
20545
+ this.#markObserved();
20546
+ return super.finally(onfinally);
20547
+ }
20548
+ };
20549
+ function normalizeCommandTag(command) {
20550
+ return command?.trim().toUpperCase();
20551
+ }
20552
+ function isTransactionControlCommandTag(command) {
20553
+ const normalized = normalizeCommandTag(command);
20554
+ return normalized !== void 0 && TRANSACTION_CONTROL_COMMAND_TAGS.has(normalized);
20555
+ }
20556
+ function isPrepareCommandTag(command) {
20557
+ return normalizeCommandTag(command) === PREPARE_COMMAND_TAG;
20558
+ }
20559
+ function queryResults(result) {
20560
+ return Array.isArray(result) ? result : [result];
20561
+ }
20562
+ function queryResultMetadata(result) {
20563
+ return {
20564
+ command: result.command,
20565
+ rowCount: result.rowCount
20566
+ };
20567
+ }
20568
+ function queryResultsMetadata(result) {
20569
+ return queryResults(result).map(queryResultMetadata);
20570
+ }
20571
+ function lastQueryResult(result) {
20572
+ if (Array.isArray(result)) {
20573
+ const last = result.at(-1);
20574
+ if (last === void 0) {
20575
+ throw new Error("PostgreSQL returned no query results");
20576
+ }
20577
+ return last;
20578
+ }
20579
+ return result;
20580
+ }
20581
+ function describeThrown(error) {
20582
+ if (error instanceof Error && error.message) return error.message;
20583
+ if (typeof error === "string") return error;
20584
+ return String(error);
20585
+ }
20586
+ function createCleanupFailureError(action, originalError, cleanupError) {
20587
+ const error = new AggregateError(
20588
+ [originalError, cleanupError],
20589
+ `${action}: ${describeThrown(cleanupError)}. The original failure is available as cause.`
20590
+ );
20591
+ Object.defineProperties(error, {
20592
+ cause: { value: originalError, configurable: true },
20593
+ cleanupError: { value: cleanupError, configurable: true },
20594
+ originalError: { value: originalError, configurable: true }
20595
+ });
20596
+ return error;
20597
+ }
20598
+ function createCleanupOnlyError(action, cleanupError) {
20599
+ const error = new AggregateError(
20600
+ [cleanupError],
20601
+ `${action}: ${describeThrown(cleanupError)}.`
20602
+ );
20603
+ Object.defineProperty(error, "cleanupError", {
20604
+ value: cleanupError,
20605
+ configurable: true
20606
+ });
20607
+ return error;
20608
+ }
20609
+ function addCleanupErrorToOriginal(originalError, cleanupError) {
20610
+ if (originalError instanceof Error) {
20611
+ if (!Object.hasOwn(originalError, "cause")) {
20612
+ Object.defineProperty(originalError, "cause", {
20613
+ value: cleanupError,
20614
+ configurable: true
20615
+ });
20616
+ }
20617
+ if (!Object.hasOwn(originalError, "cleanupError")) {
20618
+ Object.defineProperty(originalError, "cleanupError", {
20619
+ value: cleanupError,
20620
+ configurable: true
20621
+ });
20622
+ }
20623
+ return originalError;
20624
+ }
20625
+ return createCleanupFailureError(
20626
+ "PostgreSQL stream cleanup failed: CLOSE cursor failed after the stream failed",
20627
+ originalError,
20628
+ cleanupError
20629
+ );
20630
+ }
20631
+ function cleanupReleaseReason(error) {
20632
+ if (typeof error !== "object" || error === null || !("cleanupError" in error)) {
20633
+ return void 0;
20634
+ }
20635
+ const cleanupError = error.cleanupError;
20636
+ return cleanupError instanceof Error ? cleanupError : true;
20637
+ }
20638
+ function isRawSqlTransactionControlError(error) {
20639
+ return typeof error === "object" && error !== null && "dbspRawSqlTransactionControl" in error && error.dbspRawSqlTransactionControl === true;
20640
+ }
20641
+ function isSavepointUnavailableError(error) {
20642
+ return isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION) || isPgErrorWithCode(error, INVALID_SAVEPOINT_SPECIFICATION);
20643
+ }
20644
+ function addRawSqlTransactionContext(error) {
20645
+ const context = "dbsp rolled back the failed raw SQL to a savepoint, so the surrounding transaction is still usable. Side effects that do not participate in transactional rollback, including sequence advancement and session-level advisory locks, are not undone.";
20646
+ if (error instanceof Error) {
20647
+ error.message = `${error.message}; ${context}`;
20648
+ Object.defineProperty(error, "dbspRawSqlContext", {
20649
+ value: context,
20650
+ configurable: true
20651
+ });
20652
+ }
20653
+ return error;
20654
+ }
20655
+ function protectedStatementCleanupAction(rawSqlStatement, detail) {
20656
+ const prefix = rawSqlStatement ? "PostgreSQL raw SQL cleanup failed" : "PostgreSQL protected statement cleanup failed";
20657
+ return `${prefix}: ${detail}`;
20658
+ }
20659
+ function raise(error) {
20660
+ throw error;
20661
+ }
19723
20662
  function renumberSqlParams2(sql, offset) {
19724
20663
  if (offset === 0) return sql;
19725
20664
  return sql.replace(/\$(\d+)/g, (_match, num) => {
@@ -19866,8 +20805,8 @@ function mapRuntimeBindingColumnType(type) {
19866
20805
  function findRuntimeBindingSourceTable(model, sourceTable) {
19867
20806
  return model.getTable(sourceTable) ?? [...model.tables.values()].find((table) => table.name === sourceTable);
19868
20807
  }
19869
- function runtimeCastTargetSchema(schemaName, naming) {
19870
- return schemaName !== void 0 ? naming.toDatabase(schemaName) : void 0;
20808
+ function runtimeCastTargetSchema(schemaName, _naming) {
20809
+ return schemaName;
19871
20810
  }
19872
20811
  function resolveRuntimeBindingColumnType(bindingName, sourceTable, columnName, schemaName, naming) {
19873
20812
  const column = sourceTable.columns.find(
@@ -20108,9 +21047,38 @@ function createNqlBindingSelectPlan(query) {
20108
21047
  }
20109
21048
  };
20110
21049
  }
20111
- var PgsqlAdapter = class _PgsqlAdapter {
21050
+ function isPgsqlAdapterInternalOptions(options) {
21051
+ return typeof options === "object" && options !== null && pgsqlAdapterInternalOptionsKey in options && options[pgsqlAdapterInternalOptionsKey] === true;
21052
+ }
21053
+ function hasBorrowedClientOption(options) {
21054
+ return typeof options === "object" && options !== null && "borrowedClient" in options && options.borrowedClient === true;
21055
+ }
21056
+ function hasManagedTransactionsOption(options) {
21057
+ return typeof options === "object" && options !== null && "managedTransactions" in options && options.managedTransactions === true;
21058
+ }
21059
+ function isAdapterManagedTransactionOption(options) {
21060
+ return isPgsqlAdapterInternalOptions(options) && options.adapterManagedTransaction === true;
21061
+ }
21062
+ function getDbspScopeTokenOption(options) {
21063
+ return isPgsqlAdapterInternalOptions(options) ? options.dbspScopeToken : void 0;
21064
+ }
21065
+ function getDbspScopeStateOption(options) {
21066
+ return isPgsqlAdapterInternalOptions(options) ? options.dbspScopeState : void 0;
21067
+ }
21068
+ function createPgsqlAdapterFromConstructionOptions(connection, options) {
21069
+ return new PgsqlAdapter(
21070
+ connection,
21071
+ options
21072
+ );
21073
+ }
21074
+ var PgsqlAdapter = class {
20112
21075
  pool;
20113
21076
  client;
21077
+ borrowedClient;
21078
+ managedTransactions;
21079
+ adapterManagedTransaction;
21080
+ scopeToken;
21081
+ scopeState;
20114
21082
  schemaName;
20115
21083
  _dbCasing;
20116
21084
  naming;
@@ -20119,25 +21087,42 @@ var PgsqlAdapter = class _PgsqlAdapter {
20119
21087
  _capabilities;
20120
21088
  defaultPk;
20121
21089
  deriveFk;
20122
- /**
20123
- * Create a new PgsqlAdapter.
20124
- *
20125
- * @param pool - pg.Pool instance, PoolClient (transactions), or undefined (compile-only mode)
20126
- * @param options - Optional configuration
20127
- */
20128
21090
  constructor(pool, options) {
21091
+ const declaredBorrowed = hasBorrowedClientOption(options);
20129
21092
  if (pool != null) {
20130
- if ("release" in pool && typeof pool.release === "function") {
21093
+ if (isPoolClientLike(pool)) {
21094
+ if (!declaredBorrowed) {
21095
+ throw new Error(
21096
+ "PgsqlAdapter received a pg PoolClient without borrowedClient: true. A checked-out client belongs to whoever checked it out: declare it with borrowedClient: true, and pass managedTransactions: true if dbsp should run transactions on it."
21097
+ );
21098
+ }
20131
21099
  this.client = pool;
20132
- this.pool = pool;
21100
+ this.pool = void 0;
21101
+ this.borrowedClient = true;
20133
21102
  } else {
21103
+ if (declaredBorrowed) {
21104
+ throw new Error(
21105
+ "borrowedClient: true requires a pg PoolClient, not a pg Pool."
21106
+ );
21107
+ }
20134
21108
  this.pool = pool;
20135
21109
  this.client = void 0;
21110
+ this.borrowedClient = false;
20136
21111
  }
20137
21112
  } else {
21113
+ if (declaredBorrowed) {
21114
+ throw new Error(
21115
+ "borrowedClient: true requires a pg PoolClient; no connection was given."
21116
+ );
21117
+ }
20138
21118
  this.pool = void 0;
20139
21119
  this.client = void 0;
21120
+ this.borrowedClient = false;
20140
21121
  }
21122
+ this.managedTransactions = hasManagedTransactionsOption(options);
21123
+ this.adapterManagedTransaction = isAdapterManagedTransactionOption(options);
21124
+ this.scopeToken = getDbspScopeTokenOption(options);
21125
+ this.scopeState = getDbspScopeStateOption(options);
20141
21126
  this.schemaName = options?.schemaName;
20142
21127
  this._dbCasing = options?.dbCasing ?? "preserve";
20143
21128
  this.naming = getNamingPluginForDbCasing(this._dbCasing);
@@ -20145,10 +21130,12 @@ var PgsqlAdapter = class _PgsqlAdapter {
20145
21130
  this.logger = options?.logger;
20146
21131
  this.defaultPk = options?.defaultPkColumnName ?? DEFAULT_PK_COLUMN;
20147
21132
  this.deriveFk = options?.deriveFkColumnName ?? defaultFkDerivation;
21133
+ const supportsManagedTransactions = this.pool != null || this.client != null && this.managedTransactions;
20148
21134
  this._capabilities = {
20149
21135
  supportsReturning: true,
20150
21136
  supportsSchemas: true,
20151
- supportsStreaming: pool != null,
21137
+ supportsStreaming: supportsManagedTransactions,
21138
+ supportsTransactions: supportsManagedTransactions,
20152
21139
  supportsRecursiveCTE: true,
20153
21140
  supportsWindowFunctions: true,
20154
21141
  supportsArrayType: true
@@ -20164,13 +21151,25 @@ var PgsqlAdapter = class _PgsqlAdapter {
20164
21151
  * deriveFkColumnName, etc.) are propagated to scoped/transactional adapters.
20165
21152
  */
20166
21153
  cloneOptions(overrides) {
21154
+ const adapterManagedTransaction = overrides.adapterManagedTransaction ?? this.adapterManagedTransaction ? true : void 0;
21155
+ const scopeToken = overrides.dbspScopeToken ?? this.scopeToken;
21156
+ const scopeState = overrides.dbspScopeState ?? this.scopeState;
21157
+ const hasInternalOptions = adapterManagedTransaction === true || scopeToken !== void 0 || scopeState !== void 0;
20167
21158
  return {
21159
+ ...hasInternalOptions && {
21160
+ [pgsqlAdapterInternalOptionsKey]: true
21161
+ },
20168
21162
  ...this.schemaName !== void 0 && { schemaName: this.schemaName },
20169
21163
  ...this._dbCasing !== void 0 && { dbCasing: this._dbCasing },
20170
21164
  ...this.model !== void 0 && { model: this.model },
20171
21165
  ...this.logger !== void 0 && { logger: this.logger },
20172
21166
  defaultPkColumnName: this.defaultPk,
20173
21167
  deriveFkColumnName: this.deriveFk,
21168
+ ...this.borrowedClient && { borrowedClient: true },
21169
+ ...this.managedTransactions && { managedTransactions: true },
21170
+ ...adapterManagedTransaction === true && { adapterManagedTransaction },
21171
+ ...scopeToken !== void 0 && { dbspScopeToken: scopeToken },
21172
+ ...scopeState !== void 0 && { dbspScopeState: scopeState },
20174
21173
  ...overrides
20175
21174
  };
20176
21175
  }
@@ -20417,7 +21416,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
20417
21416
  return this._dbCasing;
20418
21417
  }
20419
21418
  /**
20420
- * Get the underlying pg Pool instance.
21419
+ * Get the underlying pg Pool or borrowed PoolClient instance.
20421
21420
  */
20422
21421
  getPoolInstance() {
20423
21422
  return this.requireConnection();
@@ -20682,8 +21681,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
20682
21681
  * Results are transformed to use model naming convention (e.g., snake_case → camelCase)
20683
21682
  */
20684
21683
  async execute(query) {
20685
- const executor = this.requireConnection();
20686
- const result = await executor.query(query.sql, [...query.parameters]);
21684
+ const result = await this.executeQueryProtectingOpenTransaction(query.sql, query.parameters);
20687
21685
  return this.transformResultRows(result.rows);
20688
21686
  }
20689
21687
  /**
@@ -20741,32 +21739,117 @@ var PgsqlAdapter = class _PgsqlAdapter {
20741
21739
  const adapter = this;
20742
21740
  async function* streamGenerator() {
20743
21741
  if (adapter.client) {
20744
- yield* adapter.streamWithClient(adapter.client, query, chunkSize);
21742
+ if (!adapter.managedTransactions) {
21743
+ throw new Error(
21744
+ "stream() requires a PostgreSQL transaction because cursors do not survive outside one. This adapter uses a borrowed PoolClient; pass managedTransactions: true to let dbsp open a transaction or savepoint for the stream."
21745
+ );
21746
+ }
21747
+ yield* adapter.streamWithManagedClient(
21748
+ adapter.client,
21749
+ query,
21750
+ chunkSize
21751
+ );
20745
21752
  return;
20746
21753
  }
20747
21754
  const pool = adapter.requireConnection();
20748
21755
  const client = await pool.connect();
21756
+ let begun = false;
20749
21757
  let committed = false;
21758
+ let streamFailed = false;
21759
+ let releaseScope;
21760
+ let scopeToken;
21761
+ let scopeState;
21762
+ let releaseError;
21763
+ let cleanupErrorToThrow;
21764
+ let streamRolledBackBeforeClose = false;
20750
21765
  try {
20751
- await client.query("BEGIN");
20752
- yield* adapter.streamWithClient(client, query, chunkSize);
20753
- await client.query("COMMIT");
21766
+ scopeToken = createScopeToken();
21767
+ scopeState = createScopeState();
21768
+ releaseScope = adapter.enterTransactionScope(
21769
+ client,
21770
+ scopeToken,
21771
+ scopeState
21772
+ );
21773
+ await adapter.executeScopeBoundaryStatement(
21774
+ client,
21775
+ scopeToken,
21776
+ scopeState,
21777
+ "BEGIN"
21778
+ );
21779
+ begun = true;
21780
+ scopeState.streamFailureRecovery = async () => {
21781
+ await adapter.rollbackTransactionIfOpen(
21782
+ client,
21783
+ scopeToken,
21784
+ scopeState
21785
+ );
21786
+ streamRolledBackBeforeClose = true;
21787
+ };
21788
+ yield* adapter.streamWithClient(
21789
+ client,
21790
+ query,
21791
+ chunkSize,
21792
+ scopeToken,
21793
+ scopeState.streamFailureRecovery
21794
+ );
21795
+ adapter.closeScopeAndAssertChildren(scopeState);
21796
+ await adapter.drainScopeWork(scopeState);
21797
+ adapter.throwIfScopePoisoned(scopeState);
21798
+ const commitResult = await adapter.executeScopeBoundaryStatement(
21799
+ client,
21800
+ scopeToken,
21801
+ scopeState,
21802
+ "COMMIT"
21803
+ );
20754
21804
  committed = true;
21805
+ adapter.assertCommitSucceeded(commitResult);
20755
21806
  } catch (error) {
20756
- await client.query("ROLLBACK");
20757
- throw error;
21807
+ let transactionError = error;
21808
+ try {
21809
+ adapter.closeScopeAndAssertChildren(scopeState);
21810
+ } catch (scopeError) {
21811
+ transactionError = scopeError;
21812
+ }
21813
+ streamFailed = true;
21814
+ if (begun && !committed && !streamRolledBackBeforeClose) {
21815
+ try {
21816
+ await adapter.rollbackTransactionIfOpen(
21817
+ client,
21818
+ scopeToken,
21819
+ scopeState
21820
+ );
21821
+ } catch (rollbackErr) {
21822
+ releaseError = rollbackErr instanceof Error ? rollbackErr : true;
21823
+ throw createCleanupFailureError(
21824
+ "PostgreSQL stream cleanup failed: ROLLBACK failed after the stream failed",
21825
+ transactionError,
21826
+ rollbackErr
21827
+ );
21828
+ }
21829
+ }
21830
+ throw transactionError;
20758
21831
  } finally {
20759
- if (!committed) {
21832
+ if (begun && !committed && !streamFailed && !streamRolledBackBeforeClose) {
21833
+ adapter.closeScope(scopeState);
20760
21834
  try {
20761
- await client.query("ROLLBACK");
21835
+ await adapter.rollbackTransactionIfOpen(
21836
+ client,
21837
+ scopeToken,
21838
+ scopeState
21839
+ );
20762
21840
  } catch (rollbackErr) {
20763
- adapter.logger?.debug?.(
20764
- "Rollback failed during cleanup",
21841
+ releaseError = rollbackErr instanceof Error ? rollbackErr : true;
21842
+ cleanupErrorToThrow = createCleanupOnlyError(
21843
+ "PostgreSQL stream cleanup failed: ROLLBACK failed after the stream was closed early",
20765
21844
  rollbackErr
20766
21845
  );
20767
21846
  }
20768
21847
  }
20769
- client.release();
21848
+ releaseScope?.();
21849
+ adapter.releaseClient(client, releaseError);
21850
+ if (cleanupErrorToThrow !== void 0) {
21851
+ raise(cleanupErrorToThrow);
21852
+ }
20770
21853
  }
20771
21854
  }
20772
21855
  return streamGenerator();
@@ -20774,16 +21857,26 @@ var PgsqlAdapter = class _PgsqlAdapter {
20774
21857
  /**
20775
21858
  * Internal: Stream with an existing client using cursors.
20776
21859
  */
20777
- async *streamWithClient(client, query, chunkSize) {
21860
+ async *streamWithClient(client, query, chunkSize, allowedScopeToken, recoverBeforeCloseOnError) {
20778
21861
  const cursorName = generateCursorName();
20779
- await client.query(
21862
+ await this.executeConnectionStatement(
21863
+ client,
20780
21864
  `DECLARE ${cursorName} NO SCROLL CURSOR FOR ${query.sql}`,
20781
- query.parameters
21865
+ query.parameters,
21866
+ {
21867
+ ...allowedScopeToken !== void 0 && { allowedScopeToken }
21868
+ }
20782
21869
  );
21870
+ let streamError;
20783
21871
  try {
20784
21872
  while (true) {
20785
- const result = await client.query(
20786
- `FETCH FORWARD ${chunkSize} FROM ${cursorName}`
21873
+ const result = await this.executeConnectionStatement(
21874
+ client,
21875
+ `FETCH FORWARD ${chunkSize} FROM ${cursorName}`,
21876
+ void 0,
21877
+ {
21878
+ ...allowedScopeToken !== void 0 && { allowedScopeToken }
21879
+ }
20787
21880
  );
20788
21881
  if (result.rows.length === 0) {
20789
21882
  break;
@@ -20795,8 +21888,263 @@ var PgsqlAdapter = class _PgsqlAdapter {
20795
21888
  break;
20796
21889
  }
20797
21890
  }
21891
+ } catch (error) {
21892
+ streamError = error;
21893
+ throw error;
21894
+ } finally {
21895
+ if (streamError !== void 0 && recoverBeforeCloseOnError !== void 0) {
21896
+ try {
21897
+ await recoverBeforeCloseOnError();
21898
+ } catch (cleanupErr) {
21899
+ raise(
21900
+ createCleanupFailureError(
21901
+ "PostgreSQL stream cleanup failed: rollback before CLOSE cursor failed after the stream failed",
21902
+ streamError,
21903
+ cleanupErr
21904
+ )
21905
+ );
21906
+ }
21907
+ }
21908
+ try {
21909
+ await this.executeConnectionStatement(
21910
+ client,
21911
+ `CLOSE ${cursorName}`,
21912
+ void 0,
21913
+ {
21914
+ ...allowedScopeToken !== void 0 && { allowedScopeToken },
21915
+ allowPoisonedScope: true
21916
+ }
21917
+ );
21918
+ } catch (cleanupErr) {
21919
+ if (streamError !== void 0) {
21920
+ raise(addCleanupErrorToOriginal(streamError, cleanupErr));
21921
+ }
21922
+ raise(
21923
+ createCleanupOnlyError(
21924
+ "PostgreSQL stream cleanup failed: CLOSE cursor failed",
21925
+ cleanupErr
21926
+ )
21927
+ );
21928
+ }
21929
+ }
21930
+ }
21931
+ async *streamWithManagedClient(client, query, chunkSize) {
21932
+ if (this.adapterManagedTransaction) {
21933
+ this.assertCanUseClient(client, this.scopeToken);
21934
+ yield* this.streamWithClient(
21935
+ client,
21936
+ query,
21937
+ chunkSize,
21938
+ this.scopeToken,
21939
+ this.scopeState?.streamFailureRecovery
21940
+ );
21941
+ return;
21942
+ }
21943
+ yield* this.streamWithManagedClientSavepointScope(
21944
+ client,
21945
+ query,
21946
+ chunkSize
21947
+ );
21948
+ }
21949
+ async *streamWithManagedClientSavepointScope(client, query, chunkSize) {
21950
+ const savepointName = nextSavepointName();
21951
+ const scopeToken = this.scopeToken ?? createScopeToken();
21952
+ const scopeState = this.scopeState ?? createScopeState();
21953
+ const releaseScope = this.enterSavepointScope(
21954
+ client,
21955
+ scopeToken,
21956
+ scopeState,
21957
+ "statement"
21958
+ );
21959
+ try {
21960
+ await this.executeConnectionStatement(
21961
+ client,
21962
+ `SAVEPOINT ${savepointName}`,
21963
+ void 0,
21964
+ {
21965
+ allowedScopeToken: scopeToken,
21966
+ inspectTransactionControl: false
21967
+ }
21968
+ );
21969
+ } catch (error) {
21970
+ releaseScope();
21971
+ if (isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
21972
+ yield* this.streamWithClientTransaction(client, query, chunkSize);
21973
+ return;
21974
+ }
21975
+ throw error;
21976
+ }
21977
+ let completed = false;
21978
+ let streamError;
21979
+ let savepointRolledBackBeforeClose = false;
21980
+ const recoverStreamFailure = async () => {
21981
+ const rolledBack = await this.rollbackSavepoint(
21982
+ client,
21983
+ savepointName,
21984
+ scopeToken,
21985
+ scopeState
21986
+ );
21987
+ savepointRolledBackBeforeClose ||= rolledBack;
21988
+ };
21989
+ try {
21990
+ yield* this.streamWithClient(
21991
+ client,
21992
+ query,
21993
+ chunkSize,
21994
+ scopeToken,
21995
+ recoverStreamFailure
21996
+ );
21997
+ completed = true;
21998
+ } catch (error) {
21999
+ streamError = error;
22000
+ throw error;
22001
+ } finally {
22002
+ try {
22003
+ this.closeScope(scopeState);
22004
+ if (completed) {
22005
+ this.assertScopeChildrenSettled(scopeState);
22006
+ await this.drainScopeWork(scopeState);
22007
+ this.throwIfScopePoisoned(scopeState);
22008
+ try {
22009
+ await this.executeScopeBoundaryStatement(
22010
+ client,
22011
+ scopeToken,
22012
+ scopeState,
22013
+ `RELEASE SAVEPOINT ${savepointName}`,
22014
+ {
22015
+ allowAncestorScopeToken: true
22016
+ }
22017
+ );
22018
+ } catch (releaseErr) {
22019
+ raise(
22020
+ await this.rollbackSavepointAfterReleaseFailure(
22021
+ client,
22022
+ savepointName,
22023
+ scopeToken,
22024
+ scopeState,
22025
+ releaseErr,
22026
+ "PostgreSQL stream cleanup failed: RELEASE SAVEPOINT failed after the stream completed"
22027
+ )
22028
+ );
22029
+ }
22030
+ } else {
22031
+ try {
22032
+ if (savepointRolledBackBeforeClose) {
22033
+ await this.releaseSavepoint(
22034
+ client,
22035
+ savepointName,
22036
+ scopeToken,
22037
+ scopeState
22038
+ );
22039
+ } else {
22040
+ await this.rollbackAndReleaseSavepoint(
22041
+ client,
22042
+ savepointName,
22043
+ scopeToken,
22044
+ scopeState
22045
+ );
22046
+ }
22047
+ } catch (cleanupErr) {
22048
+ if (streamError !== void 0) {
22049
+ raise(
22050
+ createCleanupFailureError(
22051
+ "PostgreSQL stream cleanup failed: savepoint cleanup failed after the stream failed",
22052
+ streamError,
22053
+ cleanupErr
22054
+ )
22055
+ );
22056
+ }
22057
+ raise(
22058
+ createCleanupOnlyError(
22059
+ "PostgreSQL stream cleanup failed: savepoint cleanup failed after the stream was closed early",
22060
+ cleanupErr
22061
+ )
22062
+ );
22063
+ }
22064
+ }
22065
+ } finally {
22066
+ releaseScope();
22067
+ }
22068
+ }
22069
+ }
22070
+ async *streamWithClientTransaction(client, query, chunkSize) {
22071
+ let begun = false;
22072
+ let committed = false;
22073
+ let streamFailed = false;
22074
+ let cleanupErrorToThrow;
22075
+ let releaseScope;
22076
+ let scopeToken;
22077
+ let scopeState;
22078
+ let streamRolledBackBeforeClose = false;
22079
+ try {
22080
+ scopeToken = createScopeToken();
22081
+ scopeState = createScopeState();
22082
+ releaseScope = this.enterTransactionScope(client, scopeToken, scopeState);
22083
+ await this.executeScopeBoundaryStatement(
22084
+ client,
22085
+ scopeToken,
22086
+ scopeState,
22087
+ "BEGIN"
22088
+ );
22089
+ begun = true;
22090
+ scopeState.streamFailureRecovery = async () => {
22091
+ await this.rollbackTransactionIfOpen(client, scopeToken, scopeState);
22092
+ streamRolledBackBeforeClose = true;
22093
+ };
22094
+ yield* this.streamWithClient(
22095
+ client,
22096
+ query,
22097
+ chunkSize,
22098
+ scopeToken,
22099
+ scopeState.streamFailureRecovery
22100
+ );
22101
+ this.closeScopeAndAssertChildren(scopeState);
22102
+ await this.drainScopeWork(scopeState);
22103
+ this.throwIfScopePoisoned(scopeState);
22104
+ const commitResult = await this.executeScopeBoundaryStatement(
22105
+ client,
22106
+ scopeToken,
22107
+ scopeState,
22108
+ "COMMIT"
22109
+ );
22110
+ committed = true;
22111
+ this.assertCommitSucceeded(commitResult);
22112
+ } catch (error) {
22113
+ let transactionError = error;
22114
+ try {
22115
+ this.closeScopeAndAssertChildren(scopeState);
22116
+ } catch (scopeError) {
22117
+ transactionError = scopeError;
22118
+ }
22119
+ streamFailed = true;
22120
+ if (begun && !committed && !streamRolledBackBeforeClose) {
22121
+ try {
22122
+ await this.rollbackTransactionIfOpen(client, scopeToken, scopeState);
22123
+ } catch (rollbackErr) {
22124
+ throw createCleanupFailureError(
22125
+ "PostgreSQL stream cleanup failed: ROLLBACK failed after the stream failed",
22126
+ transactionError,
22127
+ rollbackErr
22128
+ );
22129
+ }
22130
+ }
22131
+ throw transactionError;
20798
22132
  } finally {
20799
- await client.query(`CLOSE ${cursorName}`);
22133
+ if (begun && !committed && !streamFailed && !streamRolledBackBeforeClose) {
22134
+ this.closeScope(scopeState);
22135
+ try {
22136
+ await this.rollbackTransactionIfOpen(client, scopeToken, scopeState);
22137
+ } catch (rollbackErr) {
22138
+ cleanupErrorToThrow = createCleanupOnlyError(
22139
+ "PostgreSQL stream cleanup failed: ROLLBACK failed after the stream was closed early",
22140
+ rollbackErr
22141
+ );
22142
+ }
22143
+ }
22144
+ releaseScope?.();
22145
+ if (cleanupErrorToThrow !== void 0) {
22146
+ raise(cleanupErrorToThrow);
22147
+ }
20800
22148
  }
20801
22149
  }
20802
22150
  // =========================================================================
@@ -20816,58 +22164,1041 @@ var PgsqlAdapter = class _PgsqlAdapter {
20816
22164
  * ```
20817
22165
  */
20818
22166
  async introspect(options) {
20819
- if (!this.pool) {
22167
+ const executor = this.client ?? this.pool;
22168
+ if (!executor) {
20820
22169
  throw new Error(
20821
22170
  "Cannot introspect without a database connection (compile-only adapter)"
20822
22171
  );
20823
22172
  }
20824
- return introspect(this.pool, options);
22173
+ return introspectWithExecutor(
22174
+ {
22175
+ // The brand says this executor already carries the savepoint protection
22176
+ // the connection's ownership calls for. A bare PoolClient cannot get here.
22177
+ dbspProtectedCatalogExecutor: true,
22178
+ query: (sql, parameters) => this.executeConnectionStatement(executor, sql, parameters, {
22179
+ protectBorrowedClientTransaction: this.client !== void 0 && !this.adapterManagedTransaction
22180
+ }),
22181
+ sequentialCatalogReads: this.client !== void 0
22182
+ },
22183
+ options
22184
+ );
20825
22185
  }
20826
22186
  // =========================================================================
20827
22187
  // TransactionalAdapter Methods
20828
22188
  // =========================================================================
20829
22189
  /**
20830
22190
  * Execute a callback within a database transaction.
22191
+ *
22192
+ * ## What this guarantees
22193
+ *
22194
+ * The callback's work commits together or not at all; a statement issued inside
22195
+ * the transaction never executes after its boundary, even if you forget to
22196
+ * `await` it; a nested `transaction()` is a real savepoint; and the connection
22197
+ * never goes back to the pool with a transaction still open on it.
22198
+ *
22199
+ * ## What it cannot guarantee, and you should know before you reach for raw SQL
22200
+ *
22201
+ * **Raw SQL that ends the transaction ends it.** `COMMIT`, `ROLLBACK` and
22202
+ * `PREPARE TRANSACTION` issued through `executeRaw` — or through several commands
22203
+ * in one call — reach PostgreSQL and take effect *before* dbsp is told what they
22204
+ * were: the command tag arrives after the statement has run. dbsp detects it, kills
22205
+ * the scope so nothing else escapes, and throws — but **it cannot un-run what your
22206
+ * statement already did**, and `transaction()` rejecting does not mean nothing was
22207
+ * committed.
22208
+ *
22209
+ * The same holds for session state raw SQL creates: a sequence that advanced stays
22210
+ * advanced, an advisory lock stays held, a `PREPARE` or a `SET` you issued survives
22211
+ * on a pooled connection. dbsp cleans up only what dbsp created.
22212
+ *
22213
+ * This is the contract of an escape hatch, not an oversight — see #327. If you need
22214
+ * transaction control, own the transaction: take a client, `BEGIN` on it yourself,
22215
+ * and hand dbsp a `borrowedClient` **without** `managedTransactions`. dbsp will then
22216
+ * contain its own statements inside *your* transaction instead of the other way round.
20831
22217
  */
20832
- async transaction(fn4) {
22218
+ transaction(fn4) {
22219
+ const childObserver = this.createChildTransactionObserver();
20833
22220
  if (this.client) {
20834
- return fn4(this);
22221
+ if (!this.managedTransactions) {
22222
+ return Promise.reject(
22223
+ new Error(
22224
+ "transaction() was called on a PgsqlAdapter created with a borrowed PoolClient. This connection is yours, so the transaction is yours. Pass managedTransactions: true to let dbsp run transactions on it through a savepoint, and read the managedTransactions option documentation for the limits of that contract."
22225
+ )
22226
+ );
22227
+ }
22228
+ return this.observeChildTransaction(
22229
+ this.transactionWithManagedClient(
22230
+ this.client,
22231
+ fn4,
22232
+ childObserver,
22233
+ "release"
22234
+ ),
22235
+ childObserver
22236
+ );
20835
22237
  }
20836
- const pool = this.requireConnection();
20837
- const client = await pool.connect();
22238
+ let pool;
20838
22239
  try {
20839
- await client.query("BEGIN");
20840
- const txOptions = this.cloneOptions({});
20841
- const txAdapter = new _PgsqlAdapter(client, txOptions);
20842
- const result = await fn4(txAdapter);
20843
- await client.query("COMMIT");
20844
- return result;
22240
+ pool = this.requireConnection();
20845
22241
  } catch (error) {
20846
- await client.query("ROLLBACK");
20847
- throw error;
20848
- } finally {
20849
- client.release();
20850
- }
22242
+ return Promise.reject(error);
22243
+ }
22244
+ return this.observeChildTransaction(
22245
+ (async () => {
22246
+ const client = await pool.connect();
22247
+ let releaseError;
22248
+ try {
22249
+ return await this.transactionWithClientTransaction(
22250
+ client,
22251
+ fn4,
22252
+ childObserver,
22253
+ "commit"
22254
+ );
22255
+ } catch (error) {
22256
+ releaseError = cleanupReleaseReason(error);
22257
+ throw error;
22258
+ } finally {
22259
+ this.releaseClient(client, releaseError);
22260
+ }
22261
+ })(),
22262
+ childObserver
22263
+ );
20851
22264
  }
20852
22265
  /**
20853
- * Create a schema-scoped adapter for multi-tenant queries.
22266
+ * Execute scratch PostgreSQL work in a scope that always rolls back on success.
22267
+ *
22268
+ * This is intentionally PostgreSQL-adapter-specific. It is used for catalog
22269
+ * shaped scratch DDL such as CHECK expression canonicalisation, where the
22270
+ * caller needs PostgreSQL's rendering but must not keep the scratch objects.
22271
+ * Rollback here is cleanup of dbsp-created work, not a sandbox for arbitrary
22272
+ * session effects.
20854
22273
  */
20855
- withSchema(schemaName) {
20856
- validateIdentifier(schemaName, "schema");
20857
- const options = this.cloneOptions({ schemaName });
20858
- return new _PgsqlAdapter(this.client ?? this.pool ?? void 0, options);
20859
- }
20860
- // =========================================================================
20861
- // RawSqlAdapter Methods
22274
+ withScratchScope(fn4) {
22275
+ const childObserver = this.createChildTransactionObserver();
22276
+ if (this.client) {
22277
+ return this.observeChildTransaction(
22278
+ this.transactionWithManagedClient(
22279
+ this.client,
22280
+ fn4,
22281
+ childObserver,
22282
+ "rollback"
22283
+ ),
22284
+ childObserver
22285
+ );
22286
+ }
22287
+ let pool;
22288
+ try {
22289
+ pool = this.requireConnection();
22290
+ } catch (error) {
22291
+ return Promise.reject(error);
22292
+ }
22293
+ return this.observeChildTransaction(
22294
+ (async () => {
22295
+ const client = await pool.connect();
22296
+ let releaseError;
22297
+ try {
22298
+ return await this.transactionWithClientTransaction(
22299
+ client,
22300
+ fn4,
22301
+ childObserver,
22302
+ "rollback"
22303
+ );
22304
+ } catch (error) {
22305
+ releaseError = cleanupReleaseReason(error);
22306
+ throw error;
22307
+ } finally {
22308
+ this.releaseClient(client, releaseError);
22309
+ }
22310
+ })(),
22311
+ childObserver
22312
+ );
22313
+ }
22314
+ createManagedClientAdapter(client, scopeToken, scopeState) {
22315
+ return createPgsqlAdapterFromConstructionOptions(
22316
+ client,
22317
+ this.cloneOptions({
22318
+ borrowedClient: true,
22319
+ managedTransactions: true,
22320
+ adapterManagedTransaction: true,
22321
+ dbspScopeToken: scopeToken,
22322
+ dbspScopeState: scopeState
22323
+ })
22324
+ );
22325
+ }
22326
+ createChildTransactionObserver() {
22327
+ if (!this.adapterManagedScopeIsLive()) return void 0;
22328
+ const parentChildren = this.scopeState?.children;
22329
+ if (parentChildren === void 0) return void 0;
22330
+ let resolveSettled;
22331
+ const settledPromise = new Promise((resolve) => {
22332
+ resolveSettled = resolve;
22333
+ });
22334
+ const child = {
22335
+ observed: false,
22336
+ failure: void 0,
22337
+ settled: false,
22338
+ settledPromise,
22339
+ resolveSettled
22340
+ };
22341
+ parentChildren.open++;
22342
+ parentChildren.transactions.add(child);
22343
+ let released = false;
22344
+ const release = (failure) => {
22345
+ if (released) return;
22346
+ released = true;
22347
+ if (failure !== void 0 && child.failure === void 0) {
22348
+ child.failure = failure;
22349
+ if (!child.observed && parentChildren.failure === void 0) {
22350
+ parentChildren.failure = failure;
22351
+ }
22352
+ }
22353
+ parentChildren.open--;
22354
+ child.settled = true;
22355
+ child.resolveSettled();
22356
+ };
22357
+ return { observed: false, child, parentChildren, release };
22358
+ }
22359
+ observeChildTransaction(promise, observer) {
22360
+ if (observer === void 0) return promise;
22361
+ const observed = new ObservedChildTransactionPromise(promise, () => {
22362
+ this.markChildTransactionObserved(observer);
22363
+ });
22364
+ void Promise.prototype.then.call(observed, void 0, () => void 0);
22365
+ return observed;
22366
+ }
22367
+ markChildTransactionObserved(observer) {
22368
+ if (observer.observed) return;
22369
+ observer.observed = true;
22370
+ const child = observer.child;
22371
+ child.observed = true;
22372
+ const parentChildren = observer.parentChildren;
22373
+ if (child.failure !== void 0 && parentChildren.failure === child.failure) {
22374
+ this.refreshScopeChildrenFailure(parentChildren);
22375
+ }
22376
+ }
22377
+ refreshScopeChildrenFailure(children) {
22378
+ children.failure = void 0;
22379
+ for (const child of children.transactions) {
22380
+ if (!child.observed && child.failure !== void 0) {
22381
+ children.failure = child.failure;
22382
+ return;
22383
+ }
22384
+ }
22385
+ }
22386
+ async transactionWithManagedClient(client, fn4, childObserver, successAction = "release") {
22387
+ return this.transactionWithManagedClientSavepointScope(
22388
+ client,
22389
+ fn4,
22390
+ childObserver,
22391
+ successAction
22392
+ );
22393
+ }
22394
+ async transactionWithManagedClientSavepointScope(client, fn4, childObserver, successAction = "release") {
22395
+ const savepointName = nextSavepointName();
22396
+ const scopeToken = createScopeToken();
22397
+ const scopeState = createScopeState(this.scopeState?.statementLock);
22398
+ const txAdapter = this.createManagedClientAdapter(
22399
+ client,
22400
+ scopeToken,
22401
+ scopeState
22402
+ );
22403
+ let releaseScope;
22404
+ try {
22405
+ releaseScope = this.enterSavepointScope(
22406
+ client,
22407
+ scopeToken,
22408
+ scopeState,
22409
+ "transaction",
22410
+ childObserver
22411
+ );
22412
+ } catch (error) {
22413
+ childObserver?.release({ error });
22414
+ throw error;
22415
+ }
22416
+ let scopeFailure;
22417
+ let savepointRolledBackBeforeClose = false;
22418
+ try {
22419
+ await this.executeConnectionStatement(
22420
+ client,
22421
+ `SAVEPOINT ${savepointName}`,
22422
+ void 0,
22423
+ {
22424
+ allowedScopeToken: scopeToken,
22425
+ inspectTransactionControl: false
22426
+ }
22427
+ );
22428
+ } catch (error) {
22429
+ if (isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
22430
+ releaseScope();
22431
+ return this.transactionWithClientTransaction(
22432
+ client,
22433
+ fn4,
22434
+ childObserver,
22435
+ successAction === "rollback" ? "rollback" : "commit"
22436
+ );
22437
+ }
22438
+ releaseScope({ error });
22439
+ throw error;
22440
+ }
22441
+ scopeState.streamFailureRecovery = async () => {
22442
+ const rolledBack = await this.rollbackSavepoint(
22443
+ client,
22444
+ savepointName,
22445
+ scopeToken,
22446
+ scopeState
22447
+ );
22448
+ savepointRolledBackBeforeClose ||= rolledBack;
22449
+ };
22450
+ try {
22451
+ let result;
22452
+ try {
22453
+ result = await fn4(txAdapter);
22454
+ this.closeScopeAndAssertChildren(scopeState);
22455
+ await this.drainScopeWork(scopeState);
22456
+ this.throwIfScopePoisoned(scopeState);
22457
+ } catch (error) {
22458
+ let transactionError = error;
22459
+ try {
22460
+ this.closeScopeAndAssertChildren(scopeState);
22461
+ } catch (scopeError) {
22462
+ transactionError = scopeError;
22463
+ }
22464
+ let cleanupErr;
22465
+ try {
22466
+ let rolledBack = savepointRolledBackBeforeClose;
22467
+ if (!rolledBack) {
22468
+ rolledBack = await this.rollbackSavepoint(
22469
+ client,
22470
+ savepointName,
22471
+ scopeToken,
22472
+ scopeState
22473
+ );
22474
+ }
22475
+ await this.drainScopeChildren(scopeState);
22476
+ if (rolledBack) {
22477
+ await this.releaseSavepoint(
22478
+ client,
22479
+ savepointName,
22480
+ scopeToken,
22481
+ scopeState
22482
+ );
22483
+ }
22484
+ } catch (error2) {
22485
+ cleanupErr = error2;
22486
+ }
22487
+ if (cleanupErr !== void 0) {
22488
+ await this.drainScopeChildren(scopeState);
22489
+ if (isRawSqlTransactionControlError(transactionError) && isSavepointUnavailableError(cleanupErr)) {
22490
+ scopeFailure = { error: transactionError };
22491
+ throw transactionError;
22492
+ }
22493
+ const cleanupFailure = createCleanupFailureError(
22494
+ "PostgreSQL transaction cleanup failed: savepoint cleanup failed after the transaction body failed; the caller transaction may now be in an unknown state",
22495
+ transactionError,
22496
+ cleanupErr
22497
+ );
22498
+ scopeFailure = { error: cleanupFailure };
22499
+ throw cleanupFailure;
22500
+ }
22501
+ await this.drainScopeChildren(scopeState);
22502
+ transactionError = this.scopePoisonOutranksError(
22503
+ scopeState,
22504
+ transactionError
22505
+ );
22506
+ scopeFailure = { error: transactionError };
22507
+ throw transactionError;
22508
+ }
22509
+ try {
22510
+ if (successAction === "rollback") {
22511
+ try {
22512
+ await this.rollbackAndReleaseSavepoint(
22513
+ client,
22514
+ savepointName,
22515
+ scopeToken,
22516
+ scopeState,
22517
+ { ignoreNoActiveTransaction: false }
22518
+ );
22519
+ } catch (rollbackErr) {
22520
+ const cleanupFailure = createCleanupOnlyError(
22521
+ "PostgreSQL scratch scope cleanup failed: ROLLBACK TO SAVEPOINT/RELEASE SAVEPOINT failed after the scratch body returned",
22522
+ rollbackErr
22523
+ );
22524
+ scopeFailure = { error: cleanupFailure };
22525
+ throw cleanupFailure;
22526
+ }
22527
+ return result;
22528
+ }
22529
+ await this.executeScopeBoundaryStatement(
22530
+ client,
22531
+ scopeToken,
22532
+ scopeState,
22533
+ `RELEASE SAVEPOINT ${savepointName}`,
22534
+ {
22535
+ allowAncestorScopeToken: true
22536
+ }
22537
+ );
22538
+ } catch (releaseErr) {
22539
+ const cleanupFailure = await this.rollbackSavepointAfterReleaseFailure(
22540
+ client,
22541
+ savepointName,
22542
+ scopeToken,
22543
+ scopeState,
22544
+ releaseErr,
22545
+ "PostgreSQL transaction cleanup failed: RELEASE SAVEPOINT failed after the transaction body returned"
22546
+ );
22547
+ scopeFailure = { error: cleanupFailure };
22548
+ throw cleanupFailure;
22549
+ }
22550
+ return result;
22551
+ } finally {
22552
+ releaseScope(scopeFailure);
22553
+ }
22554
+ }
22555
+ async transactionWithClientTransaction(client, fn4, childObserver, successAction = "commit") {
22556
+ let begun = false;
22557
+ let committed = false;
22558
+ let releaseScope;
22559
+ let scopeToken;
22560
+ let scopeState;
22561
+ let scopeFailure;
22562
+ let transactionRolledBackBeforeClose = false;
22563
+ try {
22564
+ scopeToken = createScopeToken();
22565
+ scopeState = createScopeState();
22566
+ releaseScope = this.enterTransactionScope(
22567
+ client,
22568
+ scopeToken,
22569
+ scopeState,
22570
+ childObserver
22571
+ );
22572
+ await this.executeScopeBoundaryStatement(
22573
+ client,
22574
+ scopeToken,
22575
+ scopeState,
22576
+ "BEGIN"
22577
+ );
22578
+ begun = true;
22579
+ scopeState.streamFailureRecovery = async () => {
22580
+ await this.rollbackTransactionIfOpen(client, scopeToken, scopeState);
22581
+ transactionRolledBackBeforeClose = true;
22582
+ };
22583
+ const txAdapter = this.createManagedClientAdapter(
22584
+ client,
22585
+ scopeToken,
22586
+ scopeState
22587
+ );
22588
+ const result = await fn4(txAdapter);
22589
+ this.closeScopeAndAssertChildren(scopeState);
22590
+ await this.drainScopeWork(scopeState);
22591
+ this.throwIfScopePoisoned(scopeState);
22592
+ if (successAction === "rollback") {
22593
+ transactionRolledBackBeforeClose = true;
22594
+ try {
22595
+ await this.rollbackTransactionIfOpen(
22596
+ client,
22597
+ scopeToken,
22598
+ scopeState,
22599
+ false
22600
+ );
22601
+ } catch (rollbackErr) {
22602
+ const cleanupFailure = createCleanupOnlyError(
22603
+ "PostgreSQL scratch scope cleanup failed: ROLLBACK failed after the scratch body returned",
22604
+ rollbackErr
22605
+ );
22606
+ scopeFailure = { error: cleanupFailure };
22607
+ throw cleanupFailure;
22608
+ }
22609
+ return result;
22610
+ }
22611
+ const commitResult = await this.executeScopeBoundaryStatement(
22612
+ client,
22613
+ scopeToken,
22614
+ scopeState,
22615
+ "COMMIT"
22616
+ );
22617
+ committed = true;
22618
+ this.assertCommitSucceeded(commitResult);
22619
+ return result;
22620
+ } catch (error) {
22621
+ let transactionError = error;
22622
+ try {
22623
+ this.closeScopeAndAssertChildren(scopeState);
22624
+ } catch (scopeError) {
22625
+ transactionError = scopeError;
22626
+ }
22627
+ if (begun && !committed && !transactionRolledBackBeforeClose) {
22628
+ try {
22629
+ await this.rollbackTransactionIfOpen(client, scopeToken, scopeState);
22630
+ } catch (rollbackErr) {
22631
+ const cleanupFailure = createCleanupFailureError(
22632
+ "PostgreSQL transaction cleanup failed: ROLLBACK failed after the transaction body failed",
22633
+ transactionError,
22634
+ rollbackErr
22635
+ );
22636
+ scopeFailure = { error: cleanupFailure };
22637
+ await this.drainScopeChildren(scopeState);
22638
+ throw cleanupFailure;
22639
+ }
22640
+ }
22641
+ await this.drainScopeChildren(scopeState);
22642
+ transactionError = this.scopePoisonOutranksError(
22643
+ scopeState,
22644
+ transactionError
22645
+ );
22646
+ scopeFailure = { error: transactionError };
22647
+ throw transactionError;
22648
+ } finally {
22649
+ if (releaseScope === void 0) {
22650
+ if (scopeFailure !== void 0) {
22651
+ childObserver?.release(scopeFailure);
22652
+ }
22653
+ } else {
22654
+ releaseScope(scopeFailure);
22655
+ }
22656
+ }
22657
+ }
22658
+ releaseClient(client, error) {
22659
+ if (error === void 0) {
22660
+ client.release();
22661
+ return;
22662
+ }
22663
+ client.release(error);
22664
+ }
22665
+ async rollbackAndReleaseSavepoint(client, savepointName, allowedScopeToken, scopeState, options) {
22666
+ const rolledBack = await this.rollbackSavepoint(
22667
+ client,
22668
+ savepointName,
22669
+ allowedScopeToken,
22670
+ scopeState,
22671
+ options
22672
+ );
22673
+ if (!rolledBack) return;
22674
+ await this.releaseSavepoint(
22675
+ client,
22676
+ savepointName,
22677
+ allowedScopeToken,
22678
+ scopeState
22679
+ );
22680
+ }
22681
+ async rollbackSavepoint(client, savepointName, allowedScopeToken, scopeState, options) {
22682
+ try {
22683
+ await this.executeScopeBoundaryStatement(
22684
+ client,
22685
+ allowedScopeToken,
22686
+ scopeState,
22687
+ `ROLLBACK TO SAVEPOINT ${savepointName}`,
22688
+ {
22689
+ allowAncestorScopeToken: true,
22690
+ allowPoisonedScope: true
22691
+ }
22692
+ );
22693
+ } catch (error) {
22694
+ if ((options?.ignoreNoActiveTransaction ?? true) && isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
22695
+ return false;
22696
+ }
22697
+ throw error;
22698
+ }
22699
+ return true;
22700
+ }
22701
+ async releaseSavepoint(client, savepointName, allowedScopeToken, scopeState) {
22702
+ await this.executeScopeBoundaryStatement(
22703
+ client,
22704
+ allowedScopeToken,
22705
+ scopeState,
22706
+ `RELEASE SAVEPOINT ${savepointName}`,
22707
+ {
22708
+ allowAncestorScopeToken: true,
22709
+ allowPoisonedScope: true
22710
+ }
22711
+ );
22712
+ }
22713
+ async rollbackTransactionIfOpen(client, allowedScopeToken, scopeState, ignoreNoActiveTransaction = true) {
22714
+ if (allowedScopeToken === void 0 || scopeState === void 0) {
22715
+ throw new Error(
22716
+ "Cannot roll back a dbsp-managed PostgreSQL transaction without a registered dbsp scope."
22717
+ );
22718
+ }
22719
+ try {
22720
+ await this.executeScopeBoundaryStatement(
22721
+ client,
22722
+ allowedScopeToken,
22723
+ scopeState,
22724
+ "ROLLBACK",
22725
+ {
22726
+ allowAncestorScopeToken: true,
22727
+ allowPoisonedScope: true
22728
+ }
22729
+ );
22730
+ } catch (error) {
22731
+ if (ignoreNoActiveTransaction && isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
22732
+ return;
22733
+ }
22734
+ throw error;
22735
+ }
22736
+ }
22737
+ classifyTransactionStateError(error) {
22738
+ if (isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
22739
+ return "no-active-transaction";
22740
+ }
22741
+ if (isPgErrorWithCode(error, IN_FAILED_SQL_TRANSACTION)) {
22742
+ return "transaction-aborted";
22743
+ }
22744
+ if (isPgErrorWithCode(error, INVALID_SAVEPOINT_SPECIFICATION)) {
22745
+ return "savepoint-gone";
22746
+ }
22747
+ return void 0;
22748
+ }
22749
+ async probeTransactionState(client, allowedScopeToken) {
22750
+ const probeSavepointName = nextSavepointName();
22751
+ try {
22752
+ await this.executeConnectionStatement(
22753
+ client,
22754
+ `SAVEPOINT ${probeSavepointName}`,
22755
+ void 0,
22756
+ {
22757
+ ...allowedScopeToken !== void 0 && { allowedScopeToken },
22758
+ allowClosingScope: true,
22759
+ allowPoisonedScope: true,
22760
+ inspectTransactionControl: false,
22761
+ protectBorrowedClientTransaction: false
22762
+ }
22763
+ );
22764
+ } catch (error) {
22765
+ const state = this.classifyTransactionStateError(error);
22766
+ if (state === "no-active-transaction" || state === "savepoint-gone") {
22767
+ return "no-active-transaction";
22768
+ }
22769
+ if (state === "transaction-aborted") return "transaction-aborted";
22770
+ return "unknown";
22771
+ }
22772
+ try {
22773
+ await this.executeConnectionStatement(
22774
+ client,
22775
+ `RELEASE SAVEPOINT ${probeSavepointName}`,
22776
+ void 0,
22777
+ {
22778
+ ...allowedScopeToken !== void 0 && { allowedScopeToken },
22779
+ allowClosingScope: true,
22780
+ allowPoisonedScope: true,
22781
+ inspectTransactionControl: false,
22782
+ protectBorrowedClientTransaction: false
22783
+ }
22784
+ );
22785
+ } catch {
22786
+ return "unknown";
22787
+ }
22788
+ return "live";
22789
+ }
22790
+ async classifySavepointReleaseFailure(client, allowedScopeToken, releaseErr, cleanupAction) {
22791
+ const directState = this.classifyTransactionStateError(releaseErr);
22792
+ if (directState === "no-active-transaction" || directState === "savepoint-gone") {
22793
+ return this.poisonClientScopeStack(
22794
+ client,
22795
+ createCleanupOnlyError(cleanupAction, releaseErr),
22796
+ "cleanup"
22797
+ );
22798
+ }
22799
+ if (directState === "transaction-aborted") {
22800
+ return this.poisonClientScope(
22801
+ client,
22802
+ allowedScopeToken,
22803
+ new PgsqlTransactionAbortedError(releaseErr),
22804
+ "cleanup"
22805
+ );
22806
+ }
22807
+ const probedState = await this.probeTransactionState(
22808
+ client,
22809
+ allowedScopeToken
22810
+ );
22811
+ if (probedState === "no-active-transaction") {
22812
+ return this.poisonClientScopeStack(
22813
+ client,
22814
+ createCleanupOnlyError(cleanupAction, releaseErr),
22815
+ "cleanup"
22816
+ );
22817
+ }
22818
+ if (probedState === "transaction-aborted") {
22819
+ return this.poisonClientScope(
22820
+ client,
22821
+ allowedScopeToken,
22822
+ new PgsqlTransactionAbortedError(releaseErr),
22823
+ "cleanup"
22824
+ );
22825
+ }
22826
+ return createCleanupOnlyError(cleanupAction, releaseErr);
22827
+ }
22828
+ async rollbackSavepointAfterReleaseFailure(client, savepointName, allowedScopeToken, scopeState, releaseErr, cleanupAction) {
22829
+ try {
22830
+ await this.executeScopeBoundaryStatement(
22831
+ client,
22832
+ allowedScopeToken,
22833
+ scopeState,
22834
+ `ROLLBACK TO SAVEPOINT ${savepointName}`,
22835
+ {
22836
+ allowAncestorScopeToken: true,
22837
+ allowPoisonedScope: true
22838
+ }
22839
+ );
22840
+ } catch (rollbackErr) {
22841
+ const classifiedRollbackFailure = await this.classifySavepointReleaseFailure(
22842
+ client,
22843
+ allowedScopeToken,
22844
+ rollbackErr,
22845
+ cleanupAction
22846
+ );
22847
+ if (isRawSqlTransactionControlError(classifiedRollbackFailure) || classifiedRollbackFailure instanceof PgsqlTransactionAbortedError) {
22848
+ return classifiedRollbackFailure;
22849
+ }
22850
+ return createCleanupFailureError(
22851
+ `${cleanupAction}; ROLLBACK TO SAVEPOINT also failed and the caller transaction may now be in an unknown state`,
22852
+ releaseErr,
22853
+ rollbackErr
22854
+ );
22855
+ }
22856
+ try {
22857
+ await this.releaseSavepoint(
22858
+ client,
22859
+ savepointName,
22860
+ allowedScopeToken,
22861
+ scopeState
22862
+ );
22863
+ } catch (releaseAfterRollbackErr) {
22864
+ return createCleanupFailureError(
22865
+ `${cleanupAction}; RELEASE SAVEPOINT also failed after ROLLBACK TO SAVEPOINT and the caller transaction may now be in an unknown state`,
22866
+ releaseErr,
22867
+ releaseAfterRollbackErr
22868
+ );
22869
+ }
22870
+ return createCleanupOnlyError(cleanupAction, releaseErr);
22871
+ }
22872
+ enterTransactionScope(client, scopeToken, scopeState, childObserver) {
22873
+ const current = this.currentClientScope(client);
22874
+ if (current === void 0 && this.scopeToken !== void 0) {
22875
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22876
+ }
22877
+ if (current !== void 0) {
22878
+ const ownScope = this.scopeToken === void 0 ? void 0 : this.findClientScope(client, this.scopeToken);
22879
+ if (ownScope?.state.closing) {
22880
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22881
+ }
22882
+ if (current.state.closing) {
22883
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22884
+ }
22885
+ this.assertScopeNotPoisoned(current);
22886
+ if (current.token !== this.scopeToken) {
22887
+ throw new Error(SAVEPOINT_SCOPE_OWNER_MESSAGE);
22888
+ }
22889
+ throw new Error(SAVEPOINT_SCOPE_BUSY_MESSAGE);
22890
+ }
22891
+ return this.pushClientScope(
22892
+ client,
22893
+ {
22894
+ kind: "transaction",
22895
+ token: scopeToken,
22896
+ state: scopeState
22897
+ },
22898
+ childObserver
22899
+ );
22900
+ }
22901
+ enterSavepointScope(client, scopeToken, scopeState, purpose, childObserver) {
22902
+ const current = this.currentClientScope(client);
22903
+ if (current === void 0 && this.scopeToken !== void 0) {
22904
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22905
+ }
22906
+ if (current !== void 0) {
22907
+ const ownScope = this.scopeToken === void 0 ? void 0 : this.findClientScope(client, this.scopeToken);
22908
+ if (ownScope?.state.closing) {
22909
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22910
+ }
22911
+ if (current.state.closing) {
22912
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22913
+ }
22914
+ this.assertScopeNotPoisoned(current);
22915
+ if (current.state.statementLock.tail !== void 0) {
22916
+ throw new Error(SAVEPOINT_SCOPE_BUSY_MESSAGE);
22917
+ }
22918
+ if (current.kind === "statement-savepoint") {
22919
+ throw new Error(SAVEPOINT_SCOPE_BUSY_MESSAGE);
22920
+ }
22921
+ if (current.token !== this.scopeToken) {
22922
+ throw new Error(SAVEPOINT_SCOPE_OWNER_MESSAGE);
22923
+ }
22924
+ if (purpose !== "transaction") {
22925
+ throw new Error(SAVEPOINT_SCOPE_BUSY_MESSAGE);
22926
+ }
22927
+ }
22928
+ return this.pushClientScope(
22929
+ client,
22930
+ {
22931
+ kind: purpose === "transaction" ? "transaction-savepoint" : "statement-savepoint",
22932
+ token: scopeToken,
22933
+ state: scopeState
22934
+ },
22935
+ childObserver
22936
+ );
22937
+ }
22938
+ pushClientScope(client, scope, childObserver) {
22939
+ const stack = activeClientScopes.get(client);
22940
+ if (stack === void 0) {
22941
+ activeClientScopes.set(client, [scope]);
22942
+ } else {
22943
+ stack.push(scope);
22944
+ }
22945
+ let released = false;
22946
+ return (failure) => {
22947
+ if (released) return;
22948
+ released = true;
22949
+ try {
22950
+ const currentStack = activeClientScopes.get(client);
22951
+ if (currentStack === void 0) return;
22952
+ const index = currentStack.indexOf(scope);
22953
+ if (index === -1) return;
22954
+ currentStack.splice(index, 1);
22955
+ if (currentStack.length === 0) {
22956
+ activeClientScopes.delete(client);
22957
+ }
22958
+ } finally {
22959
+ childObserver?.release(failure);
22960
+ }
22961
+ };
22962
+ }
22963
+ currentClientScope(client) {
22964
+ return activeClientScopes.get(client)?.at(-1);
22965
+ }
22966
+ assertCanUseClient(client, allowedScopeToken = this.scopeToken, allowAncestorScopeToken = false, allowClosingScope = false, allowPoisonedScope = false) {
22967
+ const stack = activeClientScopes.get(client);
22968
+ const current = stack?.at(-1);
22969
+ if (allowedScopeToken === void 0) {
22970
+ if (current === void 0) return;
22971
+ throw new Error(SAVEPOINT_SCOPE_OWNER_MESSAGE);
22972
+ }
22973
+ if (current?.token === allowedScopeToken) {
22974
+ this.assertUsableScopeAncestors(
22975
+ stack,
22976
+ stack.length - 1,
22977
+ allowClosingScope,
22978
+ allowPoisonedScope
22979
+ );
22980
+ if (current.state.closing && !allowClosingScope) {
22981
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22982
+ }
22983
+ if (!allowPoisonedScope) {
22984
+ this.assertScopeNotPoisoned(current);
22985
+ }
22986
+ return;
22987
+ }
22988
+ const allowedScope = this.findClientScope(client, allowedScopeToken);
22989
+ if (allowedScope?.state.closing && !allowClosingScope) {
22990
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
22991
+ }
22992
+ if (allowAncestorScopeToken && allowedScope !== void 0) {
22993
+ this.assertUsableScopeAncestors(
22994
+ stack,
22995
+ stack.indexOf(allowedScope),
22996
+ allowClosingScope,
22997
+ allowPoisonedScope
22998
+ );
22999
+ if (!allowPoisonedScope) {
23000
+ this.assertScopeNotPoisoned(allowedScope);
23001
+ }
23002
+ return;
23003
+ }
23004
+ if (allowedScope === void 0) {
23005
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
23006
+ }
23007
+ throw new Error(SAVEPOINT_SCOPE_OWNER_MESSAGE);
23008
+ }
23009
+ assertUsableScopeAncestors(stack, scopeIndex, allowClosingScope, allowPoisonedScope) {
23010
+ for (let index = 0; index < scopeIndex; index++) {
23011
+ const ancestor = stack[index];
23012
+ if (ancestor === void 0) continue;
23013
+ if (ancestor.state.closing && !allowClosingScope) {
23014
+ throw new Error(TRANSACTION_SCOPE_ENDED_MESSAGE);
23015
+ }
23016
+ if (!allowPoisonedScope) {
23017
+ this.assertScopeNotPoisoned(ancestor);
23018
+ }
23019
+ }
23020
+ }
23021
+ findClientScope(client, scopeToken) {
23022
+ const stack = activeClientScopes.get(client);
23023
+ if (stack === void 0) return void 0;
23024
+ if (scopeToken === void 0) return stack.at(-1);
23025
+ for (let index = stack.length - 1; index >= 0; index--) {
23026
+ const scope = stack[index];
23027
+ if (scope?.token === scopeToken) return scope;
23028
+ }
23029
+ return void 0;
23030
+ }
23031
+ assertScopeNotPoisoned(scope) {
23032
+ this.throwIfScopePoisoned(scope.state);
23033
+ }
23034
+ throwIfScopePoisoned(scopeState) {
23035
+ const poison = scopeState.poisoned;
23036
+ if (poison !== void 0) {
23037
+ throw poison.error;
23038
+ }
23039
+ }
23040
+ scopePoisonOutranksError(scopeState, error) {
23041
+ const poison = scopeState?.poisoned;
23042
+ if (poison === void 0 || poison.error === error) return error;
23043
+ if (poison.origin === "cleanup") {
23044
+ if (error instanceof Error && !Object.hasOwn(error, "cause")) {
23045
+ Object.defineProperty(error, "cause", {
23046
+ value: poison.error,
23047
+ configurable: true
23048
+ });
23049
+ }
23050
+ return error;
23051
+ }
23052
+ if (!isRawSqlTransactionControlError(poison.error)) {
23053
+ return error;
23054
+ }
23055
+ Object.defineProperty(poison.error, "cause", {
23056
+ value: error,
23057
+ configurable: true
23058
+ });
23059
+ return poison.error;
23060
+ }
23061
+ poisonScopeState(scopeState, error, origin = "caller") {
23062
+ const poison = scopeState.poisoned;
23063
+ if (poison === void 0 || poison.origin === "cleanup" && origin === "caller") {
23064
+ scopeState.poisoned = { error, origin };
23065
+ return error;
23066
+ }
23067
+ return poison.error;
23068
+ }
23069
+ poisonClientScope(client, scopeToken, error, origin) {
23070
+ const scope = this.findClientScope(client, scopeToken);
23071
+ if (scope === void 0) return error;
23072
+ return this.poisonScopeState(scope.state, error, origin);
23073
+ }
23074
+ poisonClientScopeStack(client, error, origin) {
23075
+ const stack = activeClientScopes.get(client);
23076
+ if (stack === void 0 || stack.length === 0) return error;
23077
+ let poisonedError;
23078
+ for (const scope of stack) {
23079
+ const scopeError = this.poisonScopeState(scope.state, error, origin);
23080
+ poisonedError ??= scopeError;
23081
+ }
23082
+ return poisonedError ?? error;
23083
+ }
23084
+ async runWithScopeStatementLock(scopeState, fn4) {
23085
+ const lock = scopeState.statementLock;
23086
+ const previous = lock.tail ?? Promise.resolve();
23087
+ let release;
23088
+ const next = new Promise((resolve) => {
23089
+ release = resolve;
23090
+ });
23091
+ const tail = previous.catch(() => void 0).then(() => next);
23092
+ lock.tail = tail;
23093
+ await previous.catch(() => void 0);
23094
+ try {
23095
+ return await fn4();
23096
+ } finally {
23097
+ release();
23098
+ if (lock.tail === tail) {
23099
+ lock.tail = void 0;
23100
+ }
23101
+ }
23102
+ }
23103
+ closeScope(scopeState) {
23104
+ if (scopeState !== void 0) {
23105
+ scopeState.closing = true;
23106
+ }
23107
+ }
23108
+ closeScopeAndAssertChildren(scopeState) {
23109
+ this.closeScope(scopeState);
23110
+ if (scopeState !== void 0) {
23111
+ this.assertScopeChildrenSettled(scopeState);
23112
+ }
23113
+ }
23114
+ async drainScopeStatements(scopeState) {
23115
+ await scopeState.statementLock.tail?.catch(() => void 0);
23116
+ }
23117
+ async drainScopeChildren(scopeState) {
23118
+ if (scopeState === void 0) return;
23119
+ while (scopeState.children.open > 0) {
23120
+ const pending = [...scopeState.children.transactions].filter(
23121
+ (child) => !child.settled
23122
+ );
23123
+ if (pending.length === 0) return;
23124
+ await Promise.all(pending.map((child) => child.settledPromise));
23125
+ }
23126
+ }
23127
+ assertScopeChildrenSettled(scopeState) {
23128
+ const failure = scopeState.children.failure;
23129
+ if (failure !== void 0) {
23130
+ throw failure.error;
23131
+ }
23132
+ if (scopeState.children.open > 0) {
23133
+ throw new Error(NESTED_TRANSACTION_NOT_AWAITED_MESSAGE);
23134
+ }
23135
+ for (const child of scopeState.children.transactions) {
23136
+ if (!child.observed) {
23137
+ throw new Error(NESTED_TRANSACTION_NOT_AWAITED_MESSAGE);
23138
+ }
23139
+ }
23140
+ }
23141
+ async drainScopeWork(scopeState) {
23142
+ await this.drainScopeStatements(scopeState);
23143
+ }
23144
+ async executeScopeBoundaryStatement(client, scopeToken, scopeState, sql, options = {}) {
23145
+ const boundaryOptions = {
23146
+ ...options,
23147
+ allowedScopeToken: scopeToken,
23148
+ allowClosingScope: true,
23149
+ inspectTransactionControl: false,
23150
+ protectBorrowedClientTransaction: false
23151
+ };
23152
+ return this.runWithScopeStatementLock(
23153
+ scopeState,
23154
+ () => this.executeConnectionStatementUnlocked(
23155
+ client,
23156
+ sql,
23157
+ void 0,
23158
+ boundaryOptions,
23159
+ scopeToken
23160
+ )
23161
+ );
23162
+ }
23163
+ assertCommitSucceeded(result) {
23164
+ if (result.command === "ROLLBACK") {
23165
+ throw new PgsqlTransactionAbortedCommitError(result);
23166
+ }
23167
+ }
23168
+ /**
23169
+ * Create a schema-scoped adapter for multi-tenant queries.
23170
+ */
23171
+ withSchema(schemaName) {
23172
+ validateIdentifier(schemaName, "schema");
23173
+ const options = this.cloneOptions({ schemaName });
23174
+ return createPgsqlAdapterFromConstructionOptions(
23175
+ this.client ?? this.pool ?? void 0,
23176
+ options
23177
+ );
23178
+ }
23179
+ // =========================================================================
23180
+ // RawSqlAdapter Methods
20862
23181
  // =========================================================================
20863
23182
  /**
20864
23183
  * Execute raw SQL directly.
20865
23184
  *
20866
23185
  * ⚠️ WARNING: Use parameter placeholders ($1, $2, etc.) for all values.
23186
+ *
23187
+ * Transaction control through raw SQL inside a scope dbsp is managing is
23188
+ * unsupported. `COMMIT`, `ROLLBACK`, and `PREPARE TRANSACTION` end the
23189
+ * transaction dbsp is working inside; dbsp detects that and fails loudly, but
23190
+ * the data is already whatever your statement made it. Raw savepoint control
23191
+ * (`SAVEPOINT`, `RELEASE SAVEPOINT`, `ROLLBACK TO SAVEPOINT`) can alter the
23192
+ * savepoint stack before dbsp sees the command tag; dbsp poisons the scope, but
23193
+ * it cannot make that command un-run. Manage your transaction outside dbsp's
23194
+ * calls.
20867
23195
  */
20868
23196
  async executeRaw(sql, parameters = []) {
20869
- const executor = this.requireConnection();
20870
- const result = await executor.query(sql, [...parameters]);
23197
+ const result = await this.executeQueryProtectingOpenTransaction(
23198
+ sql,
23199
+ parameters,
23200
+ { rawSqlStatement: true }
23201
+ );
20871
23202
  return result.rows;
20872
23203
  }
20873
23204
  // =========================================================================
@@ -20898,19 +23229,311 @@ var PgsqlAdapter = class _PgsqlAdapter {
20898
23229
  * @since DDL-TABLE-001
20899
23230
  */
20900
23231
  async executeDDL(sql) {
20901
- if (!this.pool) {
23232
+ if (!this.client && !this.pool) {
20902
23233
  throw new Error("Cannot execute DDL on compile-only adapter");
20903
23234
  }
20904
- await this.pool.query(sql);
23235
+ await this.executeQueryProtectingOpenTransaction(sql);
20905
23236
  }
20906
23237
  /**
20907
- * Whether this adapter instance is scoped inside a transaction.
20908
- * Guards unsafe DDL operations (VACUUM, CREATE INDEX CONCURRENTLY).
23238
+ * Whether a transaction is open on this adapter's connection.
23239
+ * This is true for dbsp-managed scopes and for borrowed pg clients whose
23240
+ * ReadyForQuery status says the caller has an open transaction.
20909
23241
  *
20910
23242
  * @since DDL-TABLE-001
20911
23243
  */
20912
23244
  get inTransaction() {
20913
- return this.client !== void 0;
23245
+ if (this.adapterManagedTransaction) return this.adapterManagedScopeIsLive();
23246
+ if (this.client === void 0) return false;
23247
+ return poolClientTransactionOpen(this.client) !== false;
23248
+ }
23249
+ adapterManagedScopeIsLive() {
23250
+ if (!this.adapterManagedTransaction || this.client === void 0 || this.scopeToken === void 0 || this.scopeState === void 0) {
23251
+ return false;
23252
+ }
23253
+ return this.findClientScope(this.client, this.scopeToken)?.state === this.scopeState;
23254
+ }
23255
+ async executeQueryProtectingOpenTransaction(sql, parameters, options = {}) {
23256
+ return this.executeConnectionStatement(
23257
+ this.requireConnection(),
23258
+ sql,
23259
+ parameters,
23260
+ {
23261
+ ...options,
23262
+ protectBorrowedClientTransaction: this.client !== void 0 && !this.adapterManagedTransaction
23263
+ }
23264
+ );
23265
+ }
23266
+ async executeConnectionStatement(executor, sql, parameters, options = {}) {
23267
+ const shouldProtect = options.protectBorrowedClientTransaction ?? false;
23268
+ if (shouldProtect && isPoolClientLike(executor)) {
23269
+ return this.executeConnectionStatementInSavepoint(
23270
+ executor,
23271
+ sql,
23272
+ parameters,
23273
+ options
23274
+ );
23275
+ }
23276
+ const allowedScopeToken = options.allowedScopeToken ?? this.scopeToken;
23277
+ if (isPoolClientLike(executor)) {
23278
+ this.assertCanUseClient(
23279
+ executor,
23280
+ allowedScopeToken,
23281
+ options.allowAncestorScopeToken ?? false,
23282
+ options.allowClosingScope ?? false,
23283
+ options.allowPoisonedScope ?? false
23284
+ );
23285
+ }
23286
+ const managedScope = isPoolClientLike(executor) && options.inspectTransactionControl !== false && options.allowPoisonedScope !== true ? this.findClientScope(executor, allowedScopeToken) : void 0;
23287
+ if (managedScope !== void 0) {
23288
+ return this.runWithScopeStatementLock(
23289
+ managedScope.state,
23290
+ () => this.executeConnectionStatementUnlocked(
23291
+ executor,
23292
+ sql,
23293
+ parameters,
23294
+ { ...options, allowClosingScope: true },
23295
+ allowedScopeToken
23296
+ )
23297
+ );
23298
+ }
23299
+ return this.executeConnectionStatementUnlocked(
23300
+ executor,
23301
+ sql,
23302
+ parameters,
23303
+ options,
23304
+ allowedScopeToken
23305
+ );
23306
+ }
23307
+ async executeConnectionStatementUnlocked(executor, sql, parameters, options, allowedScopeToken) {
23308
+ if (isPoolClientLike(executor)) {
23309
+ this.assertCanUseClient(
23310
+ executor,
23311
+ allowedScopeToken,
23312
+ options.allowAncestorScopeToken ?? false,
23313
+ options.allowClosingScope ?? false,
23314
+ options.allowPoisonedScope ?? false
23315
+ );
23316
+ }
23317
+ let pendingError;
23318
+ let result;
23319
+ try {
23320
+ const rawResult = await this.issueConnectionQuery(
23321
+ executor,
23322
+ sql,
23323
+ parameters
23324
+ );
23325
+ if (options.inspectTransactionControl !== false) {
23326
+ await this.assertNoTransactionControlCommand(
23327
+ executor,
23328
+ rawResult,
23329
+ allowedScopeToken
23330
+ );
23331
+ this.assertNoMultiCommandRawCall(
23332
+ executor,
23333
+ rawResult,
23334
+ allowedScopeToken
23335
+ );
23336
+ }
23337
+ result = lastQueryResult(rawResult);
23338
+ } catch (error) {
23339
+ pendingError = error;
23340
+ if (isPoolClientLike(executor) && options.inspectTransactionControl !== false && options.allowPoisonedScope !== true) {
23341
+ this.poisonClientScope(
23342
+ executor,
23343
+ allowedScopeToken,
23344
+ new PgsqlTransactionAbortedError(error)
23345
+ );
23346
+ }
23347
+ }
23348
+ if (pendingError !== void 0) {
23349
+ throw pendingError;
23350
+ }
23351
+ if (result === void 0) {
23352
+ throw new Error("PostgreSQL returned no query results");
23353
+ }
23354
+ return result;
23355
+ }
23356
+ async executeConnectionStatementInSavepoint(client, sql, parameters, options = {}) {
23357
+ const rawSqlStatement = options.rawSqlStatement ?? false;
23358
+ const savepointName = nextSavepointName();
23359
+ const scopeToken = this.scopeToken ?? createScopeToken();
23360
+ const scopeState = this.scopeState ?? createScopeState();
23361
+ const releaseScope = this.enterSavepointScope(
23362
+ client,
23363
+ scopeToken,
23364
+ scopeState,
23365
+ "statement"
23366
+ );
23367
+ try {
23368
+ await this.executeConnectionStatement(
23369
+ client,
23370
+ `SAVEPOINT ${savepointName}`,
23371
+ void 0,
23372
+ {
23373
+ allowedScopeToken: scopeToken,
23374
+ inspectTransactionControl: false,
23375
+ protectBorrowedClientTransaction: false
23376
+ }
23377
+ );
23378
+ } catch (error) {
23379
+ releaseScope();
23380
+ if (isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
23381
+ return this.executeConnectionStatement(client, sql, parameters, {
23382
+ ...options,
23383
+ inspectTransactionControl: options.inspectTransactionControl ?? true,
23384
+ protectBorrowedClientTransaction: false
23385
+ });
23386
+ }
23387
+ throw error;
23388
+ }
23389
+ let result;
23390
+ try {
23391
+ result = await this.executeConnectionStatement(
23392
+ client,
23393
+ sql,
23394
+ parameters,
23395
+ {
23396
+ ...options,
23397
+ allowedScopeToken: scopeToken,
23398
+ inspectTransactionControl: options.inspectTransactionControl ?? true,
23399
+ protectBorrowedClientTransaction: false
23400
+ }
23401
+ );
23402
+ } catch (error) {
23403
+ this.closeScope(scopeState);
23404
+ try {
23405
+ await this.rollbackAndReleaseSavepoint(
23406
+ client,
23407
+ savepointName,
23408
+ scopeToken,
23409
+ scopeState
23410
+ );
23411
+ } catch (cleanupErr) {
23412
+ if (isRawSqlTransactionControlError(error) && isSavepointUnavailableError(cleanupErr)) {
23413
+ throw error;
23414
+ }
23415
+ throw createCleanupFailureError(
23416
+ protectedStatementCleanupAction(
23417
+ rawSqlStatement,
23418
+ "savepoint cleanup failed after the protected statement failed; the caller transaction may now be in an unknown state"
23419
+ ),
23420
+ error,
23421
+ cleanupErr
23422
+ );
23423
+ } finally {
23424
+ releaseScope();
23425
+ }
23426
+ if (isRawSqlTransactionControlError(error)) {
23427
+ throw error;
23428
+ }
23429
+ if (rawSqlStatement) {
23430
+ throw addRawSqlTransactionContext(error);
23431
+ }
23432
+ throw error;
23433
+ }
23434
+ try {
23435
+ this.closeScopeAndAssertChildren(scopeState);
23436
+ await this.drainScopeWork(scopeState);
23437
+ this.throwIfScopePoisoned(scopeState);
23438
+ await this.executeScopeBoundaryStatement(
23439
+ client,
23440
+ scopeToken,
23441
+ scopeState,
23442
+ `RELEASE SAVEPOINT ${savepointName}`,
23443
+ {
23444
+ protectBorrowedClientTransaction: false
23445
+ }
23446
+ );
23447
+ } catch (releaseErr) {
23448
+ throw await this.rollbackSavepointAfterReleaseFailure(
23449
+ client,
23450
+ savepointName,
23451
+ scopeToken,
23452
+ scopeState,
23453
+ releaseErr,
23454
+ protectedStatementCleanupAction(
23455
+ rawSqlStatement,
23456
+ "RELEASE SAVEPOINT failed after the protected statement succeeded"
23457
+ )
23458
+ );
23459
+ } finally {
23460
+ releaseScope();
23461
+ }
23462
+ return result;
23463
+ }
23464
+ async issueConnectionQuery(executor, sql, parameters) {
23465
+ if (parameters === void 0) {
23466
+ return executor.query(sql);
23467
+ }
23468
+ return executor.query(sql, [...parameters]);
23469
+ }
23470
+ assertNoMultiCommandRawCall(executor, result, allowedScopeToken) {
23471
+ if (!Array.isArray(result) || !isPoolClientLike(executor)) return;
23472
+ const current = this.currentClientScope(executor);
23473
+ if (current === void 0 || current.token !== allowedScopeToken) return;
23474
+ throw this.poisonClientScope(
23475
+ executor,
23476
+ allowedScopeToken,
23477
+ new Error(RAW_SQL_MULTI_COMMAND_MESSAGE, {
23478
+ cause: queryResultsMetadata(result)
23479
+ })
23480
+ );
23481
+ }
23482
+ async assertNoTransactionControlCommand(executor, result, allowedScopeToken) {
23483
+ if (!isPoolClientLike(executor)) return;
23484
+ const current = this.currentClientScope(executor);
23485
+ if (current === void 0 || current.token !== allowedScopeToken) return;
23486
+ const controlCause = Array.isArray(result) ? queryResultsMetadata(result) : void 0;
23487
+ for (const queryResult of queryResults(result)) {
23488
+ if (isPrepareCommandTag(queryResult.command)) {
23489
+ await this.assertPrepareDidNotEndTransaction(
23490
+ executor,
23491
+ allowedScopeToken,
23492
+ queryResult,
23493
+ controlCause
23494
+ );
23495
+ continue;
23496
+ }
23497
+ if (isTransactionControlCommandTag(queryResult.command)) {
23498
+ throw this.poisonClientScopeStack(
23499
+ executor,
23500
+ new PgsqlRawSqlTransactionControlError(controlCause ?? queryResult)
23501
+ );
23502
+ }
23503
+ }
23504
+ }
23505
+ async assertPrepareDidNotEndTransaction(client, allowedScopeToken, queryResult, cause = queryResult) {
23506
+ const probeSavepointName = nextSavepointName();
23507
+ try {
23508
+ await this.executeConnectionStatement(
23509
+ client,
23510
+ `SAVEPOINT ${probeSavepointName}`,
23511
+ void 0,
23512
+ {
23513
+ ...allowedScopeToken !== void 0 && { allowedScopeToken },
23514
+ inspectTransactionControl: false,
23515
+ protectBorrowedClientTransaction: false
23516
+ }
23517
+ );
23518
+ } catch (error) {
23519
+ if (isPgErrorWithCode(error, NO_ACTIVE_SQL_TRANSACTION)) {
23520
+ throw this.poisonClientScopeStack(
23521
+ client,
23522
+ new PgsqlRawSqlTransactionControlError(cause)
23523
+ );
23524
+ }
23525
+ throw error;
23526
+ }
23527
+ await this.executeConnectionStatement(
23528
+ client,
23529
+ `RELEASE SAVEPOINT ${probeSavepointName}`,
23530
+ void 0,
23531
+ {
23532
+ ...allowedScopeToken !== void 0 && { allowedScopeToken },
23533
+ inspectTransactionControl: false,
23534
+ protectBorrowedClientTransaction: false
23535
+ }
23536
+ );
20914
23537
  }
20915
23538
  /**
20916
23539
  * Resolve the explicit schema for a catalog read: an explicit argument, else
@@ -20929,7 +23552,6 @@ var PgsqlAdapter = class _PgsqlAdapter {
20929
23552
  * @param schema - Schema name (defaults to the search_path-resolved schema)
20930
23553
  */
20931
23554
  async listIndexes(table, schema, options) {
20932
- const executor = this.requireConnection();
20933
23555
  const params = [table, this.explicitSchema(schema) ?? null];
20934
23556
  let sql = `SELECT indexname, indexdef FROM pg_indexes WHERE tablename = $1 AND schemaname = COALESCE($2, ${RESOLVE_TABLE_SCHEMA_SQL("$1")})`;
20935
23557
  if (options?.namePattern) {
@@ -20937,7 +23559,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
20937
23559
  params.push(options.namePattern);
20938
23560
  }
20939
23561
  sql += " ORDER BY indexname";
20940
- const result = await executor.query(sql, params);
23562
+ const result = await this.executeQueryProtectingOpenTransaction(sql, params);
20941
23563
  return result.rows.map((row) => {
20942
23564
  const def = row.indexdef;
20943
23565
  const unique = /\bCREATE UNIQUE INDEX\b/i.test(def);
@@ -20954,8 +23576,7 @@ var PgsqlAdapter = class _PgsqlAdapter {
20954
23576
  * @param schema - Schema name (defaults to the search_path-resolved schema)
20955
23577
  */
20956
23578
  async indexExists(name, table, schema) {
20957
- const executor = this.requireConnection();
20958
- const result = await executor.query(
23579
+ const result = await this.executeQueryProtectingOpenTransaction(
20959
23580
  `SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = $1 AND tablename = $2 AND schemaname = COALESCE($3, ${RESOLVE_TABLE_SCHEMA_SQL("$2")})) AS exists`,
20960
23581
  [name, table, this.explicitSchema(schema) ?? null]
20961
23582
  );
@@ -20973,14 +23594,12 @@ var PgsqlAdapter = class _PgsqlAdapter {
20973
23594
  * @param schema - Schema name (defaults to the search_path-resolved schema)
20974
23595
  */
20975
23596
  async storageSize(table, schema) {
20976
- const executor = this.requireConnection();
20977
23597
  const schemaName = this.explicitSchema(schema);
20978
23598
  const quotedTable = `"${table.replace(/"/g, '""')}"`;
20979
23599
  const identifier = schemaName !== void 0 ? `"${schemaName.replace(/"/g, '""')}".${quotedTable}` : quotedTable;
20980
- const result = await executor.query(
20981
- `SELECT pg_total_relation_size($1::regclass)::bigint AS size`,
20982
- [identifier]
20983
- );
23600
+ const result = await this.executeQueryProtectingOpenTransaction(`SELECT pg_total_relation_size($1::regclass)::bigint AS size`, [
23601
+ identifier
23602
+ ]);
20984
23603
  return Number(result.rows[0]?.size ?? 0);
20985
23604
  }
20986
23605
  /**
@@ -21034,8 +23653,22 @@ var PgsqlAdapter = class _PgsqlAdapter {
21034
23653
  validateIdentifier(value, identifierType);
21035
23654
  }
21036
23655
  };
21037
- function createPgsqlAdapter(pool, options) {
21038
- return new PgsqlAdapter(pool, options);
23656
+ function createPgsqlAdapter(connection, options) {
23657
+ if (isPoolClientLike(connection)) {
23658
+ if (!hasBorrowedClientOption(options)) {
23659
+ throw new Error(
23660
+ "createPgsqlAdapter() received a pg PoolClient. Pass borrowedClient: true to declare that the caller owns this connection."
23661
+ );
23662
+ }
23663
+ } else if (hasBorrowedClientOption(options)) {
23664
+ throw new Error(
23665
+ "createPgsqlAdapter() received borrowedClient: true with a pg Pool. Pass a PoolClient when borrowing a caller-owned connection."
23666
+ );
23667
+ }
23668
+ return createPgsqlAdapterFromConstructionOptions(
23669
+ connection,
23670
+ options ?? {}
23671
+ );
21039
23672
  }
21040
23673
  function createPgsqlCompileOnlyAdapter(options) {
21041
23674
  return new PgsqlAdapter(
@@ -21085,12 +23718,20 @@ function matchesAnyPattern(value, patterns) {
21085
23718
  init_validate();
21086
23719
  export {
21087
23720
  CamelCaseNamingPlugin,
23721
+ CheckConstraintCanonicalizationError,
23722
+ CheckConstraintNewEnumValueError,
21088
23723
  DEFAULT_PK_COLUMN,
21089
23724
  DEFAULT_REDACTION_PATTERNS,
23725
+ ExpressionCanonicalizationUnavailableError,
21090
23726
  IdentityNamingPlugin,
21091
23727
  InvalidIdentifierError,
23728
+ NonConvergentSchemaDiffError,
21092
23729
  PgsqlAdapter,
23730
+ PgsqlRawSqlTransactionControlError,
23731
+ PgsqlTransactionAbortedCommitError,
23732
+ PgsqlTransactionAbortedError,
21093
23733
  PlanCompiler,
23734
+ assertNoRepeatedExpressionSurfaceDrift,
21094
23735
  bm25Search,
21095
23736
  booleanSearch,
21096
23737
  boost,
@@ -21109,7 +23750,9 @@ export {
21109
23750
  buildStreamingStatements,
21110
23751
  camelCaseNaming,
21111
23752
  canGenerateCreateIndex,
23753
+ canonicalizeCheckConstraints,
21112
23754
  collectAndValidateParamRefs,
23755
+ comparePgsqlDatabaseSchema,
21113
23756
  compareSchemata,
21114
23757
  compileDelete,
21115
23758
  compileInsert,