joist-orm 2.3.0-next.42 → 2.3.0-next.43
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/build/_virtual/_rolldown/runtime.cjs +23 -0
- package/build/_virtual/_rolldown/runtime.js +5 -0
- package/build/codegen.cjs +19 -0
- package/build/codegen.cjs.map +1 -0
- package/build/codegen.d.cts +1 -0
- package/build/codegen.d.mts +1 -0
- package/build/codegen.js +11 -28
- package/build/codegen.js.map +1 -1
- package/build/drivers/PostgresDriver.cjs +290 -0
- package/build/drivers/PostgresDriver.cjs.map +1 -0
- package/build/drivers/PostgresDriver.d.cts +77 -0
- package/build/drivers/PostgresDriver.d.cts.map +1 -0
- package/build/drivers/PostgresDriver.d.mts +77 -0
- package/build/drivers/PostgresDriver.d.mts.map +1 -0
- package/build/drivers/PostgresDriver.js +249 -325
- package/build/drivers/PostgresDriver.js.map +1 -1
- package/build/drivers/WireRowData.cjs +374 -0
- package/build/drivers/WireRowData.cjs.map +1 -0
- package/build/drivers/WireRowData.d.cts +111 -0
- package/build/drivers/WireRowData.d.cts.map +1 -0
- package/build/drivers/WireRowData.d.mts +111 -0
- package/build/drivers/WireRowData.d.mts.map +1 -0
- package/build/drivers/WireRowData.js +352 -412
- package/build/drivers/WireRowData.js.map +1 -1
- package/build/drivers/binaryParsers.cjs +422 -0
- package/build/drivers/binaryParsers.cjs.map +1 -0
- package/build/drivers/{binaryParsers.d.ts → binaryParsers.d.cts} +18 -14
- package/build/drivers/binaryParsers.d.cts.map +1 -0
- package/build/drivers/binaryParsers.d.mts +80 -0
- package/build/drivers/binaryParsers.d.mts.map +1 -0
- package/build/drivers/binaryParsers.js +315 -385
- package/build/drivers/binaryParsers.js.map +1 -1
- package/build/drivers/patchPgProtocol.cjs +255 -0
- package/build/drivers/patchPgProtocol.cjs.map +1 -0
- package/build/drivers/{patchPgProtocol.d.ts → patchPgProtocol.d.cts} +18 -14
- package/build/drivers/patchPgProtocol.d.cts.map +1 -0
- package/build/drivers/patchPgProtocol.d.mts +47 -0
- package/build/drivers/patchPgProtocol.d.mts.map +1 -0
- package/build/drivers/patchPgProtocol.js +211 -232
- package/build/drivers/patchPgProtocol.js.map +1 -1
- package/build/graphql-codegen-export.cjs +10 -0
- package/build/graphql-codegen-export.d.cts +1 -0
- package/build/graphql-codegen-export.d.mts +1 -0
- package/build/graphql-codegen-export.js +2 -19
- package/build/graphql-export.cjs +10 -0
- package/build/graphql-export.d.cts +1 -0
- package/build/graphql-export.d.mts +1 -0
- package/build/graphql-export.js +2 -19
- package/build/index.cjs +41 -0
- package/build/index.d.cts +3 -0
- package/build/index.d.mts +3 -0
- package/build/index.js +3 -27
- package/build/knex-export.cjs +10 -0
- package/build/knex-export.d.cts +1 -0
- package/build/knex-export.d.mts +1 -0
- package/build/knex-export.js +2 -19
- package/build/pg-export.cjs +13 -0
- package/build/pg-export.d.cts +4 -0
- package/build/pg-export.d.mts +4 -0
- package/build/pg-export.js +4 -20
- package/build/pg-migrate.cjs +22 -0
- package/build/pg-migrate.cjs.map +1 -0
- package/build/pg-migrate.d.cts +1 -0
- package/build/pg-migrate.d.mts +1 -0
- package/build/pg-migrate.js +13 -28
- package/build/pg-migrate.js.map +1 -1
- package/build/seed.cjs +49 -0
- package/build/seed.cjs.map +1 -0
- package/build/{seed.d.ts → seed.d.cts} +5 -1
- package/build/seed.d.cts.map +1 -0
- package/build/seed.d.mts +24 -0
- package/build/seed.d.mts.map +1 -0
- package/build/seed.js +39 -45
- package/build/seed.js.map +1 -1
- package/build/tests-export.cjs +10 -0
- package/build/tests-export.d.cts +1 -0
- package/build/tests-export.d.mts +1 -0
- package/build/tests-export.js +2 -19
- package/package.json +150 -28
- package/build/codegen.d.ts +0 -2
- package/build/drivers/PostgresDriver.d.ts +0 -73
- package/build/drivers/WireRowData.d.ts +0 -107
- package/build/graphql-codegen-export.d.ts +0 -1
- package/build/graphql-codegen-export.js.map +0 -1
- package/build/graphql-export.d.ts +0 -1
- package/build/graphql-export.js.map +0 -1
- package/build/index.d.ts +0 -3
- package/build/index.js.map +0 -1
- package/build/knex-export.d.ts +0 -1
- package/build/knex-export.js.map +0 -1
- package/build/pg-export.d.ts +0 -3
- package/build/pg-export.js.map +0 -1
- package/build/pg-migrate.d.ts +0 -2
- package/build/tests-export.d.ts +0 -1
- package/build/tests-export.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.__toESM = __toESM;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
let joist_codegen = require("joist-codegen");
|
|
4
|
+
//#region src/codegen.ts
|
|
5
|
+
if (typeof module !== "undefined" && require.main === module) (0, joist_codegen.joistCodegen)().then(() => (0, joist_codegen.maybeSetExitCode)()).catch((err) => {
|
|
6
|
+
console.error(err);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
Object.keys(joist_codegen).forEach(function(k) {
|
|
11
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function() {
|
|
14
|
+
return joist_codegen[k];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=codegen.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.cjs","names":["maybeSetExitCode"],"sources":["../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { joistCodegen, maybeSetExitCode } from \"joist-codegen\";\n\n// Re-export everything from joist-codegen\nexport * from \"joist-codegen\";\n\n// Duplicate the `require.main` check from joist-codegen\nif (typeof module !== \"undefined\" && require.main === module) {\n joistCodegen()\n .then(() => maybeSetExitCode())\n .catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n"],"mappings":";;;;AAQA,IAAI,OAAO,WAAW,eAAe,QAAQ,SAAS,QACpD,CAAA,GAAA,cAAA,aAAA,CAAa,CAAC,CACX,YAAA,GAAWA,cAAAA,iBAAAA,CAAiB,CAAC,CAAC,CAC9B,OAAO,QAAQ;CACd,QAAQ,MAAM,GAAG;CACjB,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "joist-codegen";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "joist-codegen";
|
package/build/codegen.js
CHANGED
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}));
|
|
14
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
15
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
16
|
-
};
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
const joist_codegen_1 = require("joist-codegen");
|
|
19
|
-
// Re-export everything from joist-codegen
|
|
20
|
-
__exportStar(require("joist-codegen"), exports);
|
|
21
|
-
// Duplicate the `require.main` check from joist-codegen
|
|
22
|
-
if (require.main === module) {
|
|
23
|
-
(0, joist_codegen_1.joistCodegen)()
|
|
24
|
-
.then(() => (0, joist_codegen_1.maybeSetExitCode)())
|
|
25
|
-
.catch((err) => {
|
|
26
|
-
console.error(err);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
});
|
|
29
|
-
}
|
|
2
|
+
import { __require } from "./_virtual/_rolldown/runtime.js";
|
|
3
|
+
import { joistCodegen, maybeSetExitCode } from "joist-codegen";
|
|
4
|
+
export * from "joist-codegen";
|
|
5
|
+
//#region src/codegen.ts
|
|
6
|
+
if (typeof module !== "undefined" && __require.main === module) joistCodegen().then(() => maybeSetExitCode()).catch((err) => {
|
|
7
|
+
console.error(err);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
10
|
+
//#endregion
|
|
11
|
+
export {};
|
|
12
|
+
|
|
30
13
|
//# sourceMappingURL=codegen.js.map
|
package/build/codegen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.js","
|
|
1
|
+
{"version":3,"file":"codegen.js","names":[],"sources":["../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { joistCodegen, maybeSetExitCode } from \"joist-codegen\";\n\n// Re-export everything from joist-codegen\nexport * from \"joist-codegen\";\n\n// Duplicate the `require.main` check from joist-codegen\nif (typeof module !== \"undefined\" && require.main === module) {\n joistCodegen()\n .then(() => maybeSetExitCode())\n .catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;AAQA,IAAI,OAAO,WAAW,eAAA,UAAuB,SAAS,QACpD,aAAa,CAAC,CACX,WAAW,iBAAiB,CAAC,CAAC,CAC9B,OAAO,QAAQ;CACd,QAAQ,MAAM,GAAG;CACjB,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_drivers_binaryParsers = require("./binaryParsers.cjs");
|
|
4
|
+
const require_drivers_patchPgProtocol = require("./patchPgProtocol.cjs");
|
|
5
|
+
const require_drivers_WireRowData = require("./WireRowData.cjs");
|
|
6
|
+
let joist_core = require("joist-core");
|
|
7
|
+
let pg = require("pg");
|
|
8
|
+
pg = require_runtime.__toESM(pg, 1);
|
|
9
|
+
let pg_types = require("pg-types");
|
|
10
|
+
let postgres_array = require("postgres-array");
|
|
11
|
+
postgres_array = require_runtime.__toESM(postgres_array, 1);
|
|
12
|
+
//#region src/drivers/PostgresDriver.ts
|
|
13
|
+
/**
|
|
14
|
+
* Implements the `Driver` interface for Postgres.
|
|
15
|
+
*
|
|
16
|
+
* This is the canonical driver implementation and leverages several aspects of
|
|
17
|
+
* Postgres for the best performance, i.e.:
|
|
18
|
+
*
|
|
19
|
+
* - Deferred foreign key constraints are used to allow bulk inserting/updating
|
|
20
|
+
* all entities without any topographic sorting/ordering issues.
|
|
21
|
+
*
|
|
22
|
+
* - Sequences are used to bulk-assign + then bulk-insert all entities, to again
|
|
23
|
+
* avoid issues with ordering issues (cannot bulk insert A w/o knowing the id of B).
|
|
24
|
+
*
|
|
25
|
+
* - We use a pg-specific bulk update syntax.
|
|
26
|
+
*/
|
|
27
|
+
var PostgresDriver = class {
|
|
28
|
+
pool;
|
|
29
|
+
#idAssigner;
|
|
30
|
+
#preloadPlugin;
|
|
31
|
+
#onQuery;
|
|
32
|
+
lazyRows;
|
|
33
|
+
/** Defined only when `lazyRows` is enabled; its presence is the capability signal the EM checks. */
|
|
34
|
+
executeFindRowData;
|
|
35
|
+
#databaseBinaryParsers = void 0;
|
|
36
|
+
constructor(pool, opts) {
|
|
37
|
+
this.pool = pool;
|
|
38
|
+
this.#idAssigner = opts?.idAssigner ?? new joist_core.SequenceIdAssigner(async (sql) => {
|
|
39
|
+
this.#onQuery?.(sql);
|
|
40
|
+
return (await pool.query(sql)).rows;
|
|
41
|
+
});
|
|
42
|
+
this.#preloadPlugin = opts?.preloadPlugin;
|
|
43
|
+
this.#onQuery = opts?.onQuery;
|
|
44
|
+
this.lazyRows = (opts?.lazyRows ?? false) && require_drivers_patchPgProtocol.ensureLazyDataRows();
|
|
45
|
+
if (opts?.lazyRows && !this.lazyRows) console.warn("joist-orm: lazyRows was requested, but patching pg-protocol failed; using classic rows.");
|
|
46
|
+
this.executeFindRowData = this.lazyRows ? (em, parsed, settings) => this.#executeFindRowData(em, parsed, settings) : void 0;
|
|
47
|
+
setupLatestPgTypes((0, joist_core.getRuntimeConfig)().temporal);
|
|
48
|
+
}
|
|
49
|
+
async executeFind(em, parsed, settings) {
|
|
50
|
+
const { sql, bindings } = (0, joist_core.buildRawQuery)(parsed, {
|
|
51
|
+
limit: em.entityLimit,
|
|
52
|
+
...settings
|
|
53
|
+
});
|
|
54
|
+
return this.executeQuery(em, sql, bindings);
|
|
55
|
+
}
|
|
56
|
+
async #executeFindRowData(em, parsed, settings) {
|
|
57
|
+
await (this.#databaseBinaryParsers ??= require_drivers_binaryParsers.registerDatabaseBinaryParsers(this.pool).catch((err) => {
|
|
58
|
+
this.#databaseBinaryParsers = void 0;
|
|
59
|
+
throw err;
|
|
60
|
+
}));
|
|
61
|
+
const { sql, bindings } = (0, joist_core.buildRawQuery)(parsed, {
|
|
62
|
+
limit: em.entityLimit,
|
|
63
|
+
...settings
|
|
64
|
+
});
|
|
65
|
+
const pgSql = toPgParams(sql);
|
|
66
|
+
this.#onQuery?.(pgSql);
|
|
67
|
+
const txnClient = em.txn;
|
|
68
|
+
const client = txnClient ?? await this.pool.connect();
|
|
69
|
+
try {
|
|
70
|
+
if (!require_drivers_WireRowData.isRowDataCapableClient(client)) {
|
|
71
|
+
warnUnsupportedClientOnce();
|
|
72
|
+
return new joist_core.PojoRowData((await client.query(pgSql, bindings)).rows);
|
|
73
|
+
}
|
|
74
|
+
return await require_drivers_WireRowData.executeRowDataQuery(client, pgSql, bindings);
|
|
75
|
+
} finally {
|
|
76
|
+
if (!txnClient) client.release();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async executeQuery(em, sql, bindings) {
|
|
80
|
+
const pgSql = toPgParams(sql);
|
|
81
|
+
this.#onQuery?.(pgSql);
|
|
82
|
+
return this.getMaybeInTxnClient(em).query(pgSql, bindings).then((result) => result.rows);
|
|
83
|
+
}
|
|
84
|
+
async transaction(em, fn) {
|
|
85
|
+
if (em.txn) return fn(em.txn);
|
|
86
|
+
const client = await this.pool.connect();
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
await (0, joist_core.driverBeforeBegin)(em, client);
|
|
90
|
+
this.#onQuery?.("BEGIN;");
|
|
91
|
+
await client.query("BEGIN");
|
|
92
|
+
em.txn = client;
|
|
93
|
+
try {
|
|
94
|
+
await (0, joist_core.driverAfterBegin)(em, client);
|
|
95
|
+
result = await fn(client);
|
|
96
|
+
await (0, joist_core.driverBeforeCommit)(em, client);
|
|
97
|
+
this.#onQuery?.("COMMIT;");
|
|
98
|
+
await client.query("COMMIT");
|
|
99
|
+
} catch (e) {
|
|
100
|
+
await client.query("ROLLBACK");
|
|
101
|
+
throw e;
|
|
102
|
+
} finally {
|
|
103
|
+
em.txn = void 0;
|
|
104
|
+
}
|
|
105
|
+
await (0, joist_core.driverAfterCommit)(em, client);
|
|
106
|
+
} finally {
|
|
107
|
+
client.release();
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
async assignNewIds(_, todos) {
|
|
112
|
+
return this.#idAssigner.assignNewIds(todos);
|
|
113
|
+
}
|
|
114
|
+
async flush(em, entityTodos, joinRows) {
|
|
115
|
+
const client = em.txn ?? (0, joist_core.fail)("Expected EntityManager.txn to be set");
|
|
116
|
+
await this.#idAssigner.assignNewIds(entityTodos);
|
|
117
|
+
const ops = (0, joist_core.generateOps)(entityTodos);
|
|
118
|
+
const onQuery = this.#onQuery;
|
|
119
|
+
await Promise.all([
|
|
120
|
+
...ops.inserts.map((op) => batchInsert(client, op, onQuery)),
|
|
121
|
+
...ops.updates.map((op) => batchUpdate(client, op, onQuery)),
|
|
122
|
+
...ops.deletes.map((op) => batchDelete(client, op, onQuery)),
|
|
123
|
+
...Object.entries(joinRows).flatMap(([joinTableName, todo]) => {
|
|
124
|
+
return [m2mBatchInsert(client, joinTableName, todo, onQuery), m2mBatchDelete(client, joinTableName, todo, onQuery)];
|
|
125
|
+
})
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
get defaultPlugins() {
|
|
129
|
+
return { preloadPlugin: this.#preloadPlugin };
|
|
130
|
+
}
|
|
131
|
+
getMaybeInTxnClient(em) {
|
|
132
|
+
return em.txn || this.pool;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
async function batchInsert(client, op, onQuery) {
|
|
136
|
+
const { tableName, columns, columnValues } = op;
|
|
137
|
+
const [cte, bindings] = buildUnnestCte("data", columns, columnValues);
|
|
138
|
+
const pgSql = toPgParams((0, joist_core.cleanSql)(`
|
|
139
|
+
${cte}
|
|
140
|
+
INSERT INTO ${(0, joist_core.kq)(tableName)} (${columns.map((c) => (0, joist_core.kq)(c.columnName)).join(", ")})
|
|
141
|
+
SELECT * FROM data
|
|
142
|
+
`));
|
|
143
|
+
onQuery?.(pgSql);
|
|
144
|
+
return client.query(pgSql, bindings);
|
|
145
|
+
}
|
|
146
|
+
async function batchUpdate(client, op, onQuery) {
|
|
147
|
+
const { tableName, columns, columnValues, updatedAt } = op;
|
|
148
|
+
const [cte, bindings] = buildUnnestCte("data", columns, columnValues);
|
|
149
|
+
const truncateToMills = !(0, joist_core.getRuntimeConfig)().temporal;
|
|
150
|
+
const maybeUpdatedAt = updatedAt && truncateToMills ? ` AND date_trunc('milliseconds', ${(0, joist_core.kqDot)(tableName, updatedAt)}) = data.__original_updated_at` : updatedAt ? ` AND ${(0, joist_core.kqDot)(tableName, updatedAt)} = data.__original_updated_at` : "";
|
|
151
|
+
const sql = `
|
|
152
|
+
${cte}
|
|
153
|
+
UPDATE ${(0, joist_core.kq)(tableName)}
|
|
154
|
+
SET ${columns.filter((c) => c.columnName !== "id" && c.columnName !== "__original_updated_at").map((c) => `${(0, joist_core.kq)(c.columnName)} = data.${(0, joist_core.kq)(c.columnName)}`).join(", ")}
|
|
155
|
+
FROM data
|
|
156
|
+
WHERE ${(0, joist_core.kq)(tableName)}.id = data.id ${maybeUpdatedAt}
|
|
157
|
+
RETURNING ${(0, joist_core.kq)(tableName)}.id
|
|
158
|
+
`;
|
|
159
|
+
const pgSql = toPgParams((0, joist_core.cleanSql)(sql));
|
|
160
|
+
onQuery?.(pgSql);
|
|
161
|
+
const results = (await client.query(pgSql, bindings)).rows;
|
|
162
|
+
const ids = columnValues[0];
|
|
163
|
+
if (results.length !== ids.length) {
|
|
164
|
+
const updated = new Set(results.map((r) => r.id));
|
|
165
|
+
const missing = ids.filter((id) => !updated.has(id));
|
|
166
|
+
throw new Error(`Oplock failure for ${tableName} rows ${missing.join(", ")}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function batchDelete(client, op, onQuery) {
|
|
170
|
+
const { tableName, ids } = op;
|
|
171
|
+
const pgSql = toPgParams(`DELETE FROM ${(0, joist_core.kq)(tableName)} WHERE id = ANY(?)`);
|
|
172
|
+
onQuery?.(pgSql);
|
|
173
|
+
await client.query(pgSql, [ids]);
|
|
174
|
+
}
|
|
175
|
+
/** Creates a CTE named `tableName` that bulk-injects the `columnValues` into a SQL query. */
|
|
176
|
+
function buildUnnestCte(tableName, columns, columnValues) {
|
|
177
|
+
(0, joist_core.ensureRectangularArraySizes)(columns, columnValues);
|
|
178
|
+
return [`WITH ${tableName} AS (SELECT ${columns.map((c) => {
|
|
179
|
+
if (c.dbType.endsWith("[]")) {
|
|
180
|
+
if (c.isNullableArray) return `unnest_arrays(?::${c.dbType}[], true) as ${(0, joist_core.kq)(c.columnName)}`;
|
|
181
|
+
else return `unnest_arrays(?::${c.dbType}[]) as ${(0, joist_core.kq)(c.columnName)}`;
|
|
182
|
+
} else return `unnest(?::${c.dbType}[]) as ${(0, joist_core.kq)(c.columnName)}`;
|
|
183
|
+
}).join(", ")})`, columnValues];
|
|
184
|
+
}
|
|
185
|
+
async function m2mBatchInsert(client, joinTableName, todo, onQuery) {
|
|
186
|
+
const { m2m, newRows } = todo;
|
|
187
|
+
if (newRows.length === 0) return;
|
|
188
|
+
const col1Values = newRows.map((row) => todo.dbValue(row, m2m.columnName));
|
|
189
|
+
const col2Values = newRows.map((row) => todo.dbValue(row, m2m.otherColumnName));
|
|
190
|
+
if (m2m.hasJoinTableId) {
|
|
191
|
+
const pgSql = toPgParams((0, joist_core.cleanSql)(`
|
|
192
|
+
WITH data AS (SELECT unnest(?::int[]) as ${(0, joist_core.kq)(m2m.columnName)}, unnest(?::int[]) as ${(0, joist_core.kq)(m2m.otherColumnName)})
|
|
193
|
+
INSERT INTO ${(0, joist_core.kq)(joinTableName)} (${(0, joist_core.kq)(m2m.columnName)}, ${(0, joist_core.kq)(m2m.otherColumnName)})
|
|
194
|
+
SELECT * FROM data
|
|
195
|
+
ON CONFLICT (${(0, joist_core.kq)(m2m.columnName)}, ${(0, joist_core.kq)(m2m.otherColumnName)}) DO UPDATE SET id = ${(0, joist_core.kq)(joinTableName)}.id
|
|
196
|
+
RETURNING id;
|
|
197
|
+
`));
|
|
198
|
+
onQuery?.(pgSql);
|
|
199
|
+
const { rows } = await client.query(pgSql, [col1Values, col2Values]);
|
|
200
|
+
for (let i = 0; i < rows.length; i++) {
|
|
201
|
+
newRows[i].id = rows[i].id;
|
|
202
|
+
newRows[i].op = joist_core.JoinRowOperation.Flushed;
|
|
203
|
+
newRows[i].persisted = true;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
const pgSql = toPgParams((0, joist_core.cleanSql)(`
|
|
207
|
+
WITH data AS (SELECT unnest(?::int[]) as ${(0, joist_core.kq)(m2m.columnName)}, unnest(?::int[]) as ${(0, joist_core.kq)(m2m.otherColumnName)})
|
|
208
|
+
INSERT INTO ${(0, joist_core.kq)(joinTableName)} (${(0, joist_core.kq)(m2m.columnName)}, ${(0, joist_core.kq)(m2m.otherColumnName)})
|
|
209
|
+
SELECT * FROM data
|
|
210
|
+
ON CONFLICT (${(0, joist_core.kq)(m2m.columnName)}, ${(0, joist_core.kq)(m2m.otherColumnName)}) DO NOTHING;
|
|
211
|
+
`));
|
|
212
|
+
onQuery?.(pgSql);
|
|
213
|
+
await client.query(pgSql, [col1Values, col2Values]);
|
|
214
|
+
for (const row of newRows) {
|
|
215
|
+
row.op = joist_core.JoinRowOperation.Flushed;
|
|
216
|
+
row.persisted = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function m2mBatchDelete(client, joinTableName, todo, onQuery) {
|
|
221
|
+
const { m2m, deletedRows } = todo;
|
|
222
|
+
if (deletedRows.length === 0) return;
|
|
223
|
+
const [haveIds, noIds] = (0, joist_core.partition)(deletedRows, (r) => {
|
|
224
|
+
return r.id !== void 0 && r.id !== -1;
|
|
225
|
+
});
|
|
226
|
+
if (haveIds.length > 0) {
|
|
227
|
+
const pgSql = toPgParams(`DELETE FROM ${(0, joist_core.kq)(joinTableName)} WHERE id = ANY(?)`);
|
|
228
|
+
onQuery?.(pgSql);
|
|
229
|
+
await client.query(pgSql, [haveIds.map((r) => r.id)]);
|
|
230
|
+
}
|
|
231
|
+
if (noIds.length > 0) {
|
|
232
|
+
const data = noIds.filter((row) => !todo.isNew(row, m2m.columnName) && !todo.isNew(row, m2m.otherColumnName)).map((row) => [todo.dbValue(row, m2m.columnName), todo.dbValue(row, m2m.otherColumnName)]);
|
|
233
|
+
if (data.length > 0) {
|
|
234
|
+
const pgSql = toPgParams(`
|
|
235
|
+
DELETE FROM ${(0, joist_core.kq)(joinTableName)}
|
|
236
|
+
WHERE (${(0, joist_core.kq)(m2m.columnName)}, ${(0, joist_core.kq)(m2m.otherColumnName)}) IN (
|
|
237
|
+
SELECT (data->>0)::int, (data->>1)::int FROM jsonb_array_elements(?) data
|
|
238
|
+
)`);
|
|
239
|
+
onQuery?.(pgSql);
|
|
240
|
+
await client.query(pgSql, [JSON.stringify(data)]);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
deletedRows.forEach((row) => {
|
|
244
|
+
row.id = void 0;
|
|
245
|
+
row.persisted = false;
|
|
246
|
+
row.op = joist_core.JoinRowOperation.Flushed;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Configures the `pg` driver with the best type parsers.
|
|
251
|
+
*
|
|
252
|
+
* In particular, pg's default `TIMESTAMPTZ` parser is very inefficient, and even just
|
|
253
|
+
* pulling in the latest `pg-types` and installing the fixed parser makes a noticable
|
|
254
|
+
* difference.
|
|
255
|
+
*/
|
|
256
|
+
function setupLatestPgTypes(temporal) {
|
|
257
|
+
if (temporal) {
|
|
258
|
+
const noop = (s) => s;
|
|
259
|
+
const noopArray = (s) => postgres_array.default.parse(s, noop);
|
|
260
|
+
const { TIMESTAMP, TIMESTAMPTZ, DATE } = pg.default.types.builtins;
|
|
261
|
+
pg.default.types.setTypeParser(DATE, noop);
|
|
262
|
+
pg.default.types.setTypeParser(TIMESTAMP, noop);
|
|
263
|
+
pg.default.types.setTypeParser(TIMESTAMPTZ, noop);
|
|
264
|
+
pg.default.types.setTypeParser(1182, noopArray);
|
|
265
|
+
pg.default.types.setTypeParser(1115, noopArray);
|
|
266
|
+
pg.default.types.setTypeParser(1185, noopArray);
|
|
267
|
+
require_drivers_binaryParsers.registerTemporalBinaryParsers();
|
|
268
|
+
} else pg.default.types.setTypeParser(pg.default.types.builtins.TIMESTAMPTZ, (0, pg_types.getTypeParser)(pg_types.builtins.TIMESTAMPTZ));
|
|
269
|
+
}
|
|
270
|
+
const questionMarks = /(?<!@)\?/g;
|
|
271
|
+
/**
|
|
272
|
+
* Converts our knex-style `?` placeholders to pg-native `$1, $2, ...` numbered parameters.
|
|
273
|
+
*
|
|
274
|
+
* We skipping postgres operators like `@?`. */
|
|
275
|
+
function toPgParams(sql) {
|
|
276
|
+
let i = 0;
|
|
277
|
+
return sql.replace(questionMarks, () => `$${++i}`);
|
|
278
|
+
}
|
|
279
|
+
let warnedUnsupportedClient = false;
|
|
280
|
+
/** Warns once when lazyRows degrades to classic rows for an unsupported client, i.e. pg-native. */
|
|
281
|
+
function warnUnsupportedClientOnce() {
|
|
282
|
+
if (warnedUnsupportedClient) return;
|
|
283
|
+
warnedUnsupportedClient = true;
|
|
284
|
+
console.warn("joist-orm: lazyRows is enabled, but this client does not support it; using classic rows.");
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
exports.PostgresDriver = PostgresDriver;
|
|
288
|
+
exports.setupLatestPgTypes = setupLatestPgTypes;
|
|
289
|
+
|
|
290
|
+
//# sourceMappingURL=PostgresDriver.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresDriver.cjs","names":["#idAssigner","#preloadPlugin","#onQuery","SequenceIdAssigner","ensureLazyDataRows","#executeFindRowData","getRuntimeConfig","buildRawQuery","#databaseBinaryParsers","registerDatabaseBinaryParsers","isRowDataCapableClient","PojoRowData","executeRowDataQuery","driverBeforeBegin","driverAfterBegin","driverBeforeCommit","driverAfterCommit","fail","generateOps","cleanSql","kq","kqDot","JoinRowOperation","partition","array","getTypeParser","builtins"],"sources":["../../src/drivers/PostgresDriver.ts"],"sourcesContent":["import {\n type DeleteOp,\n type Driver,\n type EntityManager,\n type IdAssigner,\n type InsertOp,\n JoinRowOperation,\n type JoinRowTodo,\n type OpColumn,\n type ParsedFindQuery,\n PojoRowData,\n type PreloadPlugin,\n type RowData,\n type RuntimeConfig,\n SequenceIdAssigner,\n type Todo,\n type UpdateOp,\n buildRawQuery,\n cleanSql,\n driverAfterBegin,\n driverAfterCommit,\n driverBeforeBegin,\n driverBeforeCommit,\n ensureRectangularArraySizes,\n fail,\n generateOps,\n getRuntimeConfig,\n kq,\n kqDot,\n partition,\n} from \"joist-core\";\nimport pg from \"pg\";\nimport { builtins, getTypeParser } from \"pg-types\";\nimport array from \"postgres-array\";\n\nimport { registerDatabaseBinaryParsers, registerTemporalBinaryParsers } from \"./binaryParsers.ts\";\nimport { ensureLazyDataRows } from \"./patchPgProtocol.ts\";\nimport { executeRowDataQuery, isRowDataCapableClient } from \"./WireRowData.ts\";\n\nexport interface PostgresDriverOpts {\n idAssigner?: IdAssigner;\n /** Sets a default `PreloadPlugin` for any `EntityManager` that uses this driver. */\n preloadPlugin?: PreloadPlugin;\n /** Called after each query is executed, useful for testing/debugging. */\n onQuery?: (sql: string) => void;\n /**\n * Experimental: keeps entity find results as raw wire bytes and decodes each row/column cell\n * lazily on field access, instead of eagerly materializing POJO rows.\n *\n * Applies to unpaginated `em.find`, `em.load`, and o2m/o2o/recursive relation loads against\n * the pure-JS pg client. Other loaders deliberately stay classic: paginated finds (small\n * pages, measured neutral), m2m join-table rows (narrow + fully read, where materialized rows\n * measured faster, and `JoinRows` owns/mutates them), lazy columns, and id/count loaders.\n *\n * Lazy queries also request *binary* result format (prototype), decoding int/bool/float/text\n * cells — and, via the fast paths `setupLatestPgTypes` registers, date/timestamptz cells —\n * wire-bytes -> value with no intermediate strings; numeric/exotic cells render to pg's\n * canonical text and reuse the active text parsers for parity. `JOIST_LAZY_BINARY=0`\n * reverts lazy queries to text-format results. It\n * uses classic rows when the choice is knowable up-front (patching pg-protocol failed, or the\n * client is unsupported, i.e. pg-native — both warn once). If a connection turns out\n * mid-query to use an unpatched pg-protocol copy (a duplicate `pg` install), the query fails\n * with a descriptive error — a misconfiguration CI builds/smoketests will catch immediately.\n * Note deferred decoding also defers custom-parser errors from `await em.find` to first field\n * access. See JS-ROW-STORE-DESIGN.md.\n */\n lazyRows?: boolean;\n}\n\n/**\n * Provides a callback for tests/debugging.\n *\n * We used to get this for-free from knex, but node-pg does not have a `query` event.\n */\ntype OnQuery = ((sql: string) => void) | undefined;\n\n/**\n * Implements the `Driver` interface for Postgres.\n *\n * This is the canonical driver implementation and leverages several aspects of\n * Postgres for the best performance, i.e.:\n *\n * - Deferred foreign key constraints are used to allow bulk inserting/updating\n * all entities without any topographic sorting/ordering issues.\n *\n * - Sequences are used to bulk-assign + then bulk-insert all entities, to again\n * avoid issues with ordering issues (cannot bulk insert A w/o knowing the id of B).\n *\n * - We use a pg-specific bulk update syntax.\n */\nexport class PostgresDriver implements Driver<pg.PoolClient> {\n readonly #idAssigner: IdAssigner;\n readonly #preloadPlugin: PreloadPlugin | undefined;\n readonly #onQuery: OnQuery;\n readonly lazyRows: boolean;\n /** Defined only when `lazyRows` is enabled; its presence is the capability signal the EM checks. */\n readonly executeFindRowData: Driver[\"executeFindRowData\"];\n #databaseBinaryParsers: Promise<void> | undefined = undefined;\n\n constructor(\n readonly pool: pg.Pool,\n opts?: PostgresDriverOpts,\n ) {\n this.#idAssigner =\n opts?.idAssigner ??\n new SequenceIdAssigner(async (sql: string) => {\n this.#onQuery?.(sql);\n return (await pool.query(sql)).rows;\n });\n this.#preloadPlugin = opts?.preloadPlugin;\n this.#onQuery = opts?.onQuery;\n // Lazy rows require pg-protocol to emit lazy DataRows, which we patch in at runtime; if the\n // patch cannot be applied/verified (i.e. future pg-protocol internals changed), stay classic\n this.lazyRows = (opts?.lazyRows ?? false) && ensureLazyDataRows();\n if (opts?.lazyRows && !this.lazyRows) {\n console.warn(\"joist-orm: lazyRows was requested, but patching pg-protocol failed; using classic rows.\");\n }\n this.executeFindRowData = this.lazyRows\n ? (em, parsed, settings) => this.#executeFindRowData(em, parsed, settings)\n : undefined;\n setupLatestPgTypes(getRuntimeConfig().temporal);\n }\n\n async executeFind(\n em: EntityManager,\n parsed: ParsedFindQuery,\n settings: { limit?: number; offset?: number },\n ): Promise<any[]> {\n const { sql, bindings } = buildRawQuery(parsed, { limit: em.entityLimit, ...settings });\n return this.executeQuery(em, sql, bindings);\n }\n\n async #executeFindRowData(\n em: EntityManager,\n parsed: ParsedFindQuery,\n settings: { limit?: number; offset?: number },\n ): Promise<RowData> {\n // Auto-register binary parsers for the db's dynamic-oid text-likes (native enums, citext)\n // once, before our first lazy query; on failure, reset so the next query retries\n await (this.#databaseBinaryParsers ??= registerDatabaseBinaryParsers(this.pool).catch((err) => {\n this.#databaseBinaryParsers = undefined;\n throw err;\n }));\n const { sql, bindings } = buildRawQuery(parsed, { limit: em.entityLimit, ...settings });\n const pgSql = toPgParams(sql);\n this.#onQuery?.(pgSql);\n // Centralize client checkout here (pg-pool rejects Submittables in pool.query), and decide\n // classic-vs-lazy *before* submitting anything, i.e. unsupported clients like pg-native fall\n // back to classic rows rather than failing mid-query\n const txnClient = em.txn as pg.PoolClient | undefined;\n const client = txnClient ?? (await this.pool.connect());\n try {\n if (!isRowDataCapableClient(client)) {\n warnUnsupportedClientOnce();\n return new PojoRowData((await (client as pg.PoolClient).query(pgSql, bindings as any[])).rows);\n }\n return await executeRowDataQuery(client, pgSql, bindings);\n } finally {\n if (!txnClient) client.release();\n }\n }\n\n async executeQuery(em: EntityManager, sql: string, bindings: readonly any[]): Promise<any[]> {\n const pgSql = toPgParams(sql);\n this.#onQuery?.(pgSql);\n const client = this.getMaybeInTxnClient(em);\n return client.query(pgSql, bindings as any[]).then((result) => result.rows);\n }\n\n async transaction<T>(em: EntityManager, fn: (txn: pg.PoolClient) => Promise<T>): Promise<T> {\n if (em.txn) {\n return fn(em.txn as pg.PoolClient);\n }\n const client = await this.pool.connect();\n let result: T;\n try {\n await driverBeforeBegin(em, client);\n this.#onQuery?.(\"BEGIN;\");\n await client.query(\"BEGIN\");\n em.txn = client;\n try {\n await driverAfterBegin(em, client);\n result = await fn(client);\n await driverBeforeCommit(em, client);\n this.#onQuery?.(\"COMMIT;\");\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n em.txn = undefined;\n }\n await driverAfterCommit(em, client);\n } finally {\n client.release();\n }\n return result;\n }\n\n async assignNewIds(_: EntityManager, todos: Record<string, Todo>): Promise<void> {\n return this.#idAssigner.assignNewIds(todos);\n }\n\n async flush(\n em: EntityManager,\n entityTodos: Record<string, Todo>,\n joinRows: Record<string, JoinRowTodo>,\n ): Promise<void> {\n const client = (em.txn ?? fail(\"Expected EntityManager.txn to be set\")) as pg.PoolClient;\n await this.#idAssigner.assignNewIds(entityTodos);\n const ops = generateOps(entityTodos);\n const onQuery = this.#onQuery;\n // Do INSERTs+UPDATEs first so that we avoid DELETE cascades invalidating oplocks\n // See https://github.com/joist-orm/joist-orm/issues/591\n await Promise.all([\n ...ops.inserts.map((op) => batchInsert(client, op, onQuery)),\n ...ops.updates.map((op) => batchUpdate(client, op, onQuery)),\n ...ops.deletes.map((op) => batchDelete(client, op, onQuery)),\n ...Object.entries(joinRows).flatMap(([joinTableName, todo]) => {\n return [\n m2mBatchInsert(client, joinTableName, todo, onQuery),\n m2mBatchDelete(client, joinTableName, todo, onQuery),\n ];\n }),\n ]);\n }\n\n get defaultPlugins() {\n return { preloadPlugin: this.#preloadPlugin };\n }\n\n private getMaybeInTxnClient(em: EntityManager): pg.PoolClient | pg.Pool {\n return (em.txn as pg.PoolClient) || this.pool;\n }\n}\n\nasync function batchInsert(client: pg.PoolClient, op: InsertOp, onQuery: OnQuery): Promise<unknown> {\n const { tableName, columns, columnValues } = op;\n const [cte, bindings] = buildUnnestCte(\"data\", columns, columnValues);\n const sql = cleanSql(`\n ${cte}\n INSERT INTO ${kq(tableName)} (${columns.map((c) => kq(c.columnName)).join(\", \")})\n SELECT * FROM data\n `);\n const pgSql = toPgParams(sql);\n onQuery?.(pgSql);\n return client.query(pgSql, bindings);\n}\n\nasync function batchUpdate(client: pg.PoolClient, op: UpdateOp, onQuery: OnQuery): Promise<void> {\n const { tableName, columns, columnValues, updatedAt } = op;\n\n const [cte, bindings] = buildUnnestCte(\"data\", columns, columnValues);\n\n // JS Dates only have millisecond-level precision, so we may have dropped/lost accuracy when\n // reading Postgres's microsecond-level `timestamptz` values; using `date_trunc` \"downgrades\"\n // the pg data to match what we have in the JS Date.\n //\n // ...but Temporal's types don't have this flaw.\n const truncateToMills = !getRuntimeConfig().temporal;\n const maybeUpdatedAt =\n updatedAt && truncateToMills\n ? ` AND date_trunc('milliseconds', ${kqDot(tableName, updatedAt)}) = data.__original_updated_at`\n : updatedAt\n ? ` AND ${kqDot(tableName, updatedAt)} = data.__original_updated_at`\n : \"\";\n\n const sql = `\n ${cte}\n UPDATE ${kq(tableName)}\n SET ${columns\n .filter((c) => c.columnName !== \"id\" && c.columnName !== \"__original_updated_at\")\n .map((c) => `${kq(c.columnName)} = data.${kq(c.columnName)}`)\n .join(\", \")}\n FROM data\n WHERE ${kq(tableName)}.id = data.id ${maybeUpdatedAt}\n RETURNING ${kq(tableName)}.id\n `;\n\n const pgSql = toPgParams(cleanSql(sql));\n onQuery?.(pgSql);\n const result = await client.query(pgSql, bindings);\n const results = result.rows;\n\n const ids = columnValues[0]; // assume id is the 1st column\n if (results.length !== ids.length) {\n const updated = new Set(results.map((r: any) => r.id));\n const missing = ids.filter((id) => !updated.has(id));\n throw new Error(`Oplock failure for ${tableName} rows ${missing.join(\", \")}`);\n }\n}\n\nasync function batchDelete(client: pg.PoolClient, op: DeleteOp, onQuery: OnQuery): Promise<void> {\n const { tableName, ids } = op;\n const pgSql = toPgParams(`DELETE FROM ${kq(tableName)} WHERE id = ANY(?)`);\n onQuery?.(pgSql);\n await client.query(pgSql, [ids]);\n}\n\n/** Creates a CTE named `tableName` that bulk-injects the `columnValues` into a SQL query. */\nfunction buildUnnestCte(tableName: string, columns: OpColumn[], columnValues: any[][]): [string, any[][]] {\n ensureRectangularArraySizes(columns, columnValues);\n const selects = columns.map((c) => {\n if (c.dbType.endsWith(\"[]\")) {\n if (c.isNullableArray) {\n return `unnest_arrays(?::${c.dbType}[], true) as ${kq(c.columnName)}`;\n } else {\n return `unnest_arrays(?::${c.dbType}[]) as ${kq(c.columnName)}`;\n }\n } else {\n return `unnest(?::${c.dbType}[]) as ${kq(c.columnName)}`;\n }\n });\n const sql = `WITH ${tableName} AS (SELECT ${selects.join(\", \")})`;\n return [sql, columnValues];\n}\n\nasync function m2mBatchInsert(client: pg.PoolClient, joinTableName: string, todo: JoinRowTodo, onQuery: OnQuery) {\n const { m2m, newRows } = todo;\n if (newRows.length === 0) return;\n const col1Values = newRows.map((row) => todo.dbValue(row, m2m.columnName));\n const col2Values = newRows.map((row) => todo.dbValue(row, m2m.otherColumnName));\n if (m2m.hasJoinTableId) {\n const sql = cleanSql(`\n WITH data AS (SELECT unnest(?::int[]) as ${kq(m2m.columnName)}, unnest(?::int[]) as ${kq(m2m.otherColumnName)})\n INSERT INTO ${kq(joinTableName)} (${kq(m2m.columnName)}, ${kq(m2m.otherColumnName)})\n SELECT * FROM data\n ON CONFLICT (${kq(m2m.columnName)}, ${kq(m2m.otherColumnName)}) DO UPDATE SET id = ${kq(joinTableName)}.id\n RETURNING id;\n `);\n const pgSql = toPgParams(sql);\n onQuery?.(pgSql);\n const { rows } = await client.query(pgSql, [col1Values, col2Values]);\n for (let i = 0; i < rows.length; i++) {\n newRows[i].id = rows[i].id;\n newRows[i].op = JoinRowOperation.Flushed;\n newRows[i].persisted = true;\n }\n } else {\n // Id-less join tables have no surrogate id to return; the FK pair is the PK, so just\n // insert and let any duplicate be a no-op.\n const sql = cleanSql(`\n WITH data AS (SELECT unnest(?::int[]) as ${kq(m2m.columnName)}, unnest(?::int[]) as ${kq(m2m.otherColumnName)})\n INSERT INTO ${kq(joinTableName)} (${kq(m2m.columnName)}, ${kq(m2m.otherColumnName)})\n SELECT * FROM data\n ON CONFLICT (${kq(m2m.columnName)}, ${kq(m2m.otherColumnName)}) DO NOTHING;\n `);\n const pgSql = toPgParams(sql);\n onQuery?.(pgSql);\n await client.query(pgSql, [col1Values, col2Values]);\n for (const row of newRows) {\n row.op = JoinRowOperation.Flushed;\n row.persisted = true;\n }\n }\n}\n\nasync function m2mBatchDelete(client: pg.PoolClient, joinTableName: string, todo: JoinRowTodo, onQuery: OnQuery) {\n const { m2m, deletedRows } = todo;\n if (deletedRows.length === 0) return;\n // Rows with a surrogate id are deleted by id; rows without one — id-less tables, or `remove`s\n // done against an unloaded ManyToManyCollection — are deleted by their (col1, col2) composite.\n const [haveIds, noIds] = partition(deletedRows, (r) => {\n // We used to use `id !== -1` as a marker for \"row is/is-not persisted in the db\" -- but when adding support\n // for id-column-less m2m tables, we couldn't use `id === -1` as this marker anymore, so now rely on a dedicated\n // `persisted` key instead.\n //\n // So, ideally, this `id !== -1` could go away _except_ that our internal verisoning plugin leverages this \"m2m rows\n // with an id=-1 delete by their FK values\" to handle our versioned m2m tables. And for now it's easier to\n // keep/restore this `id !== -1` than refactor that.\n return r.id !== undefined && r.id !== -1;\n });\n if (haveIds.length > 0) {\n const pgSql = toPgParams(`DELETE FROM ${kq(joinTableName)} WHERE id = ANY(?)`);\n onQuery?.(pgSql);\n await client.query(pgSql, [haveIds.map((r) => r.id!)]);\n }\n if (noIds.length > 0) {\n const data = noIds\n // Watch for m2m rows that got added-then-removed to entities that were themselves added-then-removed,\n // as the deTagId will be undefined for those, as we're skipping adding them to the database.\n .filter((row) => !todo.isNew(row, m2m.columnName) && !todo.isNew(row, m2m.otherColumnName))\n .map((row) => [todo.dbValue(row, m2m.columnName), todo.dbValue(row, m2m.otherColumnName)] as any);\n if (data.length > 0) {\n const pgSql = toPgParams(`\n DELETE FROM ${kq(joinTableName)}\n WHERE (${kq(m2m.columnName)}, ${kq(m2m.otherColumnName)}) IN (\n SELECT (data->>0)::int, (data->>1)::int FROM jsonb_array_elements(?) data\n )`);\n onQuery?.(pgSql);\n await client.query(pgSql, [JSON.stringify(data)]);\n }\n }\n deletedRows.forEach((row) => {\n row.id = undefined;\n row.persisted = false;\n row.op = JoinRowOperation.Flushed;\n });\n}\n\n/**\n * Configures the `pg` driver with the best type parsers.\n *\n * In particular, pg's default `TIMESTAMPTZ` parser is very inefficient, and even just\n * pulling in the latest `pg-types` and installing the fixed parser makes a noticable\n * difference.\n */\nexport function setupLatestPgTypes(temporal: RuntimeConfig[\"temporal\"]): void {\n if (temporal) {\n // Don't eagerly parse the strings, instead defer to the serde logic. This only governs\n // classic *text* results (knex, pool.query, non-lazy drivers); binary lazy cells construct\n // Temporal values directly, with no strings involved (see registerTemporalBinaryParsers).\n const noop = (s: string) => s;\n const noopArray = (s: string) => array.parse(s, noop);\n\n const { TIMESTAMP, TIMESTAMPTZ, DATE } = pg.types.builtins;\n pg.types.setTypeParser(DATE, noop);\n pg.types.setTypeParser(TIMESTAMP, noop);\n pg.types.setTypeParser(TIMESTAMPTZ, noop);\n\n // Use `as number` b/c the typings of shadowed pg-types from `pg` and `pg-types` top-level don't line up\n pg.types.setTypeParser(1182 as number, noopArray); // date[]\n pg.types.setTypeParser(1115 as number, noopArray); // timestamp[]\n pg.types.setTypeParser(1185 as number, noopArray); // timestamptz[]\n\n registerTemporalBinaryParsers();\n } else {\n pg.types.setTypeParser(pg.types.builtins.TIMESTAMPTZ, getTypeParser(builtins.TIMESTAMPTZ));\n // The binary registry's date/timestamp/timestamptz builtins already decode to Dates\n }\n}\n\nconst questionMarks = /(?<!@)\\?/g;\n\n/**\n * Converts our knex-style `?` placeholders to pg-native `$1, $2, ...` numbered parameters.\n *\n * We skipping postgres operators like `@?`. */\nfunction toPgParams(sql: string): string {\n let i = 0;\n return sql.replace(questionMarks, () => `$${++i}`);\n}\n\nlet warnedUnsupportedClient = false;\n\n/** Warns once when lazyRows degrades to classic rows for an unsupported client, i.e. pg-native. */\nfunction warnUnsupportedClientOnce(): void {\n if (warnedUnsupportedClient) return;\n warnedUnsupportedClient = true;\n console.warn(\"joist-orm: lazyRows is enabled, but this client does not support it; using classic rows.\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Driver, EntityManager, IdAssigner, JoinRowTodo, ParsedFindQuery, PreloadPlugin, RuntimeConfig, Todo } from "joist-core";
|
|
2
|
+
import pg from "pg";
|
|
3
|
+
//#region src/drivers/PostgresDriver.d.ts
|
|
4
|
+
interface PostgresDriverOpts {
|
|
5
|
+
idAssigner?: IdAssigner;
|
|
6
|
+
/** Sets a default `PreloadPlugin` for any `EntityManager` that uses this driver. */
|
|
7
|
+
preloadPlugin?: PreloadPlugin;
|
|
8
|
+
/** Called after each query is executed, useful for testing/debugging. */
|
|
9
|
+
onQuery?: (sql: string) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Experimental: keeps entity find results as raw wire bytes and decodes each row/column cell
|
|
12
|
+
* lazily on field access, instead of eagerly materializing POJO rows.
|
|
13
|
+
*
|
|
14
|
+
* Applies to unpaginated `em.find`, `em.load`, and o2m/o2o/recursive relation loads against
|
|
15
|
+
* the pure-JS pg client. Other loaders deliberately stay classic: paginated finds (small
|
|
16
|
+
* pages, measured neutral), m2m join-table rows (narrow + fully read, where materialized rows
|
|
17
|
+
* measured faster, and `JoinRows` owns/mutates them), lazy columns, and id/count loaders.
|
|
18
|
+
*
|
|
19
|
+
* Lazy queries also request *binary* result format (prototype), decoding int/bool/float/text
|
|
20
|
+
* cells — and, via the fast paths `setupLatestPgTypes` registers, date/timestamptz cells —
|
|
21
|
+
* wire-bytes -> value with no intermediate strings; numeric/exotic cells render to pg's
|
|
22
|
+
* canonical text and reuse the active text parsers for parity. `JOIST_LAZY_BINARY=0`
|
|
23
|
+
* reverts lazy queries to text-format results. It
|
|
24
|
+
* uses classic rows when the choice is knowable up-front (patching pg-protocol failed, or the
|
|
25
|
+
* client is unsupported, i.e. pg-native — both warn once). If a connection turns out
|
|
26
|
+
* mid-query to use an unpatched pg-protocol copy (a duplicate `pg` install), the query fails
|
|
27
|
+
* with a descriptive error — a misconfiguration CI builds/smoketests will catch immediately.
|
|
28
|
+
* Note deferred decoding also defers custom-parser errors from `await em.find` to first field
|
|
29
|
+
* access. See JS-ROW-STORE-DESIGN.md.
|
|
30
|
+
*/
|
|
31
|
+
lazyRows?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Implements the `Driver` interface for Postgres.
|
|
35
|
+
*
|
|
36
|
+
* This is the canonical driver implementation and leverages several aspects of
|
|
37
|
+
* Postgres for the best performance, i.e.:
|
|
38
|
+
*
|
|
39
|
+
* - Deferred foreign key constraints are used to allow bulk inserting/updating
|
|
40
|
+
* all entities without any topographic sorting/ordering issues.
|
|
41
|
+
*
|
|
42
|
+
* - Sequences are used to bulk-assign + then bulk-insert all entities, to again
|
|
43
|
+
* avoid issues with ordering issues (cannot bulk insert A w/o knowing the id of B).
|
|
44
|
+
*
|
|
45
|
+
* - We use a pg-specific bulk update syntax.
|
|
46
|
+
*/
|
|
47
|
+
declare class PostgresDriver implements Driver<pg.PoolClient> {
|
|
48
|
+
#private;
|
|
49
|
+
readonly pool: pg.Pool;
|
|
50
|
+
readonly lazyRows: boolean;
|
|
51
|
+
/** Defined only when `lazyRows` is enabled; its presence is the capability signal the EM checks. */
|
|
52
|
+
readonly executeFindRowData: Driver["executeFindRowData"];
|
|
53
|
+
constructor(pool: pg.Pool, opts?: PostgresDriverOpts);
|
|
54
|
+
executeFind(em: EntityManager, parsed: ParsedFindQuery, settings: {
|
|
55
|
+
limit?: number;
|
|
56
|
+
offset?: number;
|
|
57
|
+
}): Promise<any[]>;
|
|
58
|
+
executeQuery(em: EntityManager, sql: string, bindings: readonly any[]): Promise<any[]>;
|
|
59
|
+
transaction<T>(em: EntityManager, fn: (txn: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
60
|
+
assignNewIds(_: EntityManager, todos: Record<string, Todo>): Promise<void>;
|
|
61
|
+
flush(em: EntityManager, entityTodos: Record<string, Todo>, joinRows: Record<string, JoinRowTodo>): Promise<void>;
|
|
62
|
+
get defaultPlugins(): {
|
|
63
|
+
preloadPlugin: any;
|
|
64
|
+
};
|
|
65
|
+
private getMaybeInTxnClient;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Configures the `pg` driver with the best type parsers.
|
|
69
|
+
*
|
|
70
|
+
* In particular, pg's default `TIMESTAMPTZ` parser is very inefficient, and even just
|
|
71
|
+
* pulling in the latest `pg-types` and installing the fixed parser makes a noticable
|
|
72
|
+
* difference.
|
|
73
|
+
*/
|
|
74
|
+
declare function setupLatestPgTypes(temporal: RuntimeConfig["temporal"]): void;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { PostgresDriver, PostgresDriverOpts, setupLatestPgTypes };
|
|
77
|
+
//# sourceMappingURL=PostgresDriver.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresDriver.d.cts","names":[],"sources":["../../src/drivers/PostgresDriver.ts"],"mappings":";;;UAuCiB;EACf,aAAa;;EAEb,gBAAgB;;EAEhB,WAAW;;;;;;;;;;;;;;;;;;;;;;EAsBX;;;;;;;;;;;;;;;;cAwBW,0BAA0B,OAAO,GAAG;;WAUpC,MAAM,GAAG;WANX;;WAEA,oBAAoB;EAG7B,YACW,MAAM,GAAG,MAClB,OAAO;EAsBH,YACJ,IAAI,eACJ,QAAQ,iBACR;IAAY;IAAgB;MAC3B;EAmCG,aAAa,IAAI,eAAe,aAAa,2BAA2B;EAOxE,YAAY,GAAG,IAAI,eAAe,KAAK,KAAK,GAAG,eAAe,QAAQ,KAAK,QAAQ;EA8BnF,aAAa,GAAG,eAAe,OAAO,eAAe,QAAQ;EAI7D,MACJ,IAAI,eACJ,aAAa,eAAe,OAC5B,UAAU,eAAe,eACxB;MAoBC;IACO;;UAGH;;;;;;;;;iBAgLM,mBAAmB,UAAU"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Driver, EntityManager, IdAssigner, JoinRowTodo, ParsedFindQuery, PreloadPlugin, RuntimeConfig, Todo } from "joist-core";
|
|
2
|
+
import pg from "pg";
|
|
3
|
+
//#region src/drivers/PostgresDriver.d.ts
|
|
4
|
+
interface PostgresDriverOpts {
|
|
5
|
+
idAssigner?: IdAssigner;
|
|
6
|
+
/** Sets a default `PreloadPlugin` for any `EntityManager` that uses this driver. */
|
|
7
|
+
preloadPlugin?: PreloadPlugin;
|
|
8
|
+
/** Called after each query is executed, useful for testing/debugging. */
|
|
9
|
+
onQuery?: (sql: string) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Experimental: keeps entity find results as raw wire bytes and decodes each row/column cell
|
|
12
|
+
* lazily on field access, instead of eagerly materializing POJO rows.
|
|
13
|
+
*
|
|
14
|
+
* Applies to unpaginated `em.find`, `em.load`, and o2m/o2o/recursive relation loads against
|
|
15
|
+
* the pure-JS pg client. Other loaders deliberately stay classic: paginated finds (small
|
|
16
|
+
* pages, measured neutral), m2m join-table rows (narrow + fully read, where materialized rows
|
|
17
|
+
* measured faster, and `JoinRows` owns/mutates them), lazy columns, and id/count loaders.
|
|
18
|
+
*
|
|
19
|
+
* Lazy queries also request *binary* result format (prototype), decoding int/bool/float/text
|
|
20
|
+
* cells — and, via the fast paths `setupLatestPgTypes` registers, date/timestamptz cells —
|
|
21
|
+
* wire-bytes -> value with no intermediate strings; numeric/exotic cells render to pg's
|
|
22
|
+
* canonical text and reuse the active text parsers for parity. `JOIST_LAZY_BINARY=0`
|
|
23
|
+
* reverts lazy queries to text-format results. It
|
|
24
|
+
* uses classic rows when the choice is knowable up-front (patching pg-protocol failed, or the
|
|
25
|
+
* client is unsupported, i.e. pg-native — both warn once). If a connection turns out
|
|
26
|
+
* mid-query to use an unpatched pg-protocol copy (a duplicate `pg` install), the query fails
|
|
27
|
+
* with a descriptive error — a misconfiguration CI builds/smoketests will catch immediately.
|
|
28
|
+
* Note deferred decoding also defers custom-parser errors from `await em.find` to first field
|
|
29
|
+
* access. See JS-ROW-STORE-DESIGN.md.
|
|
30
|
+
*/
|
|
31
|
+
lazyRows?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Implements the `Driver` interface for Postgres.
|
|
35
|
+
*
|
|
36
|
+
* This is the canonical driver implementation and leverages several aspects of
|
|
37
|
+
* Postgres for the best performance, i.e.:
|
|
38
|
+
*
|
|
39
|
+
* - Deferred foreign key constraints are used to allow bulk inserting/updating
|
|
40
|
+
* all entities without any topographic sorting/ordering issues.
|
|
41
|
+
*
|
|
42
|
+
* - Sequences are used to bulk-assign + then bulk-insert all entities, to again
|
|
43
|
+
* avoid issues with ordering issues (cannot bulk insert A w/o knowing the id of B).
|
|
44
|
+
*
|
|
45
|
+
* - We use a pg-specific bulk update syntax.
|
|
46
|
+
*/
|
|
47
|
+
declare class PostgresDriver implements Driver<pg.PoolClient> {
|
|
48
|
+
#private;
|
|
49
|
+
readonly pool: pg.Pool;
|
|
50
|
+
readonly lazyRows: boolean;
|
|
51
|
+
/** Defined only when `lazyRows` is enabled; its presence is the capability signal the EM checks. */
|
|
52
|
+
readonly executeFindRowData: Driver["executeFindRowData"];
|
|
53
|
+
constructor(pool: pg.Pool, opts?: PostgresDriverOpts);
|
|
54
|
+
executeFind(em: EntityManager, parsed: ParsedFindQuery, settings: {
|
|
55
|
+
limit?: number;
|
|
56
|
+
offset?: number;
|
|
57
|
+
}): Promise<any[]>;
|
|
58
|
+
executeQuery(em: EntityManager, sql: string, bindings: readonly any[]): Promise<any[]>;
|
|
59
|
+
transaction<T>(em: EntityManager, fn: (txn: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
60
|
+
assignNewIds(_: EntityManager, todos: Record<string, Todo>): Promise<void>;
|
|
61
|
+
flush(em: EntityManager, entityTodos: Record<string, Todo>, joinRows: Record<string, JoinRowTodo>): Promise<void>;
|
|
62
|
+
get defaultPlugins(): {
|
|
63
|
+
preloadPlugin: any;
|
|
64
|
+
};
|
|
65
|
+
private getMaybeInTxnClient;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Configures the `pg` driver with the best type parsers.
|
|
69
|
+
*
|
|
70
|
+
* In particular, pg's default `TIMESTAMPTZ` parser is very inefficient, and even just
|
|
71
|
+
* pulling in the latest `pg-types` and installing the fixed parser makes a noticable
|
|
72
|
+
* difference.
|
|
73
|
+
*/
|
|
74
|
+
declare function setupLatestPgTypes(temporal: RuntimeConfig["temporal"]): void;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { PostgresDriver, PostgresDriverOpts, setupLatestPgTypes };
|
|
77
|
+
//# sourceMappingURL=PostgresDriver.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresDriver.d.mts","names":[],"sources":["../../src/drivers/PostgresDriver.ts"],"mappings":";;;UAuCiB;EACf,aAAa;;EAEb,gBAAgB;;EAEhB,WAAW;;;;;;;;;;;;;;;;;;;;;;EAsBX;;;;;;;;;;;;;;;;cAwBW,0BAA0B,OAAO,GAAG;;WAUpC,MAAM,GAAG;WANX;;WAEA,oBAAoB;EAG7B,YACW,MAAM,GAAG,MAClB,OAAO;EAsBH,YACJ,IAAI,eACJ,QAAQ,iBACR;IAAY;IAAgB;MAC3B;EAmCG,aAAa,IAAI,eAAe,aAAa,2BAA2B;EAOxE,YAAY,GAAG,IAAI,eAAe,KAAK,KAAK,GAAG,eAAe,QAAQ,KAAK,QAAQ;EA8BnF,aAAa,GAAG,eAAe,OAAO,eAAe,QAAQ;EAI7D,MACJ,IAAI,eACJ,aAAa,eAAe,OAC5B,UAAU,eAAe,eACxB;MAoBC;IACO;;UAGH;;;;;;;;;iBAgLM,mBAAmB,UAAU"}
|