@warlock.js/cascade 5.20.0 → 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 +30 -1
- package/cjs/index.cjs +2283 -1037
- package/cjs/index.cjs.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 +47 -16
- 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 +190 -57
- 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 +5 -6
- package/esm/index.mjs +4 -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 +369 -146
- 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/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/package.json +4 -4
- 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
|
@@ -2,14 +2,13 @@ import { databaseTransactionContext } from "../../context/database-transaction-c
|
|
|
2
2
|
import { flushAfterCommit } from "../../transactions/after-commit.mjs";
|
|
3
3
|
import { TransactionRollbackError } from "../../errors/transaction-rollback.error.mjs";
|
|
4
4
|
import { UnsupportedUpdateOperationError } from "../../errors/unsupported-update-operation.error.mjs";
|
|
5
|
-
import { isValidDateValue } from "../../utils/is-valid-date-value.mjs";
|
|
6
5
|
import { PostgresBlueprint } from "./postgres-blueprint.mjs";
|
|
7
6
|
import { PostgresDialect } from "./postgres-dialect.mjs";
|
|
8
7
|
import { SqlDatabaseDirtyTracker } from "../../sql-database-dirty-tracker.mjs";
|
|
8
|
+
import { convertPostgresValue } from "./postgres-type-converters.mjs";
|
|
9
9
|
import { PostgresMigrationDriver } from "./postgres-migration-driver.mjs";
|
|
10
10
|
import { PostgresQueryBuilder } from "./postgres-query-builder.mjs";
|
|
11
11
|
import { PostgresSQLSerializer } from "./postgres-sql-serializer.mjs";
|
|
12
|
-
import { PostgresSyncAdapter } from "./postgres-sync-adapter.mjs";
|
|
13
12
|
import { assertPostgresUpdate } from "./postgres-update-validator.mjs";
|
|
14
13
|
import { log } from "@warlock.js/logger";
|
|
15
14
|
import { colors } from "@mongez/copper";
|
|
@@ -55,6 +54,7 @@ function buildPostgresPoolConfig(config) {
|
|
|
55
54
|
idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
|
|
56
55
|
connectionTimeoutMillis: config.connectionTimeoutMillis ?? 1e4,
|
|
57
56
|
keepAlive: config.keepAlive ?? true,
|
|
57
|
+
maxUses: config.maxUses,
|
|
58
58
|
application_name: config.application_name ?? "cascade",
|
|
59
59
|
ssl: config.ssl
|
|
60
60
|
};
|
|
@@ -79,6 +79,10 @@ async function loadPg() {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
+
* Transaction session -> the driver (data source) that opened it.
|
|
83
|
+
*/
|
|
84
|
+
const transactionOwners = /* @__PURE__ */ new WeakMap();
|
|
85
|
+
/**
|
|
82
86
|
* PostgreSQL database driver implementing the Cascade DriverContract.
|
|
83
87
|
*
|
|
84
88
|
* Provides connection pooling, CRUD operations, transactions, and
|
|
@@ -107,6 +111,11 @@ async function loadPg() {
|
|
|
107
111
|
* await driver.disconnect();
|
|
108
112
|
* ```
|
|
109
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
|
+
}
|
|
110
119
|
var PostgresDriver = class {
|
|
111
120
|
config;
|
|
112
121
|
/**
|
|
@@ -169,6 +178,8 @@ var PostgresDriver = class {
|
|
|
169
178
|
* @see PostgresPoolConfig.nativeArrayColumns
|
|
170
179
|
*/
|
|
171
180
|
_nativeArrayColumns;
|
|
181
|
+
/** Column `dataTypeID`s per result row, filled by `query()` for `deserialize()`. */
|
|
182
|
+
rowColumnTypes = /* @__PURE__ */ new WeakMap();
|
|
172
183
|
/** Unique keys per table, resolved from `pg_index` for upsert conflict targets. */
|
|
173
184
|
_uniqueKeys = /* @__PURE__ */ new Map();
|
|
174
185
|
/**
|
|
@@ -343,6 +354,16 @@ var PostgresDriver = class {
|
|
|
343
354
|
return this._nativeArrayColumns.has(column);
|
|
344
355
|
}
|
|
345
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
|
+
/**
|
|
346
367
|
* Introspect the live schema for native-array columns so array values bind
|
|
347
368
|
* correctly with zero app configuration.
|
|
348
369
|
*
|
|
@@ -394,26 +415,11 @@ var PostgresDriver = class {
|
|
|
394
415
|
* @returns Deserialized JavaScript object
|
|
395
416
|
*/
|
|
396
417
|
deserialize(data) {
|
|
418
|
+
const columnTypes = this.rowColumnTypes.get(data);
|
|
419
|
+
if (!columnTypes) return data;
|
|
397
420
|
for (const [key, value] of Object.entries(data)) {
|
|
398
421
|
if (typeof value !== "string") continue;
|
|
399
|
-
|
|
400
|
-
data[key] = new Date(value);
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (value.charCodeAt(0) === 91 && value.charCodeAt(value.length - 1) === 93) {
|
|
404
|
-
const parts = value.slice(1, -1).split(",");
|
|
405
|
-
const nums = new Array(parts.length);
|
|
406
|
-
let isNumericVector = parts.length > 0;
|
|
407
|
-
for (let i = 0; i < parts.length; i++) {
|
|
408
|
-
const n = +(parts[i] ?? "");
|
|
409
|
-
if (!Number.isFinite(n)) {
|
|
410
|
-
isNumericVector = false;
|
|
411
|
-
break;
|
|
412
|
-
}
|
|
413
|
-
nums[i] = n;
|
|
414
|
-
}
|
|
415
|
-
if (isNumericVector) data[key] = nums;
|
|
416
|
-
}
|
|
422
|
+
data[key] = convertPostgresValue(value, columnTypes.get(key));
|
|
417
423
|
}
|
|
418
424
|
return data;
|
|
419
425
|
}
|
|
@@ -490,7 +496,15 @@ var PostgresDriver = class {
|
|
|
490
496
|
try {
|
|
491
497
|
return { modifiedCount: (await this.query(sql, params)).rowCount ?? 0 };
|
|
492
498
|
} catch (error) {
|
|
493
|
-
|
|
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
|
+
});
|
|
494
508
|
throw error;
|
|
495
509
|
}
|
|
496
510
|
}
|
|
@@ -667,7 +681,8 @@ var PostgresDriver = class {
|
|
|
667
681
|
const conflictColumns = options?.conflictColumns ?? Object.keys(filter);
|
|
668
682
|
if (conflictColumns.length === 0) throw new Error("Upsert requires conflictColumns option or filter with columns");
|
|
669
683
|
const quotedConflictColumns = conflictColumns.map((c) => this.dialect.quoteIdentifier(c)).join(", ");
|
|
670
|
-
const
|
|
684
|
+
const createdAtColumn = options?.createdAtColumn ?? this.modelDefaults.createdAtColumn;
|
|
685
|
+
const setClauses = columns.filter((col) => !conflictColumns.includes(col) && col !== createdAtColumn).map((col, i) => {
|
|
671
686
|
const valueIndex = columns.indexOf(col) + 1;
|
|
672
687
|
return `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(valueIndex)}`;
|
|
673
688
|
}).join(", ");
|
|
@@ -685,7 +700,7 @@ var PostgresDriver = class {
|
|
|
685
700
|
async findOneAndDelete(table, filter, _options) {
|
|
686
701
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
687
702
|
const { whereClause, whereParams } = this.buildWhereClause(filter, 1);
|
|
688
|
-
const sql =
|
|
703
|
+
const sql = `${this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter, whereClause)} RETURNING *`;
|
|
689
704
|
const result = await this.query(sql, whereParams);
|
|
690
705
|
return result.rows[0] ? result.rows[0] : null;
|
|
691
706
|
}
|
|
@@ -700,7 +715,7 @@ var PostgresDriver = class {
|
|
|
700
715
|
async delete(table, filter, _options) {
|
|
701
716
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
702
717
|
const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);
|
|
703
|
-
const sql =
|
|
718
|
+
const sql = this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter ?? {}, whereClause);
|
|
704
719
|
return (await this.query(sql, whereParams)).rowCount ?? 0;
|
|
705
720
|
}
|
|
706
721
|
/**
|
|
@@ -801,29 +816,32 @@ var PostgresDriver = class {
|
|
|
801
816
|
const ctx = { rollback(reason) {
|
|
802
817
|
throw new TransactionRollbackError(reason);
|
|
803
818
|
} };
|
|
804
|
-
if (
|
|
819
|
+
if (this.canJoinActiveTransaction()) return fn(ctx);
|
|
805
820
|
const tx = await this.beginTransaction(options);
|
|
806
|
-
|
|
821
|
+
transactionOwners.set(tx.context, this);
|
|
807
822
|
let commitStarted = false;
|
|
808
823
|
let committedCallbacks = [];
|
|
809
824
|
let result;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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;
|
|
822
843
|
}
|
|
823
|
-
|
|
824
|
-
} finally {
|
|
825
|
-
databaseTransactionContext.exit();
|
|
826
|
-
}
|
|
844
|
+
});
|
|
827
845
|
await flushAfterCommit(committedCallbacks);
|
|
828
846
|
return result;
|
|
829
847
|
}
|
|
@@ -856,13 +874,94 @@ var PostgresDriver = class {
|
|
|
856
874
|
/**
|
|
857
875
|
* Get the sync adapter for bulk denormalized updates.
|
|
858
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
|
+
*
|
|
859
882
|
* @returns Sync adapter instance
|
|
860
883
|
*/
|
|
861
884
|
syncAdapter() {
|
|
862
|
-
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
|
+
}
|
|
863
897
|
return this._syncAdapter;
|
|
864
898
|
}
|
|
865
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
|
+
/**
|
|
866
965
|
* Get the migration driver for schema operations.
|
|
867
966
|
*
|
|
868
967
|
* @returns Migration driver instance
|
|
@@ -879,6 +978,25 @@ var PostgresDriver = class {
|
|
|
879
978
|
return new PostgresSQLSerializer(this.dialect);
|
|
880
979
|
}
|
|
881
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
|
+
/**
|
|
882
1000
|
* Execute a raw SQL query.
|
|
883
1001
|
*
|
|
884
1002
|
* Automatically uses the transaction client if one is active.
|
|
@@ -887,15 +1005,15 @@ var PostgresDriver = class {
|
|
|
887
1005
|
* @param params - Query parameters
|
|
888
1006
|
* @returns Query result
|
|
889
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
|
+
}
|
|
890
1013
|
async query(sql, params = []) {
|
|
891
|
-
const txClient =
|
|
1014
|
+
const txClient = this.getOwnSession();
|
|
892
1015
|
const startTime = this.config.logging ? performance.now() : 0;
|
|
893
|
-
|
|
894
|
-
if (this.config.logging && params.length > 0) {
|
|
895
|
-
paramsString = JSON.stringify(params);
|
|
896
|
-
if (paramsString.length > 300) paramsString = paramsString.substring(0, 300) + "...";
|
|
897
|
-
paramsString = ` | Params: ${paramsString}`;
|
|
898
|
-
}
|
|
1016
|
+
const paramsString = this.config.logging && params.length > 0 ? ` | ${describeParams(params)}` : "";
|
|
899
1017
|
try {
|
|
900
1018
|
let result;
|
|
901
1019
|
if (this.config.logging) log.info({
|
|
@@ -903,8 +1021,8 @@ var PostgresDriver = class {
|
|
|
903
1021
|
action: "query.executing",
|
|
904
1022
|
message: `${sql}${paramsString}`,
|
|
905
1023
|
context: {
|
|
906
|
-
|
|
907
|
-
|
|
1024
|
+
sql,
|
|
1025
|
+
paramCount: params.length
|
|
908
1026
|
}
|
|
909
1027
|
});
|
|
910
1028
|
if (txClient) result = await txClient.query(sql, params);
|
|
@@ -916,12 +1034,13 @@ var PostgresDriver = class {
|
|
|
916
1034
|
action: "query.executed",
|
|
917
1035
|
message: `[${duration}ms] ${sql}${paramsString}`,
|
|
918
1036
|
context: {
|
|
919
|
-
params,
|
|
920
1037
|
sql,
|
|
1038
|
+
paramCount: params.length,
|
|
921
1039
|
duration
|
|
922
1040
|
}
|
|
923
1041
|
});
|
|
924
1042
|
}
|
|
1043
|
+
this.recordColumnTypes(result);
|
|
925
1044
|
return result;
|
|
926
1045
|
} catch (error) {
|
|
927
1046
|
if (this.config.logging) {
|
|
@@ -932,7 +1051,7 @@ var PostgresDriver = class {
|
|
|
932
1051
|
message: `[${duration}ms] ${sql}${paramsString}`,
|
|
933
1052
|
context: {
|
|
934
1053
|
sql,
|
|
935
|
-
params,
|
|
1054
|
+
paramCount: params.length,
|
|
936
1055
|
error: error instanceof Error ? error.message : String(error)
|
|
937
1056
|
}
|
|
938
1057
|
});
|
|
@@ -1084,14 +1203,28 @@ var PostgresDriver = class {
|
|
|
1084
1203
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
1085
1204
|
const { whereClause, whereParams } = this.buildWhereClause(filter, set.nextIndex);
|
|
1086
1205
|
params.push(...whereParams);
|
|
1087
|
-
|
|
1088
|
-
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(", ")}`;
|
|
1089
1207
|
return {
|
|
1090
|
-
sql,
|
|
1208
|
+
sql: limit === 1 && whereClause ? this.singleRowTarget(head, quotedTable, filter, whereClause) : `${head} ${whereClause}`,
|
|
1091
1209
|
params
|
|
1092
1210
|
};
|
|
1093
1211
|
}
|
|
1094
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
|
+
/**
|
|
1095
1228
|
* Create a new database.
|
|
1096
1229
|
*
|
|
1097
1230
|
* Note: This requires connecting to a system database (like 'postgres')
|