@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.d.ts +1265 -945
- package/dist/index.js +2875 -232
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
1104
|
-
|
|
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
|
-
|
|
1137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
12138
|
-
|
|
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,
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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(
|
|
12859
|
-
|
|
12860
|
-
|
|
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
|
|
12871
|
-
if (
|
|
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:
|
|
13021
|
+
meta: { check: dbCheckEntry.check }
|
|
12878
13022
|
});
|
|
12879
13023
|
changes.push({
|
|
12880
13024
|
kind: "add_check_constraint",
|
|
12881
13025
|
table: schema.name,
|
|
12882
|
-
destructive:
|
|
13026
|
+
destructive: true,
|
|
12883
13027
|
details: `Add CHECK constraint "${name}" ${check.expression}`,
|
|
12884
13028
|
meta: { check }
|
|
12885
13029
|
});
|
|
12886
13030
|
} else {
|
|
12887
|
-
const dbNotValid =
|
|
12888
|
-
const schemaNotValid =
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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 +
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
14824
|
+
function qualifyTable2(tableName, schemaName, naming) {
|
|
14647
14825
|
const table = quoteIdentifier(naming.toDatabase(tableName));
|
|
14648
14826
|
if (schemaName) {
|
|
14649
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
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
|
-
|
|
15866
|
+
[schema]
|
|
15867
|
+
)
|
|
15214
15868
|
),
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
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
|
-
|
|
15880
|
+
[schema]
|
|
15881
|
+
)
|
|
15226
15882
|
),
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
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
|
-
|
|
15925
|
+
[schema]
|
|
15926
|
+
)
|
|
15269
15927
|
),
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
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
|
-
|
|
15988
|
+
[schema]
|
|
15989
|
+
)
|
|
15330
15990
|
),
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
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
|
-
|
|
16002
|
+
[schema]
|
|
16003
|
+
)
|
|
15342
16004
|
),
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
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
|
-
|
|
16018
|
+
[schema]
|
|
16019
|
+
)
|
|
15356
16020
|
),
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
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
|
-
|
|
16034
|
+
[schema]
|
|
16035
|
+
)
|
|
15370
16036
|
),
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
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.
|
|
16043
|
+
NOT c.convalidated AS not_valid,
|
|
16044
|
+
r.relname AS raw_table
|
|
15377
16045
|
FROM pg_constraint c
|
|
15378
|
-
JOIN
|
|
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
|
-
|
|
16050
|
+
[schema]
|
|
16051
|
+
)
|
|
15382
16052
|
),
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
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
|
-
|
|
16067
|
+
[schema]
|
|
16068
|
+
)
|
|
15397
16069
|
),
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
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
|
-
|
|
15405
|
-
|
|
15406
|
-
|
|
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
|
-
|
|
16088
|
+
[schema]
|
|
16089
|
+
)
|
|
15414
16090
|
),
|
|
15415
|
-
|
|
15416
|
-
|
|
15417
|
-
|
|
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
|
-
|
|
16099
|
+
[schema]
|
|
16100
|
+
)
|
|
15423
16101
|
),
|
|
15424
|
-
|
|
15425
|
-
|
|
15426
|
-
|
|
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
|
-
|
|
16117
|
+
[schema]
|
|
16118
|
+
)
|
|
15439
16119
|
),
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
19870
|
-
return schemaName
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
20752
|
-
|
|
20753
|
-
|
|
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
|
-
|
|
20757
|
-
|
|
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
|
|
21835
|
+
await adapter.rollbackTransactionIfOpen(
|
|
21836
|
+
client,
|
|
21837
|
+
scopeToken,
|
|
21838
|
+
scopeState
|
|
21839
|
+
);
|
|
20762
21840
|
} catch (rollbackErr) {
|
|
20763
|
-
|
|
20764
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
20786
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
22218
|
+
transaction(fn4) {
|
|
22219
|
+
const childObserver = this.createChildTransactionObserver();
|
|
20833
22220
|
if (this.client) {
|
|
20834
|
-
|
|
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
|
-
|
|
20837
|
-
const client = await pool.connect();
|
|
22238
|
+
let pool;
|
|
20838
22239
|
try {
|
|
20839
|
-
|
|
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
|
-
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
20856
|
-
|
|
20857
|
-
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
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
|
|
20870
|
-
|
|
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.
|
|
23235
|
+
await this.executeQueryProtectingOpenTransaction(sql);
|
|
20905
23236
|
}
|
|
20906
23237
|
/**
|
|
20907
|
-
* Whether
|
|
20908
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
20981
|
-
|
|
20982
|
-
|
|
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(
|
|
21038
|
-
|
|
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,
|