@framers/sql-storage-adapter 0.6.16 → 0.7.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/README.md +94 -3
- package/dist/adapters/betterSqliteAdapter.d.ts +18 -2
- package/dist/adapters/betterSqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/betterSqliteAdapter.js +13 -5
- package/dist/adapters/betterSqliteAdapter.js.map +1 -1
- package/dist/adapters/capacitorSqliteAdapter.d.ts +8 -0
- package/dist/adapters/capacitorSqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitorSqliteAdapter.js +4 -1
- package/dist/adapters/capacitorSqliteAdapter.js.map +1 -1
- package/dist/adapters/electron/electronMainAdapter.d.ts +10 -0
- package/dist/adapters/electron/electronMainAdapter.d.ts.map +1 -1
- package/dist/adapters/electron/electronMainAdapter.js +21 -12
- package/dist/adapters/electron/electronMainAdapter.js.map +1 -1
- package/dist/adapters/indexedDbAdapter.d.ts +198 -54
- package/dist/adapters/indexedDbAdapter.d.ts.map +1 -1
- package/dist/adapters/indexedDbAdapter.js +549 -129
- package/dist/adapters/indexedDbAdapter.js.map +1 -1
- package/dist/adapters/postgresAdapter.d.ts +143 -4
- package/dist/adapters/postgresAdapter.d.ts.map +1 -1
- package/dist/adapters/postgresAdapter.js +612 -107
- package/dist/adapters/postgresAdapter.js.map +1 -1
- package/dist/adapters/sqlJsAdapter.d.ts +17 -3
- package/dist/adapters/sqlJsAdapter.d.ts.map +1 -1
- package/dist/adapters/sqlJsAdapter.js +15 -3
- package/dist/adapters/sqlJsAdapter.js.map +1 -1
- package/dist/core/database.d.ts +28 -8
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +34 -5
- package/dist/core/database.js.map +1 -1
- package/dist/core/resolver.d.ts +14 -1
- package/dist/core/resolver.d.ts.map +1 -1
- package/dist/core/resolver.js +28 -8
- package/dist/core/resolver.js.map +1 -1
- package/dist/features/kv/idb.d.ts +79 -0
- package/dist/features/kv/idb.d.ts.map +1 -0
- package/dist/features/kv/idb.js +14 -0
- package/dist/features/kv/idb.js.map +1 -0
- package/dist/features/kv/index.d.ts +14 -0
- package/dist/features/kv/index.d.ts.map +1 -0
- package/dist/features/kv/index.js +14 -0
- package/dist/features/kv/index.js.map +1 -0
- package/dist/features/kv/indexedDbKeyValueStore.d.ts +77 -0
- package/dist/features/kv/indexedDbKeyValueStore.d.ts.map +1 -0
- package/dist/features/kv/indexedDbKeyValueStore.js +127 -0
- package/dist/features/kv/indexedDbKeyValueStore.js.map +1 -0
- package/dist/features/kv/keyValueStore.d.ts +48 -0
- package/dist/features/kv/keyValueStore.d.ts.map +1 -0
- package/dist/features/kv/keyValueStore.js +54 -0
- package/dist/features/kv/keyValueStore.js.map +1 -0
- package/dist/features/kv/sqlKeyValueStore.d.ts +34 -0
- package/dist/features/kv/sqlKeyValueStore.d.ts.map +1 -0
- package/dist/features/kv/sqlKeyValueStore.js +91 -0
- package/dist/features/kv/sqlKeyValueStore.js.map +1 -0
- package/dist/features/kv/valueCodec.d.ts +35 -0
- package/dist/features/kv/valueCodec.d.ts.map +1 -0
- package/dist/features/kv/valueCodec.js +462 -0
- package/dist/features/kv/valueCodec.js.map +1 -0
- package/dist/features/migrations/dataExport.js +9 -6
- package/dist/features/migrations/dataExport.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/shared/postgresTarget.d.ts +4 -3
- package/dist/shared/postgresTarget.d.ts.map +1 -1
- package/dist/shared/postgresTarget.js +6 -4
- package/dist/shared/postgresTarget.js.map +1 -1
- package/dist/shared/segment.d.ts +43 -0
- package/dist/shared/segment.d.ts.map +1 -0
- package/dist/shared/segment.js +77 -0
- package/dist/shared/segment.js.map +1 -0
- package/package.json +10 -2
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const __isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
5
5
|
import { toPostgresStatement } from '../shared/sqlPlaceholders.js';
|
|
6
6
|
import { splitSqlStatements } from '../shared/splitSqlStatements.js';
|
|
7
|
+
import { validateSegment } from '../shared/segment.js';
|
|
7
8
|
import { getStorageLogger } from '../shared/logger.js';
|
|
8
9
|
/** The type-parser overrides `parsers` asks for, by OID, after checking each value. */
|
|
9
10
|
const typeParserOverrides = (parsers) => {
|
|
@@ -46,6 +47,25 @@ const typeParserOverrides = (parsers) => {
|
|
|
46
47
|
}
|
|
47
48
|
return overrides;
|
|
48
49
|
};
|
|
50
|
+
/** The options that configure a pool when one is made, which a pool of the caller's already has. */
|
|
51
|
+
const POOL_SETTINGS = [
|
|
52
|
+
'connectionString',
|
|
53
|
+
'host',
|
|
54
|
+
'port',
|
|
55
|
+
'database',
|
|
56
|
+
'user',
|
|
57
|
+
'password',
|
|
58
|
+
'ssl',
|
|
59
|
+
'max',
|
|
60
|
+
'min',
|
|
61
|
+
'idleTimeoutMillis',
|
|
62
|
+
'connectionTimeoutMillis',
|
|
63
|
+
'application_name',
|
|
64
|
+
'statement_timeout',
|
|
65
|
+
'query_timeout',
|
|
66
|
+
'allowExitOnIdle',
|
|
67
|
+
'typeParsers',
|
|
68
|
+
];
|
|
49
69
|
const ISOLATION_LEVELS = new Map([
|
|
50
70
|
['read uncommitted', 'READ UNCOMMITTED'],
|
|
51
71
|
['read committed', 'READ COMMITTED'],
|
|
@@ -78,33 +98,407 @@ const transactionStatus = (client) => {
|
|
|
78
98
|
};
|
|
79
99
|
/** The statement and values node-postgres receives: see toPostgresStatement. */
|
|
80
100
|
const prepareStatement = toPostgresStatement;
|
|
81
|
-
/** Execute a mutation through a
|
|
82
|
-
const
|
|
101
|
+
/** Execute a mutation through a pool or a transaction client. */
|
|
102
|
+
const runWith = async (query, statement, parameters) => {
|
|
83
103
|
const { text, values } = prepareStatement(statement, parameters);
|
|
84
|
-
const result = await
|
|
104
|
+
const result = await query(text, values);
|
|
85
105
|
const firstRow = result.rows?.[0];
|
|
86
106
|
const lastInsertRowid = firstRow && typeof firstRow === 'object' && 'id' in firstRow
|
|
87
107
|
? firstRow.id
|
|
88
108
|
: null;
|
|
89
109
|
return { changes: result.rowCount ?? 0, lastInsertRowid };
|
|
90
110
|
};
|
|
91
|
-
/** Read one row through a
|
|
92
|
-
const
|
|
111
|
+
/** Read one row through a pool or a transaction client. */
|
|
112
|
+
const getWith = async (query, statement, parameters) => {
|
|
93
113
|
const { text, values } = prepareStatement(statement, parameters);
|
|
94
|
-
const result = await
|
|
114
|
+
const result = await query(text, values);
|
|
95
115
|
return result.rows?.[0] ?? null;
|
|
96
116
|
};
|
|
97
|
-
/** Read every matching row through a
|
|
98
|
-
const
|
|
117
|
+
/** Read every matching row through a pool or a transaction client. */
|
|
118
|
+
const allWith = async (query, statement, parameters) => {
|
|
99
119
|
const { text, values } = prepareStatement(statement, parameters);
|
|
100
|
-
const result = await
|
|
120
|
+
const result = await query(text, values);
|
|
101
121
|
return result.rows ?? [];
|
|
102
122
|
};
|
|
103
|
-
/** Execute a statement script through a
|
|
104
|
-
const
|
|
123
|
+
/** Execute a statement script through a pool or a transaction client. */
|
|
124
|
+
const execWith = async (query, script) => {
|
|
105
125
|
const statements = splitSqlStatements(script);
|
|
106
126
|
for (const text of statements) {
|
|
107
|
-
await
|
|
127
|
+
await query(text, []);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* The first `limit` words of a statement, past its comments: enough to tell
|
|
132
|
+
* what the statement is. String literals and dollar-quoted strings count as
|
|
133
|
+
* one word each and never read as a keyword.
|
|
134
|
+
*/
|
|
135
|
+
const leadingWords = (statement, limit) => {
|
|
136
|
+
const words = [];
|
|
137
|
+
let i = 0;
|
|
138
|
+
const length = statement.length;
|
|
139
|
+
while (i < length && words.length < limit) {
|
|
140
|
+
const char = statement[i];
|
|
141
|
+
if (/\s/.test(char)) {
|
|
142
|
+
i += 1;
|
|
143
|
+
}
|
|
144
|
+
else if (char === '-' && statement[i + 1] === '-') {
|
|
145
|
+
const end = statement.indexOf('\n', i);
|
|
146
|
+
i = end === -1 ? length : end + 1;
|
|
147
|
+
}
|
|
148
|
+
else if (char === '/' && statement[i + 1] === '*') {
|
|
149
|
+
// PostgreSQL's block comments nest.
|
|
150
|
+
let depth = 1;
|
|
151
|
+
i += 2;
|
|
152
|
+
while (i < length && depth > 0) {
|
|
153
|
+
if (statement[i] === '/' && statement[i + 1] === '*') {
|
|
154
|
+
depth += 1;
|
|
155
|
+
i += 2;
|
|
156
|
+
}
|
|
157
|
+
else if (statement[i] === '*' && statement[i + 1] === '/') {
|
|
158
|
+
depth -= 1;
|
|
159
|
+
i += 2;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
i += 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (char === "'") {
|
|
167
|
+
i += 1;
|
|
168
|
+
while (i < length) {
|
|
169
|
+
if (statement[i] === "'" && statement[i + 1] === "'")
|
|
170
|
+
i += 2;
|
|
171
|
+
else if (statement[i] === "'")
|
|
172
|
+
break;
|
|
173
|
+
else
|
|
174
|
+
i += 1;
|
|
175
|
+
}
|
|
176
|
+
i += 1;
|
|
177
|
+
words.push({ keyword: "'", identifier: '' });
|
|
178
|
+
}
|
|
179
|
+
else if (char === '"') {
|
|
180
|
+
let identifier = '';
|
|
181
|
+
i += 1;
|
|
182
|
+
while (i < length) {
|
|
183
|
+
if (statement[i] === '"' && statement[i + 1] === '"') {
|
|
184
|
+
identifier += '"';
|
|
185
|
+
i += 2;
|
|
186
|
+
}
|
|
187
|
+
else if (statement[i] === '"') {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
identifier += statement[i];
|
|
192
|
+
i += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
i += 1;
|
|
196
|
+
words.push({ keyword: '"', identifier });
|
|
197
|
+
}
|
|
198
|
+
else if (char === '$') {
|
|
199
|
+
const tag = /^\$[A-Za-z_]*\$/.exec(statement.slice(i));
|
|
200
|
+
if (tag) {
|
|
201
|
+
const end = statement.indexOf(tag[0], i + tag[0].length);
|
|
202
|
+
i = end === -1 ? length : end + tag[0].length;
|
|
203
|
+
words.push({ keyword: '$', identifier: '' });
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
i += 1;
|
|
207
|
+
words.push({ keyword: '$', identifier: '' });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (/[A-Za-z_]/.test(char)) {
|
|
211
|
+
const word = /^[A-Za-z_][A-Za-z0-9_$]*/.exec(statement.slice(i))[0];
|
|
212
|
+
i += word.length;
|
|
213
|
+
words.push({ keyword: word.toUpperCase(), identifier: word.toLowerCase() });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
i += 1;
|
|
217
|
+
words.push({ keyword: char, identifier: '' });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return words;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* The extension a `CREATE EXTENSION` statement creates when the statement
|
|
224
|
+
* names no schema (`SCHEMA` or `WITH SCHEMA`), or null for any other
|
|
225
|
+
* statement.
|
|
226
|
+
*/
|
|
227
|
+
const unqualifiedCreateExtension = (statement) => {
|
|
228
|
+
const words = leadingWords(statement, 16);
|
|
229
|
+
if (words[0]?.keyword !== 'CREATE' || words[1]?.keyword !== 'EXTENSION')
|
|
230
|
+
return null;
|
|
231
|
+
let at = 2;
|
|
232
|
+
if (words[2]?.keyword === 'IF' && words[3]?.keyword === 'NOT' && words[4]?.keyword === 'EXISTS')
|
|
233
|
+
at = 5;
|
|
234
|
+
const name = words[at];
|
|
235
|
+
if (!name || (name.keyword !== '"' && !/^[A-Z_]/.test(name.keyword)))
|
|
236
|
+
return null;
|
|
237
|
+
if (words.slice(at + 1).some((word) => word.keyword === 'SCHEMA'))
|
|
238
|
+
return null;
|
|
239
|
+
return name.identifier;
|
|
240
|
+
};
|
|
241
|
+
/** An unknown thrown value as an Error. */
|
|
242
|
+
const asError = (value) => (value instanceof Error ? value : new Error(String(value)));
|
|
243
|
+
/**
|
|
244
|
+
* Puts a failed ROLLBACK's error on the transaction's error, as a
|
|
245
|
+
* non-enumerable `rollbackError` property, when that error can take one.
|
|
246
|
+
*/
|
|
247
|
+
const keepRollbackError = (error, rollbackError) => {
|
|
248
|
+
if (typeof error !== 'object' || error === null || !Object.isExtensible(error))
|
|
249
|
+
return;
|
|
250
|
+
try {
|
|
251
|
+
Object.defineProperty(error, 'rollbackError', {
|
|
252
|
+
value: rollbackError,
|
|
253
|
+
enumerable: false,
|
|
254
|
+
configurable: true,
|
|
255
|
+
writable: true,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// A non-configurable rollbackError is already there: keep it.
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Listens for a connection error on a checked-out client until `stop()`. pg
|
|
264
|
+
* emits a client's connection error (the server ending the session, a broken
|
|
265
|
+
* socket) on the client when no query is running, and pg-pool listens only on
|
|
266
|
+
* idle clients, so without this an unheard error event would end the process.
|
|
267
|
+
*/
|
|
268
|
+
const watchClient = (client) => {
|
|
269
|
+
let failure;
|
|
270
|
+
// A pg-compatible pool whose clients are not event emitters emits nothing to hear.
|
|
271
|
+
if (typeof client.on !== 'function' || typeof client.removeListener !== 'function') {
|
|
272
|
+
return { failure: () => undefined, stop: () => undefined };
|
|
273
|
+
}
|
|
274
|
+
const onError = (error) => {
|
|
275
|
+
if (!failure)
|
|
276
|
+
failure = error;
|
|
277
|
+
};
|
|
278
|
+
client.on('error', onError);
|
|
279
|
+
return {
|
|
280
|
+
failure: () => failure,
|
|
281
|
+
stop: () => {
|
|
282
|
+
client.removeListener?.('error', onError);
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
/** Where an installed extension's objects are, and whether ALTER EXTENSION ... SET SCHEMA can move them. */
|
|
287
|
+
const EXTENSION_PLACE = 'SELECT n.nspname AS schema, e.extrelocatable AS relocatable FROM pg_extension e ' +
|
|
288
|
+
'JOIN pg_namespace n ON n.oid = e.extnamespace WHERE e.extname = $1';
|
|
289
|
+
/**
|
|
290
|
+
* Runs a statement through a segment's connections with care for the one
|
|
291
|
+
* statement whose effect depends on where it runs: an unqualified
|
|
292
|
+
* `CREATE EXTENSION` installs into the current schema, which on a segment is
|
|
293
|
+
* the segment, where no other segment and not the host find the extension's
|
|
294
|
+
* types. Such a statement runs with `search_path` set to `public` for itself
|
|
295
|
+
* alone, as it would without a segment (an extension whose control file names
|
|
296
|
+
* a schema still goes there). Afterwards the adapter checks where the
|
|
297
|
+
* extension is, since `IF NOT EXISTS` also succeeds for one installed in a
|
|
298
|
+
* schema the segment cannot see.
|
|
299
|
+
*/
|
|
300
|
+
class SegmentStatements {
|
|
301
|
+
constructor(segment) {
|
|
302
|
+
this.segment = segment;
|
|
303
|
+
/** Extensions already reported as out of the segment's reach. */
|
|
304
|
+
this.reported = new Set();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Runs `text` on the pool. The connection that runs a `CREATE EXTENSION`
|
|
308
|
+
* goes back to the pool unless its BEGIN or ROLLBACK failed or it broke
|
|
309
|
+
* while held, in which case it is closed.
|
|
310
|
+
*/
|
|
311
|
+
async onPool(pool, text, values) {
|
|
312
|
+
const extension = unqualifiedCreateExtension(text);
|
|
313
|
+
if (extension === null) {
|
|
314
|
+
return pool.query(text, values);
|
|
315
|
+
}
|
|
316
|
+
const client = await pool.connect();
|
|
317
|
+
const watch = watchClient(client);
|
|
318
|
+
let brokenBy;
|
|
319
|
+
try {
|
|
320
|
+
return await this.inOwnTransaction(client, text, values, extension, (error) => {
|
|
321
|
+
if (!brokenBy)
|
|
322
|
+
brokenBy = error;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
watch.stop();
|
|
327
|
+
client.release(brokenBy ?? watch.failure());
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** Runs `text` on a transaction's client, inside the caller's transaction. */
|
|
331
|
+
async inTransaction(client, text, values) {
|
|
332
|
+
const extension = unqualifiedCreateExtension(text);
|
|
333
|
+
if (extension === null) {
|
|
334
|
+
return client.query(text, values);
|
|
335
|
+
}
|
|
336
|
+
return this.withinTransaction(client, text, values, extension);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Runs `text` on the connection a `withConnection()` callback holds: inside
|
|
340
|
+
* the transaction the callback began, as `inTransaction` does, or outside
|
|
341
|
+
* one in a transaction of its own, as on the pool. On a pg that does not
|
|
342
|
+
* report a connection's transaction status (before 8.21), the session's
|
|
343
|
+
* search_path is set to `public` around the statement and set back after it.
|
|
344
|
+
* `onBroken` hears when the connection cannot be trusted any more, so
|
|
345
|
+
* `withConnection()` closes it instead of returning it to the pool.
|
|
346
|
+
*/
|
|
347
|
+
async onConnection(client, text, values, onBroken) {
|
|
348
|
+
const extension = unqualifiedCreateExtension(text);
|
|
349
|
+
if (extension === null) {
|
|
350
|
+
return client.query(text, values);
|
|
351
|
+
}
|
|
352
|
+
switch (transactionStatus(client)) {
|
|
353
|
+
case 'T':
|
|
354
|
+
return this.withinTransaction(client, text, values, extension);
|
|
355
|
+
case 'I':
|
|
356
|
+
return this.inOwnTransaction(client, text, values, extension, onBroken);
|
|
357
|
+
case 'E':
|
|
358
|
+
// The transaction has failed: the statement fails with its reason.
|
|
359
|
+
return client.query(text, values);
|
|
360
|
+
default:
|
|
361
|
+
return this.aroundSession(client, text, values, extension, onBroken);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* In a transaction of its own on `client`, whose COMMIT or ROLLBACK ends the
|
|
366
|
+
* setting. A BEGIN or ROLLBACK that fails leaves the connection in a state
|
|
367
|
+
* nobody knows, so `onBroken` hears of it.
|
|
368
|
+
*/
|
|
369
|
+
async inOwnTransaction(client, text, values, extension, onBroken) {
|
|
370
|
+
try {
|
|
371
|
+
await client.query('BEGIN');
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
onBroken(asError(error));
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
let result;
|
|
378
|
+
try {
|
|
379
|
+
// set_config(..., true) is SET LOCAL: the COMMIT or ROLLBACK below ends it,
|
|
380
|
+
// so the connection goes back to the pool on the segment's search_path.
|
|
381
|
+
await client.query("SELECT set_config('search_path', 'public', true)");
|
|
382
|
+
result = await client.query(text, values);
|
|
383
|
+
await client.query('COMMIT');
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
try {
|
|
387
|
+
await client.query('ROLLBACK');
|
|
388
|
+
}
|
|
389
|
+
catch (rollbackError) {
|
|
390
|
+
onBroken(asError(rollbackError));
|
|
391
|
+
keepRollbackError(error, asError(rollbackError));
|
|
392
|
+
}
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
await this.checkReach((t, v) => client.query(t, v), extension);
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
/** Inside the transaction open on `client`, setting the search_path for the statement and back. */
|
|
399
|
+
async withinTransaction(client, text, values, extension) {
|
|
400
|
+
const saved = await client.query("SELECT current_setting('search_path') AS path");
|
|
401
|
+
const path = saved.rows[0]?.path;
|
|
402
|
+
await client.query("SELECT set_config('search_path', 'public', true)");
|
|
403
|
+
// A failure aborts the caller's transaction, whose rollback (or a
|
|
404
|
+
// rollback to a savepoint before this) undoes the setting.
|
|
405
|
+
const result = await client.query(text, values);
|
|
406
|
+
await client.query('SELECT set_config($1, $2, true)', ['search_path', typeof path === 'string' ? path : `${this.segment}, public`]);
|
|
407
|
+
await this.checkReach((t, v) => client.query(t, v), extension);
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* With the session's search_path set to `public` for the statement, and set
|
|
412
|
+
* back after it. When the statement fails inside a transaction, setting it
|
|
413
|
+
* back fails too, and that transaction's rollback restores it. Without the
|
|
414
|
+
* transaction status this cannot tell that case from a connection whose
|
|
415
|
+
* search_path stays on `public`, so a failed set-back tells `onBroken`, and
|
|
416
|
+
* the connection is closed rather than returned to the pool outside the
|
|
417
|
+
* segment.
|
|
418
|
+
*/
|
|
419
|
+
async aroundSession(client, text, values, extension, onBroken) {
|
|
420
|
+
const saved = await client.query("SELECT current_setting('search_path') AS path");
|
|
421
|
+
const savedPath = saved.rows[0]?.path;
|
|
422
|
+
const path = typeof savedPath === 'string' ? savedPath : `${this.segment}, public`;
|
|
423
|
+
await client.query("SELECT set_config('search_path', 'public', false)");
|
|
424
|
+
const setBack = async () => {
|
|
425
|
+
try {
|
|
426
|
+
await client.query('SELECT set_config($1, $2, false)', ['search_path', path]);
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
onBroken(asError(error));
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
let result;
|
|
434
|
+
try {
|
|
435
|
+
result = await client.query(text, values);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
await setBack().catch(() => undefined);
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
await setBack();
|
|
442
|
+
await this.checkReach((t, v) => client.query(t, v), extension);
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
async checkReach(query, extension) {
|
|
446
|
+
if (this.reported.has(extension))
|
|
447
|
+
return;
|
|
448
|
+
const found = await query(EXTENSION_PLACE, [extension]);
|
|
449
|
+
const place = found.rows[0];
|
|
450
|
+
if (!place || place.relocatable !== true)
|
|
451
|
+
return;
|
|
452
|
+
if (place.schema === 'public' || place.schema === this.segment)
|
|
453
|
+
return;
|
|
454
|
+
this.reported.add(extension);
|
|
455
|
+
getStorageLogger().warn({ adapter: 'postgres', segment: this.segment, extension, schema: String(place.schema) }, `PostgreSQL extension "${extension}" is installed in schema "${String(place.schema)}", which segment ` +
|
|
456
|
+
`"${this.segment}" does not reach: its search_path is ${this.segment}, public, so the extension's types and functions ` +
|
|
457
|
+
`do not resolve here. ALTER EXTENSION "${extension}" SET SCHEMA public moves it where every segment finds it.`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/** A pool on which `end()` has been called. */
|
|
461
|
+
const hasEnded = (pool) => pool.ending === true || pool.ended === true;
|
|
462
|
+
/** The listener the adapter puts on a pool it owns when no `onError` was given. */
|
|
463
|
+
const warnOfIdleError = (error) => {
|
|
464
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
465
|
+
getStorageLogger().warn({ adapter: 'postgres', error: message }, `PostgreSQL: an idle connection of the pool failed; the pool opens another when one is needed. ${message}`);
|
|
466
|
+
};
|
|
467
|
+
/** The value of an environment variable, where there is an environment. */
|
|
468
|
+
const readEnv = (key) => typeof process !== 'undefined' && process.env ? process.env[key] : undefined;
|
|
469
|
+
/**
|
|
470
|
+
* Refuses a connection string that sets its own startup options when a
|
|
471
|
+
* segment needs that parameter: pg copies a connection string's parameters
|
|
472
|
+
* over the explicit configuration, so its `options` would replace the
|
|
473
|
+
* segment's search_path.
|
|
474
|
+
*/
|
|
475
|
+
const refuseOwnStartupOptions = (connectionString) => {
|
|
476
|
+
if (connectionString && /[?&]options=/i.test(connectionString)) {
|
|
477
|
+
throw new Error('PostgresAdapter: a segment cannot be used with a connection string that sets its own startup options (options=...): ' +
|
|
478
|
+
"pg sends those in place of the segment's search_path. Move them out of the connection string, into PGOPTIONS.");
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
/**
|
|
482
|
+
* Creates a segment's schema when it is missing. The check comes first, so a
|
|
483
|
+
* role without CREATE on the database can open a segment that exists: CREATE
|
|
484
|
+
* SCHEMA IF NOT EXISTS checks that privilege before it looks for the schema.
|
|
485
|
+
*/
|
|
486
|
+
const createSchema = async (client, segment) => {
|
|
487
|
+
const found = await client.query('SELECT 1 FROM pg_namespace WHERE nspname = $1', [segment]);
|
|
488
|
+
if (found.rows.length > 0) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
// validateSegment admits lower-case letters, digits and underscores alone.
|
|
493
|
+
await client.query(`CREATE SCHEMA IF NOT EXISTS "${segment}"`);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
// Two processes that open one new segment at once can both find it
|
|
497
|
+
// missing; the slower CREATE then fails on pg_namespace's unique index.
|
|
498
|
+
const code = error?.code;
|
|
499
|
+
if (code !== '23505' && code !== '42P06') {
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
108
502
|
}
|
|
109
503
|
};
|
|
110
504
|
/**
|
|
@@ -116,12 +510,14 @@ const execWithExecutor = async (executor, script) => {
|
|
|
116
510
|
* adapter's pool, or the held connection the transaction ran on.
|
|
117
511
|
*/
|
|
118
512
|
class PostgresTransactionAdapter {
|
|
119
|
-
constructor(client, capabilities, afterEnd) {
|
|
120
|
-
this.client = client;
|
|
513
|
+
constructor(client, capabilities, segment, afterEnd) {
|
|
121
514
|
this.capabilities = capabilities;
|
|
122
515
|
this.afterEnd = afterEnd;
|
|
123
516
|
this.kind = 'postgres';
|
|
124
517
|
this.ended = false;
|
|
518
|
+
this.query = segment
|
|
519
|
+
? (text, values) => segment.inTransaction(client, text, values)
|
|
520
|
+
: (text, values) => client.query(text, values);
|
|
125
521
|
}
|
|
126
522
|
async open() {
|
|
127
523
|
// The owning PostgresAdapter opens the pool before creating this view.
|
|
@@ -129,22 +525,22 @@ class PostgresTransactionAdapter {
|
|
|
129
525
|
async run(statement, parameters) {
|
|
130
526
|
if (this.ended)
|
|
131
527
|
return this.afterEnd.run(statement, parameters);
|
|
132
|
-
return
|
|
528
|
+
return runWith(this.query, statement, parameters);
|
|
133
529
|
}
|
|
134
530
|
async get(statement, parameters) {
|
|
135
531
|
if (this.ended)
|
|
136
532
|
return this.afterEnd.get(statement, parameters);
|
|
137
|
-
return
|
|
533
|
+
return getWith(this.query, statement, parameters);
|
|
138
534
|
}
|
|
139
535
|
async all(statement, parameters) {
|
|
140
536
|
if (this.ended)
|
|
141
537
|
return this.afterEnd.all(statement, parameters);
|
|
142
|
-
return
|
|
538
|
+
return allWith(this.query, statement, parameters);
|
|
143
539
|
}
|
|
144
540
|
async exec(script) {
|
|
145
541
|
if (this.ended)
|
|
146
542
|
return this.afterEnd.exec(script);
|
|
147
|
-
await
|
|
543
|
+
await execWith(this.query, script);
|
|
148
544
|
}
|
|
149
545
|
async transaction(fn) {
|
|
150
546
|
// After the transaction has ended, a new one begins where afterEnd runs it.
|
|
@@ -162,51 +558,6 @@ class PostgresTransactionAdapter {
|
|
|
162
558
|
this.ended = true;
|
|
163
559
|
}
|
|
164
560
|
}
|
|
165
|
-
/** An unknown thrown value as an Error. */
|
|
166
|
-
const asError = (value) => (value instanceof Error ? value : new Error(String(value)));
|
|
167
|
-
/**
|
|
168
|
-
* Puts a failed ROLLBACK's error on the transaction's error, as a
|
|
169
|
-
* non-enumerable `rollbackError` property, when that error can take one.
|
|
170
|
-
*/
|
|
171
|
-
const keepRollbackError = (error, rollbackError) => {
|
|
172
|
-
if (typeof error !== 'object' || error === null || !Object.isExtensible(error))
|
|
173
|
-
return;
|
|
174
|
-
try {
|
|
175
|
-
Object.defineProperty(error, 'rollbackError', {
|
|
176
|
-
value: rollbackError,
|
|
177
|
-
enumerable: false,
|
|
178
|
-
configurable: true,
|
|
179
|
-
writable: true,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// A non-configurable rollbackError is already there: keep it.
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
/**
|
|
187
|
-
* Listens for a connection error on a checked-out client until `stop()`. pg
|
|
188
|
-
* emits a client's connection error (the server ending the session, a broken
|
|
189
|
-
* socket) on the client when no query is running, and pg-pool listens only on
|
|
190
|
-
* idle clients, so without this an unheard error event would end the process.
|
|
191
|
-
*/
|
|
192
|
-
const watchClient = (client) => {
|
|
193
|
-
let failure;
|
|
194
|
-
// A pg-compatible pool whose clients are not event emitters emits nothing to hear.
|
|
195
|
-
if (typeof client.on !== 'function' || typeof client.removeListener !== 'function') {
|
|
196
|
-
return { failure: () => undefined, stop: () => undefined };
|
|
197
|
-
}
|
|
198
|
-
const onError = (error) => {
|
|
199
|
-
if (!failure)
|
|
200
|
-
failure = error;
|
|
201
|
-
};
|
|
202
|
-
client.on('error', onError);
|
|
203
|
-
return {
|
|
204
|
-
failure: () => failure,
|
|
205
|
-
stop: () => {
|
|
206
|
-
client.removeListener('error', onError);
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
};
|
|
210
561
|
/**
|
|
211
562
|
* Runs `fn` in a transaction that `begin` opens on `client`: COMMIT when `fn`
|
|
212
563
|
* resolves, ROLLBACK when `fn` or COMMIT throws, and that error is rethrown.
|
|
@@ -215,7 +566,7 @@ const watchClient = (client) => {
|
|
|
215
566
|
* pool. When ROLLBACK fails, the original error is still the one thrown, with
|
|
216
567
|
* the ROLLBACK error on it as `rollbackError`.
|
|
217
568
|
*/
|
|
218
|
-
const runTransaction = async (client, begin, fn, capabilities, afterEnd, onBroken) => {
|
|
569
|
+
const runTransaction = async (client, begin, fn, capabilities, segment, afterEnd, onBroken) => {
|
|
219
570
|
try {
|
|
220
571
|
await client.query(begin);
|
|
221
572
|
}
|
|
@@ -223,7 +574,7 @@ const runTransaction = async (client, begin, fn, capabilities, afterEnd, onBroke
|
|
|
223
574
|
onBroken(asError(error));
|
|
224
575
|
throw error;
|
|
225
576
|
}
|
|
226
|
-
const handle = new PostgresTransactionAdapter(client, capabilities, afterEnd);
|
|
577
|
+
const handle = new PostgresTransactionAdapter(client, capabilities, segment, afterEnd);
|
|
227
578
|
try {
|
|
228
579
|
const result = await fn(handle);
|
|
229
580
|
await client.query('COMMIT');
|
|
@@ -251,27 +602,37 @@ const runTransaction = async (client, begin, fn, capabilities, afterEnd, onBroke
|
|
|
251
602
|
* closed.
|
|
252
603
|
*/
|
|
253
604
|
class PostgresConnectionAdapter {
|
|
254
|
-
constructor(client, capabilities) {
|
|
605
|
+
constructor(client, capabilities, segment) {
|
|
255
606
|
this.client = client;
|
|
256
607
|
this.capabilities = capabilities;
|
|
608
|
+
this.segment = segment;
|
|
257
609
|
this.kind = 'postgres';
|
|
258
610
|
this.ended = false;
|
|
259
611
|
this.transactionOpen = false;
|
|
612
|
+
/** Keeps the first reason the connection cannot be trusted any more. */
|
|
613
|
+
this.onBroken = (error) => {
|
|
614
|
+
if (!this.brokenBy)
|
|
615
|
+
this.brokenBy = error;
|
|
616
|
+
};
|
|
617
|
+
this.query = (text, values) => {
|
|
618
|
+
const live = this.live();
|
|
619
|
+
return segment ? segment.onConnection(live, text, values, this.onBroken) : live.query(text, values);
|
|
620
|
+
};
|
|
260
621
|
}
|
|
261
622
|
async open() {
|
|
262
623
|
// withConnection() checks the connection out before the callback runs.
|
|
263
624
|
}
|
|
264
625
|
async run(statement, parameters) {
|
|
265
|
-
return
|
|
626
|
+
return runWith(this.query, statement, parameters);
|
|
266
627
|
}
|
|
267
628
|
async get(statement, parameters) {
|
|
268
|
-
return
|
|
629
|
+
return getWith(this.query, statement, parameters);
|
|
269
630
|
}
|
|
270
631
|
async all(statement, parameters) {
|
|
271
|
-
return
|
|
632
|
+
return allWith(this.query, statement, parameters);
|
|
272
633
|
}
|
|
273
634
|
async exec(script) {
|
|
274
|
-
await
|
|
635
|
+
await execWith(this.query, script);
|
|
275
636
|
}
|
|
276
637
|
async transaction(fn, options) {
|
|
277
638
|
const begin = beginStatement(options);
|
|
@@ -281,10 +642,7 @@ class PostgresConnectionAdapter {
|
|
|
281
642
|
}
|
|
282
643
|
this.transactionOpen = true;
|
|
283
644
|
try {
|
|
284
|
-
return await runTransaction(client, begin, fn, this.capabilities, this,
|
|
285
|
-
if (!this.brokenBy)
|
|
286
|
-
this.brokenBy = error;
|
|
287
|
-
});
|
|
645
|
+
return await runTransaction(client, begin, fn, this.capabilities, this.segment, this, this.onBroken);
|
|
288
646
|
}
|
|
289
647
|
finally {
|
|
290
648
|
this.transactionOpen = false;
|
|
@@ -398,6 +756,18 @@ class PostgresConnectionAdapter {
|
|
|
398
756
|
* });
|
|
399
757
|
* ```
|
|
400
758
|
*
|
|
759
|
+
* ### A pool the host already holds
|
|
760
|
+
* ```typescript
|
|
761
|
+
* const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
762
|
+
* // The adapter runs on the host's pool; close() leaves it open.
|
|
763
|
+
* const db = new PostgresAdapter({ pool, onError: (error) => console.warn('idle connection failed', error) });
|
|
764
|
+
* ```
|
|
765
|
+
*
|
|
766
|
+
* ### A segment: the adapter's tables in a schema of their own
|
|
767
|
+
* ```typescript
|
|
768
|
+
* const agentos = createPostgresAdapter({ connectionString: process.env.DATABASE_URL, segment: 'agentos' });
|
|
769
|
+
* ```
|
|
770
|
+
*
|
|
401
771
|
* ## Connection failures
|
|
402
772
|
* - The pool drops a connection that fails, and the next query opens a new one.
|
|
403
773
|
* - The adapter never falls back to SQLite. `resolveStorageAdapter()` without
|
|
@@ -431,16 +801,60 @@ export class PostgresAdapter {
|
|
|
431
801
|
'arrays', // Native array data types
|
|
432
802
|
'prepared' // Prepared statements for security/performance
|
|
433
803
|
]);
|
|
804
|
+
/**
|
|
805
|
+
* The pool statements run on, or null while the adapter is closed. Hosts
|
|
806
|
+
* written against 0.6 hand the adapter a Pool by setting this field before
|
|
807
|
+
* `open()`; the adapter then runs on that Pool and ends it on `close()`, as
|
|
808
|
+
* 0.6 did. Keep its name, and keep it a plain field that starts as null.
|
|
809
|
+
*/
|
|
434
810
|
this.pool = null;
|
|
435
811
|
this.pgModule = null;
|
|
812
|
+
/** The error listener the adapter has on a pool, and that pool. */
|
|
813
|
+
this.listening = null;
|
|
814
|
+
this.opening = null;
|
|
815
|
+
this.closing = null;
|
|
816
|
+
/** The runner of statements outside a transaction. */
|
|
817
|
+
this.query = (text, values) => {
|
|
818
|
+
const pool = this.requirePool();
|
|
819
|
+
return this.segmentStatements ? this.segmentStatements.onPool(pool, text, values) : pool.query(text, values);
|
|
820
|
+
};
|
|
436
821
|
// Support both string and object initialization
|
|
437
|
-
|
|
438
|
-
|
|
822
|
+
this.options = typeof options === 'string' ? { connectionString: options } : { ...options };
|
|
823
|
+
const segment = this.options.segment === undefined ? undefined : validateSegment(this.options.segment);
|
|
824
|
+
this.segmentStatements = segment === undefined ? null : new SegmentStatements(segment);
|
|
825
|
+
const given = this.options.pool;
|
|
826
|
+
if (given) {
|
|
827
|
+
if (segment !== undefined) {
|
|
828
|
+
throw new Error("PostgresAdapter: a segment cannot run on a pool its caller made: the segment's search_path is set when each " +
|
|
829
|
+
'connection of the pool starts, and the adapter does not start the connections of a pool it did not make. ' +
|
|
830
|
+
'Give the connection settings and the segment instead of the pool.');
|
|
831
|
+
}
|
|
832
|
+
const settings = POOL_SETTINGS.filter((key) => this.options[key] !== undefined);
|
|
833
|
+
if (settings.length > 0) {
|
|
834
|
+
throw new Error(`PostgresAdapter: ${settings.join(', ')} cannot be given with pool: they configure a pool when one is made, ` +
|
|
835
|
+
'and the pool given was made by its caller. Set them on that pool' +
|
|
836
|
+
(settings.includes('typeParsers') ? " (typeParsers through the pool's own types option)." : '.'));
|
|
837
|
+
}
|
|
838
|
+
this.givenPool = given;
|
|
839
|
+
this.ownsPool = this.options.ownsPool === true;
|
|
840
|
+
this.pool = given;
|
|
841
|
+
this.listenOn(given);
|
|
439
842
|
}
|
|
440
843
|
else {
|
|
441
|
-
this.
|
|
844
|
+
this.givenPool = null;
|
|
845
|
+
this.ownsPool = true;
|
|
846
|
+
if (segment !== undefined) {
|
|
847
|
+
refuseOwnStartupOptions(this.options.connectionString);
|
|
848
|
+
}
|
|
442
849
|
}
|
|
443
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* The pool the adapter runs on: the pool it was given, or the pool it made
|
|
853
|
+
* while it is open. Null while the adapter is closed.
|
|
854
|
+
*/
|
|
855
|
+
getPool() {
|
|
856
|
+
return this.pool;
|
|
857
|
+
}
|
|
444
858
|
/**
|
|
445
859
|
* Lazy loader for pg module to keep the dependency optional and prevent bundling in browser builds.
|
|
446
860
|
*/
|
|
@@ -460,21 +874,69 @@ export class PostgresAdapter {
|
|
|
460
874
|
throw new Error(`[StorageAdapter] Failed to load pg module. Install it with: npm install pg. Error: ${error}`);
|
|
461
875
|
}
|
|
462
876
|
}
|
|
463
|
-
|
|
877
|
+
/**
|
|
878
|
+
* Opens the adapter: makes its pool and checks that a connection can be
|
|
879
|
+
* made (and with a segment, creates the segment's schema when it is
|
|
880
|
+
* missing), or takes up the pool it was given. A failure ends the pool it
|
|
881
|
+
* made, so a later `open()` starts again. Two calls at once open once.
|
|
882
|
+
*/
|
|
883
|
+
open(openOptions) {
|
|
464
884
|
if (this.pool) {
|
|
465
|
-
return;
|
|
885
|
+
return Promise.resolve();
|
|
886
|
+
}
|
|
887
|
+
if (!this.opening) {
|
|
888
|
+
this.opening = this.openNow(openOptions).finally(() => {
|
|
889
|
+
this.opening = null;
|
|
890
|
+
});
|
|
466
891
|
}
|
|
892
|
+
return this.opening;
|
|
893
|
+
}
|
|
894
|
+
async openNow(openOptions) {
|
|
467
895
|
if (__isBrowser) {
|
|
468
896
|
throw new Error('[StorageAdapter] PostgreSQL adapter cannot be opened in a browser environment.');
|
|
469
897
|
}
|
|
898
|
+
if (this.givenPool) {
|
|
899
|
+
if (hasEnded(this.givenPool)) {
|
|
900
|
+
throw new Error('PostgresAdapter: the pool it was given has ended, so the adapter cannot open on it again.');
|
|
901
|
+
}
|
|
902
|
+
this.pool = this.givenPool;
|
|
903
|
+
this.listenOn(this.givenPool);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const connectionString = openOptions?.connectionString ?? this.options.connectionString;
|
|
907
|
+
if (this.segmentStatements) {
|
|
908
|
+
refuseOwnStartupOptions(connectionString);
|
|
909
|
+
}
|
|
470
910
|
// Load pg module dynamically
|
|
471
911
|
const pg = await this.loadPgModule();
|
|
472
|
-
const
|
|
473
|
-
|
|
912
|
+
const pool = new pg.Pool(this.poolConfig(pg, connectionString));
|
|
913
|
+
this.listenOn(pool);
|
|
914
|
+
try {
|
|
915
|
+
// Test connection
|
|
916
|
+
const client = await pool.connect();
|
|
917
|
+
try {
|
|
918
|
+
if (this.segmentStatements) {
|
|
919
|
+
await createSchema(client, this.segmentStatements.segment);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
finally {
|
|
923
|
+
client.release();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
catch (error) {
|
|
927
|
+
// The listener stays on the pool through its end; the adapter forgets it.
|
|
928
|
+
this.listening = null;
|
|
929
|
+
await pool.end().catch(() => undefined);
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
this.pool = pool;
|
|
933
|
+
}
|
|
934
|
+
/** The configuration of the pool the adapter makes. */
|
|
935
|
+
poolConfig(pg, connectionString) {
|
|
474
936
|
const poolConfig = {};
|
|
475
937
|
// Use connection string if provided
|
|
476
|
-
if (
|
|
477
|
-
poolConfig.connectionString =
|
|
938
|
+
if (connectionString) {
|
|
939
|
+
poolConfig.connectionString = connectionString;
|
|
478
940
|
}
|
|
479
941
|
else {
|
|
480
942
|
// Build from individual options
|
|
@@ -520,26 +982,36 @@ export class PostgresAdapter {
|
|
|
520
982
|
if (this.options.ssl !== undefined) {
|
|
521
983
|
poolConfig.ssl = this.options.ssl;
|
|
522
984
|
}
|
|
523
|
-
this.
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
985
|
+
if (this.segmentStatements) {
|
|
986
|
+
// Every connection the pool opens, a replacement included, starts on the
|
|
987
|
+
// segment. A startup setting is the connection's default, so RESET and
|
|
988
|
+
// DISCARD ALL return to it. pg reads PGOPTIONS only for a connection
|
|
989
|
+
// without options of its own, so its options are kept here.
|
|
990
|
+
const searchPath = `-c search_path=${this.segmentStatements.segment},public`;
|
|
991
|
+
const inherited = readEnv('PGOPTIONS')?.trim();
|
|
992
|
+
poolConfig.options = inherited ? `${inherited} ${searchPath}` : searchPath;
|
|
993
|
+
}
|
|
994
|
+
return poolConfig;
|
|
995
|
+
}
|
|
996
|
+
/** Puts the adapter's error listener on `pool`: `onError`, or the warning on a pool it owns. */
|
|
997
|
+
listenOn(pool) {
|
|
998
|
+
const listener = this.options.onError ?? (this.ownsPool ? warnOfIdleError : null);
|
|
999
|
+
if (!listener || typeof pool.on !== 'function' || this.listening?.pool === pool)
|
|
1000
|
+
return;
|
|
1001
|
+
pool.on('error', listener);
|
|
1002
|
+
this.listening = { pool, listener };
|
|
527
1003
|
}
|
|
528
1004
|
async run(statement, parameters) {
|
|
529
|
-
|
|
530
|
-
return runWithExecutor(executor, statement, parameters);
|
|
1005
|
+
return runWith(this.query, statement, parameters);
|
|
531
1006
|
}
|
|
532
1007
|
async get(statement, parameters) {
|
|
533
|
-
|
|
534
|
-
return getWithExecutor(executor, statement, parameters);
|
|
1008
|
+
return getWith(this.query, statement, parameters);
|
|
535
1009
|
}
|
|
536
1010
|
async all(statement, parameters) {
|
|
537
|
-
|
|
538
|
-
return allWithExecutor(executor, statement, parameters);
|
|
1011
|
+
return allWith(this.query, statement, parameters);
|
|
539
1012
|
}
|
|
540
1013
|
async exec(script) {
|
|
541
|
-
|
|
542
|
-
await execWithExecutor(executor, script);
|
|
1014
|
+
await execWith(this.query, script);
|
|
543
1015
|
}
|
|
544
1016
|
/**
|
|
545
1017
|
* Runs `fn` in a transaction on one pooled connection: COMMIT when it
|
|
@@ -558,7 +1030,7 @@ export class PostgresAdapter {
|
|
|
558
1030
|
const watch = watchClient(client);
|
|
559
1031
|
let brokenBy;
|
|
560
1032
|
try {
|
|
561
|
-
return await runTransaction(client, begin, fn, this.capabilities, this, (error) => {
|
|
1033
|
+
return await runTransaction(client, begin, fn, this.capabilities, this.segmentStatements, this, (error) => {
|
|
562
1034
|
if (!brokenBy)
|
|
563
1035
|
brokenBy = error;
|
|
564
1036
|
});
|
|
@@ -583,12 +1055,14 @@ export class PostgresAdapter {
|
|
|
583
1055
|
* connection is closed, which rolls the transaction back, and the call
|
|
584
1056
|
* rejects (pg 8.21 and later report the transaction status). The connection
|
|
585
1057
|
* refuses statements once `fn` has ended. A connection that broke while `fn`
|
|
586
|
-
* held it (the server ended the session) is closed, not returned.
|
|
1058
|
+
* held it (the server ended the session) is closed, not returned. On a
|
|
1059
|
+
* segmented adapter the connection starts in the segment, as every
|
|
1060
|
+
* connection of its pool does.
|
|
587
1061
|
*/
|
|
588
1062
|
async withConnection(fn) {
|
|
589
1063
|
const client = await this.requirePool().connect();
|
|
590
1064
|
const watch = watchClient(client);
|
|
591
|
-
const connection = new PostgresConnectionAdapter(client, this.capabilities);
|
|
1065
|
+
const connection = new PostgresConnectionAdapter(client, this.capabilities, this.segmentStatements);
|
|
592
1066
|
let releaseError;
|
|
593
1067
|
try {
|
|
594
1068
|
const result = await fn(connection);
|
|
@@ -613,21 +1087,52 @@ export class PostgresAdapter {
|
|
|
613
1087
|
client.release(releaseError ?? connection.brokenBy ?? watch.failure());
|
|
614
1088
|
}
|
|
615
1089
|
}
|
|
616
|
-
/**
|
|
1090
|
+
/**
|
|
1091
|
+
* The pool's counts, or null when the adapter is not open. A pool of the
|
|
1092
|
+
* caller's that does not count its connections, as pg's Pool does, reads 0.
|
|
1093
|
+
*/
|
|
617
1094
|
getPoolStats() {
|
|
618
|
-
|
|
1095
|
+
const pool = this.pool;
|
|
1096
|
+
if (!pool) {
|
|
619
1097
|
return null;
|
|
620
1098
|
}
|
|
621
|
-
return { total:
|
|
1099
|
+
return { total: pool.totalCount ?? 0, idle: pool.idleCount ?? 0, waiting: pool.waitingCount ?? 0 };
|
|
622
1100
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* Closes the adapter. A pool the adapter made or owns is ended, once; a pool
|
|
1103
|
+
* of its caller's that it does not own is left open, with the adapter's
|
|
1104
|
+
* listener taken off it. A call made while a close is running waits for it.
|
|
1105
|
+
*/
|
|
1106
|
+
close() {
|
|
1107
|
+
if (!this.closing) {
|
|
1108
|
+
this.closing = this.closeNow().finally(() => {
|
|
1109
|
+
this.closing = null;
|
|
1110
|
+
});
|
|
627
1111
|
}
|
|
1112
|
+
return this.closing;
|
|
628
1113
|
}
|
|
629
|
-
async
|
|
630
|
-
|
|
1114
|
+
async closeNow() {
|
|
1115
|
+
if (this.opening) {
|
|
1116
|
+
await this.opening.catch(() => undefined);
|
|
1117
|
+
}
|
|
1118
|
+
const pool = this.pool;
|
|
1119
|
+
this.pool = null;
|
|
1120
|
+
if (!pool) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const listening = this.listening?.pool === pool ? this.listening : null;
|
|
1124
|
+
this.listening = null;
|
|
1125
|
+
if (this.ownsPool) {
|
|
1126
|
+
// The listener stays on: a connection the pool lets go can still report
|
|
1127
|
+
// its end after end() has resolved.
|
|
1128
|
+
if (!hasEnded(pool)) {
|
|
1129
|
+
await pool.end();
|
|
1130
|
+
}
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (listening && typeof pool.removeListener === 'function') {
|
|
1134
|
+
pool.removeListener('error', listening.listener);
|
|
1135
|
+
}
|
|
631
1136
|
}
|
|
632
1137
|
requirePool() {
|
|
633
1138
|
if (!this.pool) {
|