@warlock.js/cascade 5.19.1 → 5.21.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/CHANGELOG.md +36 -1
- package/cjs/index.cjs +2356 -1039
- package/cjs/index.cjs.map +1 -1
- package/esm/context/database-transaction-context.d.mts +10 -0
- package/esm/context/database-transaction-context.d.mts.map +1 -1
- package/esm/context/database-transaction-context.mjs +17 -0
- package/esm/context/database-transaction-context.mjs.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +30 -3
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +33 -1
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +75 -5
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/database-dirty-tracker.d.mts +5 -0
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +16 -0
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +58 -19
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -3
- package/esm/drivers/postgres/index.mjs +1 -2
- package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
- package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +196 -58
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
- package/esm/events/model-events.d.mts +1 -1
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +6 -6
- package/esm/index.mjs +5 -5
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-builder.mjs +2 -2
- package/esm/migration/column-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +2 -2
- package/esm/migration/index.mjs +1 -1
- package/esm/migration/migration-order.mjs +5 -2
- package/esm/migration/migration-order.mjs.map +1 -1
- package/esm/migration/migration-runner.d.mts +50 -5
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +370 -147
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +36 -3
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +62 -5
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -3
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/delete-methods.mjs +6 -2
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs +16 -2
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/instance-event-methods.mjs +2 -1
- package/esm/model/methods/instance-event-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +53 -16
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +86 -15
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs +16 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +50 -50
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +16 -1
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +39 -5
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/register-model.d.mts +6 -1
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs +16 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +21 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +97 -20
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/key-conventions.mjs +13 -3
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.mjs +1 -1
- package/esm/relations/relation-loader.d.mts +15 -5
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +51 -16
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs +21 -7
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +12 -2
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +43 -13
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sync/model-events.mjs +63 -3
- package/esm/sync/model-events.mjs.map +1 -1
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync-operation.mjs +3 -2
- package/esm/sync/model-sync-operation.mjs.map +1 -1
- package/esm/sync/sync-manager.d.mts +3 -42
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs +37 -126
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/transactions/after-commit.d.mts +24 -0
- package/esm/transactions/after-commit.d.mts.map +1 -0
- package/esm/transactions/after-commit.mjs +47 -0
- package/esm/transactions/after-commit.mjs.map +1 -0
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/connect-to-database.mjs +4 -4
- package/esm/utils/connect-to-database.mjs.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +1 -1
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs +1 -0
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/define-model.mjs +2 -10
- package/esm/utils/define-model.mjs.map +1 -1
- package/esm/utils/is-valid-date-value.mjs +3 -2
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/once-connected.mjs +12 -2
- package/esm/utils/once-connected.mjs.map +1 -1
- package/esm/validation/mutators/embed-mutator.mjs +19 -11
- package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
- package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
- package/esm/validation/rules/database-model-rule.mjs +6 -5
- package/esm/validation/rules/database-model-rule.mjs.map +1 -1
- package/esm/validation/rules/exists-rule.mjs +6 -4
- package/esm/validation/rules/exists-rule.mjs.map +1 -1
- package/esm/validation/rules/unique-rule.mjs +5 -2
- package/esm/validation/rules/unique-rule.mjs.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.mjs +1 -1
- package/esm/validation/validators/embed-validator.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +27 -0
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +84 -17
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +2783 -2766
- package/llms.txt +15 -15
- package/package.json +6 -6
- package/skills/aggregate-data/SKILL.md +165 -165
- package/skills/alter-migration/SKILL.md +531 -531
- package/skills/configure-delete-strategy/SKILL.md +130 -130
- package/skills/define-model/SKILL.md +205 -205
- package/skills/define-relations/SKILL.md +171 -171
- package/skills/index.md +23 -0
- package/skills/manage-data-sources/SKILL.md +140 -140
- package/skills/manage-transactions/SKILL.md +208 -191
- package/skills/paginate-results/SKILL.md +122 -122
- package/skills/perform-atomic-ops/SKILL.md +156 -156
- package/skills/query-data/SKILL.md +213 -213
- package/skills/run-cascade-cli/SKILL.md +125 -125
- package/skills/search-by-vector/SKILL.md +127 -127
- package/skills/subscribe-to-model-events/SKILL.md +148 -148
- package/skills/track-changes/SKILL.md +127 -127
- package/skills/write-migration/SKILL.md +206 -206
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
- package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
- package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { databaseTransactionContext } from "../../context/database-transaction-context.mjs";
|
|
2
|
+
import { flushAfterCommit } from "../../transactions/after-commit.mjs";
|
|
2
3
|
import { TransactionRollbackError } from "../../errors/transaction-rollback.error.mjs";
|
|
3
4
|
import { UnsupportedUpdateOperationError } from "../../errors/unsupported-update-operation.error.mjs";
|
|
4
|
-
import { isValidDateValue } from "../../utils/is-valid-date-value.mjs";
|
|
5
5
|
import { PostgresBlueprint } from "./postgres-blueprint.mjs";
|
|
6
6
|
import { PostgresDialect } from "./postgres-dialect.mjs";
|
|
7
7
|
import { SqlDatabaseDirtyTracker } from "../../sql-database-dirty-tracker.mjs";
|
|
8
|
+
import { convertPostgresValue } from "./postgres-type-converters.mjs";
|
|
8
9
|
import { PostgresMigrationDriver } from "./postgres-migration-driver.mjs";
|
|
9
10
|
import { PostgresQueryBuilder } from "./postgres-query-builder.mjs";
|
|
10
11
|
import { PostgresSQLSerializer } from "./postgres-sql-serializer.mjs";
|
|
11
|
-
import { PostgresSyncAdapter } from "./postgres-sync-adapter.mjs";
|
|
12
12
|
import { assertPostgresUpdate } from "./postgres-update-validator.mjs";
|
|
13
|
-
import { colors } from "@mongez/copper";
|
|
14
13
|
import { log } from "@warlock.js/logger";
|
|
14
|
+
import { colors } from "@mongez/copper";
|
|
15
15
|
|
|
16
16
|
//#region ../cascade/src/drivers/postgres/postgres-driver.ts
|
|
17
17
|
/**
|
|
@@ -54,6 +54,7 @@ function buildPostgresPoolConfig(config) {
|
|
|
54
54
|
idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
|
|
55
55
|
connectionTimeoutMillis: config.connectionTimeoutMillis ?? 1e4,
|
|
56
56
|
keepAlive: config.keepAlive ?? true,
|
|
57
|
+
maxUses: config.maxUses,
|
|
57
58
|
application_name: config.application_name ?? "cascade",
|
|
58
59
|
ssl: config.ssl
|
|
59
60
|
};
|
|
@@ -78,6 +79,10 @@ async function loadPg() {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
82
|
+
* Transaction session -> the driver (data source) that opened it.
|
|
83
|
+
*/
|
|
84
|
+
const transactionOwners = /* @__PURE__ */ new WeakMap();
|
|
85
|
+
/**
|
|
81
86
|
* PostgreSQL database driver implementing the Cascade DriverContract.
|
|
82
87
|
*
|
|
83
88
|
* Provides connection pooling, CRUD operations, transactions, and
|
|
@@ -106,6 +111,11 @@ async function loadPg() {
|
|
|
106
111
|
* await driver.disconnect();
|
|
107
112
|
* ```
|
|
108
113
|
*/
|
|
114
|
+
/** Summarise bound parameters as count and types, never values. */
|
|
115
|
+
function describeParams(params) {
|
|
116
|
+
const types = params.map((param) => param === null ? "null" : typeof param);
|
|
117
|
+
return `Params: ${params.length} (${types.join(", ")})`;
|
|
118
|
+
}
|
|
109
119
|
var PostgresDriver = class {
|
|
110
120
|
config;
|
|
111
121
|
/**
|
|
@@ -168,6 +178,8 @@ var PostgresDriver = class {
|
|
|
168
178
|
* @see PostgresPoolConfig.nativeArrayColumns
|
|
169
179
|
*/
|
|
170
180
|
_nativeArrayColumns;
|
|
181
|
+
/** Column `dataTypeID`s per result row, filled by `query()` for `deserialize()`. */
|
|
182
|
+
rowColumnTypes = /* @__PURE__ */ new WeakMap();
|
|
171
183
|
/** Unique keys per table, resolved from `pg_index` for upsert conflict targets. */
|
|
172
184
|
_uniqueKeys = /* @__PURE__ */ new Map();
|
|
173
185
|
/**
|
|
@@ -342,6 +354,16 @@ var PostgresDriver = class {
|
|
|
342
354
|
return this._nativeArrayColumns.has(column);
|
|
343
355
|
}
|
|
344
356
|
/**
|
|
357
|
+
* Drop the cached unique keys and re-read the native-array columns.
|
|
358
|
+
*
|
|
359
|
+
* Called by the migration driver after DDL so an in-process migration is
|
|
360
|
+
* visible to the serializer and to upserts without reconnecting.
|
|
361
|
+
*/
|
|
362
|
+
async invalidateSchemaCaches() {
|
|
363
|
+
this._uniqueKeys.clear();
|
|
364
|
+
await this.loadNativeArrayColumns();
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
345
367
|
* Introspect the live schema for native-array columns so array values bind
|
|
346
368
|
* correctly with zero app configuration.
|
|
347
369
|
*
|
|
@@ -393,26 +415,11 @@ var PostgresDriver = class {
|
|
|
393
415
|
* @returns Deserialized JavaScript object
|
|
394
416
|
*/
|
|
395
417
|
deserialize(data) {
|
|
418
|
+
const columnTypes = this.rowColumnTypes.get(data);
|
|
419
|
+
if (!columnTypes) return data;
|
|
396
420
|
for (const [key, value] of Object.entries(data)) {
|
|
397
421
|
if (typeof value !== "string") continue;
|
|
398
|
-
|
|
399
|
-
data[key] = new Date(value);
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
if (value.charCodeAt(0) === 91 && value.charCodeAt(value.length - 1) === 93) {
|
|
403
|
-
const parts = value.slice(1, -1).split(",");
|
|
404
|
-
const nums = new Array(parts.length);
|
|
405
|
-
let isNumericVector = parts.length > 0;
|
|
406
|
-
for (let i = 0; i < parts.length; i++) {
|
|
407
|
-
const n = +(parts[i] ?? "");
|
|
408
|
-
if (!Number.isFinite(n)) {
|
|
409
|
-
isNumericVector = false;
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
nums[i] = n;
|
|
413
|
-
}
|
|
414
|
-
if (isNumericVector) data[key] = nums;
|
|
415
|
-
}
|
|
422
|
+
data[key] = convertPostgresValue(value, columnTypes.get(key));
|
|
416
423
|
}
|
|
417
424
|
return data;
|
|
418
425
|
}
|
|
@@ -489,7 +496,15 @@ var PostgresDriver = class {
|
|
|
489
496
|
try {
|
|
490
497
|
return { modifiedCount: (await this.query(sql, params)).rowCount ?? 0 };
|
|
491
498
|
} catch (error) {
|
|
492
|
-
|
|
499
|
+
log.error({
|
|
500
|
+
module: "database.postgres",
|
|
501
|
+
action: "update.error",
|
|
502
|
+
message: `${sql} | ${describeParams(params)}`,
|
|
503
|
+
context: {
|
|
504
|
+
sql,
|
|
505
|
+
paramCount: params.length
|
|
506
|
+
}
|
|
507
|
+
});
|
|
493
508
|
throw error;
|
|
494
509
|
}
|
|
495
510
|
}
|
|
@@ -666,7 +681,8 @@ var PostgresDriver = class {
|
|
|
666
681
|
const conflictColumns = options?.conflictColumns ?? Object.keys(filter);
|
|
667
682
|
if (conflictColumns.length === 0) throw new Error("Upsert requires conflictColumns option or filter with columns");
|
|
668
683
|
const quotedConflictColumns = conflictColumns.map((c) => this.dialect.quoteIdentifier(c)).join(", ");
|
|
669
|
-
const
|
|
684
|
+
const createdAtColumn = options?.createdAtColumn ?? this.modelDefaults.createdAtColumn;
|
|
685
|
+
const setClauses = columns.filter((col) => !conflictColumns.includes(col) && col !== createdAtColumn).map((col, i) => {
|
|
670
686
|
const valueIndex = columns.indexOf(col) + 1;
|
|
671
687
|
return `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(valueIndex)}`;
|
|
672
688
|
}).join(", ");
|
|
@@ -684,7 +700,7 @@ var PostgresDriver = class {
|
|
|
684
700
|
async findOneAndDelete(table, filter, _options) {
|
|
685
701
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
686
702
|
const { whereClause, whereParams } = this.buildWhereClause(filter, 1);
|
|
687
|
-
const sql =
|
|
703
|
+
const sql = `${this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter, whereClause)} RETURNING *`;
|
|
688
704
|
const result = await this.query(sql, whereParams);
|
|
689
705
|
return result.rows[0] ? result.rows[0] : null;
|
|
690
706
|
}
|
|
@@ -699,7 +715,7 @@ var PostgresDriver = class {
|
|
|
699
715
|
async delete(table, filter, _options) {
|
|
700
716
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
701
717
|
const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);
|
|
702
|
-
const sql =
|
|
718
|
+
const sql = this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter ?? {}, whereClause);
|
|
703
719
|
return (await this.query(sql, whereParams)).rowCount ?? 0;
|
|
704
720
|
}
|
|
705
721
|
/**
|
|
@@ -800,27 +816,34 @@ var PostgresDriver = class {
|
|
|
800
816
|
const ctx = { rollback(reason) {
|
|
801
817
|
throw new TransactionRollbackError(reason);
|
|
802
818
|
} };
|
|
803
|
-
if (
|
|
819
|
+
if (this.canJoinActiveTransaction()) return fn(ctx);
|
|
804
820
|
const tx = await this.beginTransaction(options);
|
|
805
|
-
|
|
821
|
+
transactionOwners.set(tx.context, this);
|
|
806
822
|
let commitStarted = false;
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
823
|
+
let committedCallbacks = [];
|
|
824
|
+
let result;
|
|
825
|
+
await databaseTransactionContext.run({
|
|
826
|
+
session: tx.context,
|
|
827
|
+
afterCommit: []
|
|
828
|
+
}, async () => {
|
|
829
|
+
try {
|
|
830
|
+
result = await fn(ctx);
|
|
831
|
+
commitStarted = true;
|
|
832
|
+
await tx.commit();
|
|
833
|
+
committedCallbacks = databaseTransactionContext.takeAfterCommit();
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (commitStarted) log.error(`database.postgress`, "transaction", "Transaction COMMIT failed; client discarded, no rollback issued");
|
|
836
|
+
else try {
|
|
837
|
+
await tx.rollback();
|
|
838
|
+
log.error(`database.postgress`, "transaction", "Transaction operation failed, rolled back everything");
|
|
839
|
+
} catch (rollbackError) {
|
|
840
|
+
log.error(`database.postgress`, "transaction", rollbackError);
|
|
841
|
+
}
|
|
842
|
+
throw error;
|
|
819
843
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
844
|
+
});
|
|
845
|
+
await flushAfterCommit(committedCallbacks);
|
|
846
|
+
return result;
|
|
824
847
|
}
|
|
825
848
|
/**
|
|
826
849
|
* Perform an atomic update operation.
|
|
@@ -851,13 +874,94 @@ var PostgresDriver = class {
|
|
|
851
874
|
/**
|
|
852
875
|
* Get the sync adapter for bulk denormalized updates.
|
|
853
876
|
*
|
|
877
|
+
* Sync instructions arrive in the portable Mongo shape (`$set` with a `field.$`
|
|
878
|
+
* positional key, `$pull`, `$unset`, dotted filter keys), so they are translated
|
|
879
|
+
* to jsonb SQL here instead of being handed to `updateMany`, which would quote
|
|
880
|
+
* `"tags.$"` as a column name.
|
|
881
|
+
*
|
|
854
882
|
* @returns Sync adapter instance
|
|
855
883
|
*/
|
|
856
884
|
syncAdapter() {
|
|
857
|
-
if (!this._syncAdapter)
|
|
885
|
+
if (!this._syncAdapter) {
|
|
886
|
+
const executeOne = (instruction) => this.executeSyncInstruction(instruction);
|
|
887
|
+
this._syncAdapter = {
|
|
888
|
+
executeOne,
|
|
889
|
+
executeArrayUpdate: executeOne,
|
|
890
|
+
executeBatch: async (instructions) => {
|
|
891
|
+
let affected = 0;
|
|
892
|
+
for (const instruction of instructions) affected += await executeOne(instruction);
|
|
893
|
+
return affected;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
858
897
|
return this._syncAdapter;
|
|
859
898
|
}
|
|
860
899
|
/**
|
|
900
|
+
* Execute one sync instruction as a jsonb UPDATE.
|
|
901
|
+
*
|
|
902
|
+
* @param instruction - Sync instruction in the portable Mongo shape
|
|
903
|
+
* @returns Number of affected rows
|
|
904
|
+
*/
|
|
905
|
+
async executeSyncInstruction(instruction) {
|
|
906
|
+
const { sql, params } = this.buildSyncStatement(instruction);
|
|
907
|
+
return (await this.query(sql, params)).rowCount ?? 0;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Translate a sync instruction to a jsonb UPDATE statement.
|
|
911
|
+
*
|
|
912
|
+
* @param instruction - Sync instruction
|
|
913
|
+
* @returns SQL and parameters
|
|
914
|
+
* @throws Error for update operators a sync cannot express
|
|
915
|
+
*/
|
|
916
|
+
buildSyncStatement(instruction) {
|
|
917
|
+
const params = [];
|
|
918
|
+
const bind = (value) => {
|
|
919
|
+
params.push(value);
|
|
920
|
+
return this.dialect.placeholder(params.length);
|
|
921
|
+
};
|
|
922
|
+
const quote = (name) => this.dialect.quoteIdentifier(name);
|
|
923
|
+
const elementMatch = (idField, idValue, operator) => `(e->>${bind(idField)}::text) ${operator} ${bind(String(idValue))}::text`;
|
|
924
|
+
const clauses = [];
|
|
925
|
+
for (const [key, value] of Object.entries(instruction.update)) if (key === "$set") for (const [path, embed] of Object.entries(value)) if (path.endsWith(".$")) {
|
|
926
|
+
const column = quote(path.slice(0, -2));
|
|
927
|
+
const { identifierField, identifierValue } = instruction;
|
|
928
|
+
if (!identifierField || identifierValue === void 0) throw new Error(`Positional update on "${path}" requires an identifier field/value.`);
|
|
929
|
+
const match = elementMatch(identifierField, identifierValue, "=");
|
|
930
|
+
clauses.push(`${column} = (SELECT COALESCE(jsonb_agg(CASE WHEN ${match} THEN ${bind(JSON.stringify(embed))}::jsonb ELSE e END), '[]'::jsonb) FROM jsonb_array_elements(${column}::jsonb) e)`);
|
|
931
|
+
} else if (path.includes(".")) {
|
|
932
|
+
const [head, ...rest] = path.split(".");
|
|
933
|
+
const column = quote(head);
|
|
934
|
+
clauses.push(`${column} = jsonb_set(COALESCE(${column}::jsonb, '{}'::jsonb), ${bind(rest)}::text[], ${bind(JSON.stringify(embed))}::jsonb)`);
|
|
935
|
+
} else clauses.push(`${quote(path)} = ${bind(JSON.stringify(embed))}::jsonb`);
|
|
936
|
+
else if (key === "$pull") for (const [field, criteria] of Object.entries(value)) {
|
|
937
|
+
const column = quote(field);
|
|
938
|
+
const entries = Object.entries(criteria);
|
|
939
|
+
const [idField, idValue] = entries[0] ?? [];
|
|
940
|
+
if (entries.length !== 1 || !idField) throw new Error(`$pull on "${field}" must match a single identifier field.`);
|
|
941
|
+
const match = elementMatch(idField, idValue, "IS DISTINCT FROM");
|
|
942
|
+
clauses.push(`${column} = (SELECT COALESCE(jsonb_agg(e), '[]'::jsonb) FROM jsonb_array_elements(${column}::jsonb) e WHERE ${match})`);
|
|
943
|
+
}
|
|
944
|
+
else if (key === "$unset") for (const field of Object.keys(value)) clauses.push(`${quote(field)} = NULL`);
|
|
945
|
+
else throw new Error(`Unsupported sync update operator "${key}" on the Postgres driver.`);
|
|
946
|
+
if (clauses.length === 0) throw new Error("No update operations specified for the sync instruction.");
|
|
947
|
+
const conditions = [];
|
|
948
|
+
for (const [key, value] of Object.entries(instruction.filter)) {
|
|
949
|
+
if (!key.includes(".")) {
|
|
950
|
+
conditions.push(`${quote(key)} = ${bind(value)}`);
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
const [head, ...rest] = key.split(".");
|
|
954
|
+
const column = quote(head);
|
|
955
|
+
const path = bind(rest);
|
|
956
|
+
conditions.push(instruction.isArrayUpdate && head === instruction.arrayField ? `EXISTS (SELECT 1 FROM jsonb_array_elements(${column}::jsonb) e WHERE (e #>> ${path}::text[]) = ${bind(String(value))}::text)` : `(${column}::jsonb #>> ${path}::text[]) = ${bind(String(value))}::text`);
|
|
957
|
+
}
|
|
958
|
+
const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
959
|
+
return {
|
|
960
|
+
sql: `UPDATE ${quote(instruction.targetTable)} SET ${clauses.join(", ")}${where}`,
|
|
961
|
+
params
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
861
965
|
* Get the migration driver for schema operations.
|
|
862
966
|
*
|
|
863
967
|
* @returns Migration driver instance
|
|
@@ -874,6 +978,25 @@ var PostgresDriver = class {
|
|
|
874
978
|
return new PostgresSQLSerializer(this.dialect);
|
|
875
979
|
}
|
|
876
980
|
/**
|
|
981
|
+
* Whether an active transaction can be joined: one exists and it is not
|
|
982
|
+
* owned by a different data source (sessions opened outside `transaction()`
|
|
983
|
+
* carry no owner tag and are joinable).
|
|
984
|
+
*/
|
|
985
|
+
canJoinActiveTransaction() {
|
|
986
|
+
const session = databaseTransactionContext.getSession();
|
|
987
|
+
if (!session) return false;
|
|
988
|
+
const owner = transactionOwners.get(session);
|
|
989
|
+
return owner === void 0 || owner === this;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* The ambient transaction session, only when it belongs to this driver's
|
|
993
|
+
* data source.
|
|
994
|
+
*/
|
|
995
|
+
getOwnSession() {
|
|
996
|
+
const session = databaseTransactionContext.getSession();
|
|
997
|
+
return session && transactionOwners.get(session) === this ? session : void 0;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
877
1000
|
* Execute a raw SQL query.
|
|
878
1001
|
*
|
|
879
1002
|
* Automatically uses the transaction client if one is active.
|
|
@@ -882,15 +1005,15 @@ var PostgresDriver = class {
|
|
|
882
1005
|
* @param params - Query parameters
|
|
883
1006
|
* @returns Query result
|
|
884
1007
|
*/
|
|
1008
|
+
recordColumnTypes(result) {
|
|
1009
|
+
if (!result.fields?.length || !result.rows?.length) return;
|
|
1010
|
+
const columnTypes = new Map(result.fields.map((field) => [field.name, field.dataTypeID]));
|
|
1011
|
+
for (const row of result.rows) if (row && typeof row === "object") this.rowColumnTypes.set(row, columnTypes);
|
|
1012
|
+
}
|
|
885
1013
|
async query(sql, params = []) {
|
|
886
|
-
const txClient =
|
|
1014
|
+
const txClient = this.getOwnSession();
|
|
887
1015
|
const startTime = this.config.logging ? performance.now() : 0;
|
|
888
|
-
|
|
889
|
-
if (this.config.logging && params.length > 0) {
|
|
890
|
-
paramsString = JSON.stringify(params);
|
|
891
|
-
if (paramsString.length > 300) paramsString = paramsString.substring(0, 300) + "...";
|
|
892
|
-
paramsString = ` | Params: ${paramsString}`;
|
|
893
|
-
}
|
|
1016
|
+
const paramsString = this.config.logging && params.length > 0 ? ` | ${describeParams(params)}` : "";
|
|
894
1017
|
try {
|
|
895
1018
|
let result;
|
|
896
1019
|
if (this.config.logging) log.info({
|
|
@@ -898,8 +1021,8 @@ var PostgresDriver = class {
|
|
|
898
1021
|
action: "query.executing",
|
|
899
1022
|
message: `${sql}${paramsString}`,
|
|
900
1023
|
context: {
|
|
901
|
-
|
|
902
|
-
|
|
1024
|
+
sql,
|
|
1025
|
+
paramCount: params.length
|
|
903
1026
|
}
|
|
904
1027
|
});
|
|
905
1028
|
if (txClient) result = await txClient.query(sql, params);
|
|
@@ -911,12 +1034,13 @@ var PostgresDriver = class {
|
|
|
911
1034
|
action: "query.executed",
|
|
912
1035
|
message: `[${duration}ms] ${sql}${paramsString}`,
|
|
913
1036
|
context: {
|
|
914
|
-
params,
|
|
915
1037
|
sql,
|
|
1038
|
+
paramCount: params.length,
|
|
916
1039
|
duration
|
|
917
1040
|
}
|
|
918
1041
|
});
|
|
919
1042
|
}
|
|
1043
|
+
this.recordColumnTypes(result);
|
|
920
1044
|
return result;
|
|
921
1045
|
} catch (error) {
|
|
922
1046
|
if (this.config.logging) {
|
|
@@ -927,7 +1051,7 @@ var PostgresDriver = class {
|
|
|
927
1051
|
message: `[${duration}ms] ${sql}${paramsString}`,
|
|
928
1052
|
context: {
|
|
929
1053
|
sql,
|
|
930
|
-
params,
|
|
1054
|
+
paramCount: params.length,
|
|
931
1055
|
error: error instanceof Error ? error.message : String(error)
|
|
932
1056
|
}
|
|
933
1057
|
});
|
|
@@ -1079,14 +1203,28 @@ var PostgresDriver = class {
|
|
|
1079
1203
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
1080
1204
|
const { whereClause, whereParams } = this.buildWhereClause(filter, set.nextIndex);
|
|
1081
1205
|
params.push(...whereParams);
|
|
1082
|
-
|
|
1083
|
-
if (limit === 1 && whereClause) sql = `UPDATE ${quotedTable} SET ${set.clauses.join(", ")} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1)`;
|
|
1206
|
+
const head = `UPDATE ${quotedTable} SET ${set.clauses.join(", ")}`;
|
|
1084
1207
|
return {
|
|
1085
|
-
sql,
|
|
1208
|
+
sql: limit === 1 && whereClause ? this.singleRowTarget(head, quotedTable, filter, whereClause) : `${head} ${whereClause}`,
|
|
1086
1209
|
params
|
|
1087
1210
|
};
|
|
1088
1211
|
}
|
|
1089
1212
|
/**
|
|
1213
|
+
* Build the `<head> WHERE ...` statement for a single-row UPDATE/DELETE.
|
|
1214
|
+
*
|
|
1215
|
+
* A filter pinned to the primary key (`id = <value>`) already matches at most
|
|
1216
|
+
* one row, so it is used as-is: a ctid subquery would re-check a stale tuple
|
|
1217
|
+
* after waiting on a concurrent write and silently update 0 rows.
|
|
1218
|
+
* Without a PK, the ctid subquery locks its candidate with
|
|
1219
|
+
* `FOR UPDATE SKIP LOCKED` so concurrent callers claim different rows
|
|
1220
|
+
* instead of losing the write.
|
|
1221
|
+
*/
|
|
1222
|
+
singleRowTarget(head, quotedTable, filter, whereClause) {
|
|
1223
|
+
const id = filter.id;
|
|
1224
|
+
if (id !== void 0 && id !== null && (typeof id !== "object" || id instanceof Date)) return `${head} ${whereClause}`;
|
|
1225
|
+
return `${head} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1 FOR UPDATE SKIP LOCKED)`;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1090
1228
|
* Create a new database.
|
|
1091
1229
|
*
|
|
1092
1230
|
* Note: This requires connecting to a system database (like 'postgres')
|