kern-db 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder.d.mts +1 -1
- package/dist/builder.mjs +1 -1
- package/dist/{database-B2UHm2En.mjs → database-0i2749JZ.mjs} +30 -2
- package/dist/{driver-CYKarcaa.d.mts → driver-CSfbXOBJ.d.mts} +7 -1
- package/dist/index.d.mts +17 -3
- package/dist/index.mjs +3 -3
- package/dist/node-postgres.d.mts +1 -1
- package/dist/postgres-js.d.mts +1 -1
- package/dist/{query-B8QCiInv.d.mts → query-BrvYBvWC.d.mts} +8 -2
- package/dist/{query-g8iaj0Fx.mjs → query-FKBN7BMY.mjs} +10 -1
- package/dist/testing.d.mts +29 -3
- package/dist/testing.mjs +79 -3
- package/package.json +1 -1
package/dist/builder.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as
|
|
1
|
+
import { B as takeOnlyRow, C as renderFragment, D as bindParameter, E as assertUntypedValue, H as toValueRows, L as createFieldDecodeError, O as encodeParameter, P as ResultMode, R as createResultDecodeError, V as toMissingResult, a as SqlQuery, c as StatementSource, l as startSqlQuery, v as ParameterBinder, y as SqlFragment, z as isRecordKey } from "./query-BrvYBvWC.mjs";
|
|
2
2
|
/** Quotes `name` as a PostgreSQL identifier, which keeps its case and any characters in it. */
|
|
3
3
|
export declare function quoteIdentifier(name: string): string;
|
|
4
4
|
export { type ParameterBinder, type ResultMode, SqlFragment, SqlQuery, type StatementSource, assertUntypedValue, bindParameter, createFieldDecodeError, createResultDecodeError, encodeParameter, isRecordKey, renderFragment, startSqlQuery, takeOnlyRow, toMissingResult, toValueRows };
|
package/dist/builder.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { d as SqlFragment, t as SqlQuery, v as assertUntypedValue, y as bindParameter, a as createFieldDecodeError, o as createResultDecodeError, b as encodeParameter, s as isRecordKey, x as quoteIdentifier, g as renderFragment, n as startSqlQuery, c as takeOnlyRow, l as toMissingResult, u as toValueRows } from "./query-
|
|
1
|
+
export { d as SqlFragment, t as SqlQuery, v as assertUntypedValue, y as bindParameter, a as createFieldDecodeError, o as createResultDecodeError, b as encodeParameter, s as isRecordKey, x as quoteIdentifier, g as renderFragment, n as startSqlQuery, c as takeOnlyRow, l as toMissingResult, u as toValueRows } from "./query-FKBN7BMY.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as decodeBoolean } from "./bytea-DNZ_ftio.mjs";
|
|
2
|
-
import { S as createStatement, _ as sql, f as compileFragment, n as startSqlQuery, p as compileScript, x as quoteIdentifier } from "./query-
|
|
2
|
+
import { S as createStatement, _ as sql, f as compileFragment, n as startSqlQuery, p as compileScript, x as quoteIdentifier } from "./query-FKBN7BMY.mjs";
|
|
3
3
|
import { i as setBrand, n as QueryError, r as isInstance, t as ConnectionError } from "./driver-error-26zcdHeU.mjs";
|
|
4
4
|
import { n as formatArray } from "./array-BLt8ZNR5.mjs";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -210,6 +210,23 @@ var TransactionConnectionError = class TransactionConnectionError extends Connec
|
|
|
210
210
|
}
|
|
211
211
|
};
|
|
212
212
|
/**
|
|
213
|
+
* Tells whether the outcome of an operation that rejected with `error` is unknown, since its connection failed.
|
|
214
|
+
*
|
|
215
|
+
* Pass it the error that a whole operation rejected with: `execute` or `query` of a database outside a transaction, a statement in a session, or a whole `transaction`.
|
|
216
|
+
* A `ConnectionError` of a statement caught in the callback of a transaction is not such an error: the transaction rolls back anyway, and its own error tells its outcome.
|
|
217
|
+
*
|
|
218
|
+
* - `TransactionConnectionError` of phase `commit` gives `true`, since `COMMIT` may have succeeded.
|
|
219
|
+
* - `TransactionConnectionError` of another phase gives `false`, since the transaction was not committed.
|
|
220
|
+
* - Another `ConnectionError` gives `true`, since a statement outside a transaction may have run before its connection failed.
|
|
221
|
+
* - Any other error gives `false`, such as a `QueryError`, or a `ResultDecodeError` of a statement that has run: `false` does not mean that the operation applied nothing.
|
|
222
|
+
*
|
|
223
|
+
* Classification depends only on the error class, so failures before the statement was sent, such as a failure to acquire a connection, also give `true`.
|
|
224
|
+
*/
|
|
225
|
+
function isOutcomeUnknown(error) {
|
|
226
|
+
if (error instanceof TransactionConnectionError) return error.phase === "commit";
|
|
227
|
+
return error instanceof ConnectionError;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
213
230
|
* Reserved connection of a transaction of `transaction` that records the first `ConnectionError` of its statements and scripts, including those of its nested levels.
|
|
214
231
|
*
|
|
215
232
|
* A failure before `COMMIT` is sent makes the transaction roll back rather than commit, since the statement may not have run.
|
|
@@ -1419,6 +1436,16 @@ function createDatabaseClosedError(operation) {
|
|
|
1419
1436
|
return /* @__PURE__ */ new Error(`Cannot ${operation}: the database is closed`);
|
|
1420
1437
|
}
|
|
1421
1438
|
// src/database.ts
|
|
1439
|
+
/** Marks a database of `createDatabase` of any copy of kern-db, which `isDatabase` of another copy recognizes. */
|
|
1440
|
+
const databaseBrand = /* @__PURE__ */ Symbol.for("kern-db.Database");
|
|
1441
|
+
/**
|
|
1442
|
+
* Checks whether `value` is a database of `createDatabase`, also of another copy of kern-db, such as to reject a client of PostgreSQL or a `Db` of kern-orm passed instead of a database.
|
|
1443
|
+
*
|
|
1444
|
+
* It checks the brand that `createDatabase` sets, so it rejects a spread copy of a database.
|
|
1445
|
+
*/
|
|
1446
|
+
function isDatabase(value) {
|
|
1447
|
+
return typeof value === "object" && value !== null && value[databaseBrand] === true;
|
|
1448
|
+
}
|
|
1422
1449
|
const coordinators = /* @__PURE__ */ new WeakMap();
|
|
1423
1450
|
/**
|
|
1424
1451
|
* Returns the transaction coordinator of a database of `createDatabase`, such as for the test helpers.
|
|
@@ -1453,7 +1480,8 @@ function createDatabase({ driver, ...options }) {
|
|
|
1453
1480
|
close: () => coordinator.close(),
|
|
1454
1481
|
[Symbol.asyncDispose]: () => coordinator.close()
|
|
1455
1482
|
};
|
|
1483
|
+
Object.defineProperty(database, databaseBrand, { value: true });
|
|
1456
1484
|
coordinators.set(database, coordinator);
|
|
1457
1485
|
return database;
|
|
1458
1486
|
}
|
|
1459
|
-
export { TransactionConnectionError as i, getCoordinator as n,
|
|
1487
|
+
export { TransactionConnectionError as a, isQueryError as i, getCoordinator as n, isOutcomeUnknown as o, isDatabase as r, pause as s, createDatabase as t };
|
|
@@ -49,6 +49,12 @@ interface Driver {
|
|
|
49
49
|
*/
|
|
50
50
|
close(): Promise<void>;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether `value` is a driver: an object with the methods of `Driver`, such as to reject a client of PostgreSQL passed instead of its driver.
|
|
54
|
+
*
|
|
55
|
+
* It checks the methods rather than a brand, since an application may write a driver of its own.
|
|
56
|
+
*/
|
|
57
|
+
declare function isDriver(value: unknown): value is Driver;
|
|
52
58
|
/** Connection reserved by a driver, such as for a transaction or a session. */
|
|
53
59
|
interface Connection {
|
|
54
60
|
/**
|
|
@@ -68,4 +74,4 @@ interface Connection {
|
|
|
68
74
|
*/
|
|
69
75
|
release(): void | Promise<void>;
|
|
70
76
|
}
|
|
71
|
-
export { TextValue as a, SqlStatement as i, Driver as n, createStatement as o, DriverResult as r, Connection as t };
|
|
77
|
+
export { TextValue as a, SqlStatement as i, Driver as n, createStatement as o, DriverResult as r, isDriver as s, Connection as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as TextValue, i as SqlStatement, n as Driver, o as createStatement, r as DriverResult, t as Connection } from "./driver-
|
|
1
|
+
import { a as TextValue, i as SqlStatement, n as Driver, o as createStatement, r as DriverResult, s as isDriver, t as Connection } from "./driver-CSfbXOBJ.mjs";
|
|
2
2
|
import { a as BaseDerivableType, c as Type, d as TypeOutput, f as TypeTransform, h as typeValues, i as AnyType, l as TypeInput, m as isType, n as AcceptedValues, o as DerivableType, p as defineType, r as AnyDerivableType, s as DerivableTypeWithParameters, t as AcceptedNull, u as TypeOptions } from "./type-CbrtxvDF.mjs";
|
|
3
|
-
import { A as
|
|
3
|
+
import { A as OneRow, F as ResultRows, I as ValueRows, M as ResultByRows, N as ResultDecodeError, S as param, T as UntypedValue, _ as isDatabase, a as SqlQuery, b as SqlValue, d as DatabaseOptions, f as IsolationLevel, g as createDatabase, h as TransactionOptions, i as InitialSqlQueryState, j as OneValueRows, k as NotFoundError, m as SessionOptions, n as DecodedFields, o as SqlQueryResult, p as PollLockOptions, r as FieldTypes, s as SqlQueryState, t as AnySqlQuery, u as Database, w as sql, x as identifier, y as SqlFragment } from "./query-BrvYBvWC.mjs";
|
|
4
4
|
/**
|
|
5
5
|
* Error of a statement that PostgreSQL rejected, such as for a violated constraint; the connection of the statement stays usable.
|
|
6
6
|
*
|
|
@@ -357,4 +357,18 @@ export declare class TransactionConnectionError extends ConnectionError {
|
|
|
357
357
|
/** Tells whether `value` is an instance of the class, including one of `TransactionConnectionError` of another copy of kern-db. */
|
|
358
358
|
static [Symbol.hasInstance]<T>(this: abstract new (...args: never) => T, value: unknown): value is T;
|
|
359
359
|
}
|
|
360
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Tells whether the outcome of an operation that rejected with `error` is unknown, since its connection failed.
|
|
362
|
+
*
|
|
363
|
+
* Pass it the error that a whole operation rejected with: `execute` or `query` of a database outside a transaction, a statement in a session, or a whole `transaction`.
|
|
364
|
+
* A `ConnectionError` of a statement caught in the callback of a transaction is not such an error: the transaction rolls back anyway, and its own error tells its outcome.
|
|
365
|
+
*
|
|
366
|
+
* - `TransactionConnectionError` of phase `commit` gives `true`, since `COMMIT` may have succeeded.
|
|
367
|
+
* - `TransactionConnectionError` of another phase gives `false`, since the transaction was not committed.
|
|
368
|
+
* - Another `ConnectionError` gives `true`, since a statement outside a transaction may have run before its connection failed.
|
|
369
|
+
* - Any other error gives `false`, such as a `QueryError`, or a `ResultDecodeError` of a statement that has run: `false` does not mean that the operation applied nothing.
|
|
370
|
+
*
|
|
371
|
+
* Classification depends only on the error class, so failures before the statement was sent, such as a failure to acquire a connection, also give `true`.
|
|
372
|
+
*/
|
|
373
|
+
export declare function isOutcomeUnknown(error: unknown): boolean;
|
|
374
|
+
export { type AcceptedNull, type AcceptedValues, type AnyDerivableType, type AnySqlQuery, type AnyType, type BaseDerivableType, type Connection, type Database, type DatabaseOptions, type DecodedFields, type DerivableType, type DerivableTypeWithParameters, type Driver, type DriverErrorFields, type DriverResult, type FieldTypes, type InitialSqlQueryState, type IsolationLevel, NotFoundError, type OneRow, type OneValueRows, type PollLockOptions, type QueryErrorCode, type QueryErrorCondition, type ResultByRows, ResultDecodeError, type ResultRows, type SessionOptions, type SqlFragment, type SqlQuery, type SqlQueryResult, type SqlQueryState, type SqlStatement, type SqlValue, type TextValue, type TransactionOptions, type Type, type TypeInput, type TypeOptions, type TypeOutput, type TypeTransform, type UntypedValue, type ValueRows, createDatabase, createStatement, defineType, identifier, isDatabase, isDriver, isType, param, sql, type typeValues };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as defineType, s as isType } from "./bytea-DNZ_ftio.mjs";
|
|
2
|
-
import { S as createStatement, _ as sql, h as param, i as ResultDecodeError, m as identifier, r as NotFoundError } from "./query-
|
|
2
|
+
import { C as isDriver, S as createStatement, _ as sql, h as param, i as ResultDecodeError, m as identifier, r as NotFoundError } from "./query-FKBN7BMY.mjs";
|
|
3
3
|
import { a as toDriverError, n as QueryError, t as ConnectionError } from "./driver-error-26zcdHeU.mjs";
|
|
4
|
-
import {
|
|
5
|
-
export { ConnectionError, NotFoundError, QueryError, ResultDecodeError, TransactionConnectionError, createDatabase, createStatement, defineType, identifier, isQueryError, isType, param, sql, toDriverError };
|
|
4
|
+
import { a as TransactionConnectionError, i as isQueryError, o as isOutcomeUnknown, r as isDatabase, t as createDatabase } from "./database-0i2749JZ.mjs";
|
|
5
|
+
export { ConnectionError, NotFoundError, QueryError, ResultDecodeError, TransactionConnectionError, createDatabase, createStatement, defineType, identifier, isDatabase, isDriver, isOutcomeUnknown, isQueryError, isType, param, sql, toDriverError };
|
package/dist/node-postgres.d.mts
CHANGED
package/dist/postgres-js.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as TextValue, i as SqlStatement, n as Driver, r as DriverResult } from "./driver-
|
|
1
|
+
import { a as TextValue, i as SqlStatement, n as Driver, r as DriverResult } from "./driver-CSfbXOBJ.mjs";
|
|
2
2
|
import { c as Type, d as TypeOutput, i as AnyType } from "./type-CbrtxvDF.mjs";
|
|
3
3
|
/**
|
|
4
4
|
* How a query returns its rows: `many` as an array; `one` as one row, rejecting without it; `nullable` as one row or `null` without it; `optional` as one row or `undefined` without it.
|
|
@@ -267,6 +267,12 @@ interface DatabaseOptions {
|
|
|
267
267
|
*/
|
|
268
268
|
onReleaseError?: (error: unknown) => void;
|
|
269
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Checks whether `value` is a database of `createDatabase`, also of another copy of kern-db, such as to reject a client of PostgreSQL or a `Db` of kern-orm passed instead of a database.
|
|
272
|
+
*
|
|
273
|
+
* It checks the brand that `createDatabase` sets, so it rejects a spread copy of a database.
|
|
274
|
+
*/
|
|
275
|
+
declare function isDatabase(value: unknown): value is Database;
|
|
270
276
|
/** Creates a database over a driver. */
|
|
271
277
|
declare function createDatabase({ driver, ...options }: DatabaseOptions): Database;
|
|
272
278
|
/** Compiles the statement of a query of SQL text, each time the query executes or compiles, so that what it reads when it compiles, such as a PostgreSQL schema of the current tenant, is current. */
|
|
@@ -419,4 +425,4 @@ declare class SqlQuery<out S extends SqlQueryState> {
|
|
|
419
425
|
type AnySqlQuery = SqlQuery<any>;
|
|
420
426
|
/** Starts the query of the statement that `source` compiles, executed on `database`, which resolves to the number of the rows of its command tag. */
|
|
421
427
|
declare function startSqlQuery(source: StatementSource, database: Pick<Database, "execute">): SqlQuery<InitialSqlQueryState>;
|
|
422
|
-
export {
|
|
428
|
+
export { OneRow as A, takeOnlyRow as B, renderFragment as C, bindParameter as D, assertUntypedValue as E, ResultRows as F, toValueRows as H, ValueRows as I, createFieldDecodeError as L, ResultByRows as M, ResultDecodeError as N, encodeParameter as O, ResultMode as P, createResultDecodeError as R, param as S, UntypedValue as T, toMissingResult as V, isDatabase as _, SqlQuery as a, SqlValue as b, StatementSource as c, DatabaseOptions as d, IsolationLevel as f, createDatabase as g, TransactionOptions as h, InitialSqlQueryState as i, OneValueRows as j, NotFoundError as k, startSqlQuery as l, SessionOptions as m, DecodedFields as n, SqlQueryResult as o, PollLockOptions as p, FieldTypes as r, SqlQueryState as s, AnySqlQuery as t, Database as u, ParameterBinder as v, sql as w, identifier as x, SqlFragment as y, isRecordKey as z };
|
|
@@ -7,6 +7,15 @@ function createStatement(text, parameters = []) {
|
|
|
7
7
|
parameters
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Checks whether `value` is a driver: an object with the methods of `Driver`, such as to reject a client of PostgreSQL passed instead of its driver.
|
|
12
|
+
*
|
|
13
|
+
* It checks the methods rather than a brand, since an application may write a driver of its own.
|
|
14
|
+
*/
|
|
15
|
+
function isDriver(value) {
|
|
16
|
+
const driver = value;
|
|
17
|
+
return (typeof driver === "object" || typeof driver === "function") && driver !== null && typeof driver.execute === "function" && typeof driver.executeScript === "function" && typeof driver.acquire === "function" && typeof driver.close === "function";
|
|
18
|
+
}
|
|
10
19
|
// src/identifier.ts
|
|
11
20
|
/** Quotes `name` as a PostgreSQL identifier, which keeps its case and any characters in it. */
|
|
12
21
|
function quoteIdentifier(name) {
|
|
@@ -427,4 +436,4 @@ function requireType(value, what) {
|
|
|
427
436
|
if (!isType(value)) throw new TypeError(`${what} takes a type`);
|
|
428
437
|
return value;
|
|
429
438
|
}
|
|
430
|
-
export { createStatement as S, sql as _, createFieldDecodeError as a, encodeParameter as b, takeOnlyRow as c, SqlFragment as d, compileFragment as f, renderFragment as g, param as h, ResultDecodeError as i, toMissingResult as l, identifier as m, startSqlQuery as n, createResultDecodeError as o, compileScript as p, NotFoundError as r, isRecordKey as s, SqlQuery as t, toValueRows as u, assertUntypedValue as v, quoteIdentifier as x, bindParameter as y };
|
|
439
|
+
export { isDriver as C, createStatement as S, sql as _, createFieldDecodeError as a, encodeParameter as b, takeOnlyRow as c, SqlFragment as d, compileFragment as f, renderFragment as g, param as h, ResultDecodeError as i, toMissingResult as l, identifier as m, startSqlQuery as n, createResultDecodeError as o, compileScript as p, NotFoundError as r, isRecordKey as s, SqlQuery as t, toValueRows as u, assertUntypedValue as v, quoteIdentifier as x, bindParameter as y };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as Driver } from "./driver-
|
|
2
|
-
import { f as IsolationLevel, u as Database } from "./query-
|
|
1
|
+
import { n as Driver, t as Connection } from "./driver-CSfbXOBJ.mjs";
|
|
2
|
+
import { f as IsolationLevel, u as Database } from "./query-BrvYBvWC.mjs";
|
|
3
3
|
/** Test fixture of `useTestFixture`, open while the tests of its `describe` block or test file run. */
|
|
4
4
|
interface TestFixture<T = unknown> {
|
|
5
5
|
/** Result of the seed of the fixture; reading it while the fixture is not open throws. */
|
|
@@ -98,4 +98,30 @@ interface DriverContractRunner extends TestHooks {
|
|
|
98
98
|
* They run in the empty database of its clients and leave it unchanged.
|
|
99
99
|
*/
|
|
100
100
|
export declare function describeDriverContract(options: DriverContractOptions, runner: DriverContractRunner): void;
|
|
101
|
-
|
|
101
|
+
/** Options of `waitForLock` and `waitForLockedStatement`. */
|
|
102
|
+
interface LockWaitOptions {
|
|
103
|
+
/** Milliseconds to wait before rejecting; 2000 by default, shorter than the default timeout of a test, so that the helper rejects with its own error. */
|
|
104
|
+
timeout?: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolves to the process ID of the backend of PostgreSQL that runs the statements of `target`, `pg_backend_pid()`.
|
|
108
|
+
*
|
|
109
|
+
* Call it on the connection that will wait for a lock, such as inside its transaction or session: on the pool of a database or a driver, it gives the backend of whichever connection runs the statement.
|
|
110
|
+
*/
|
|
111
|
+
export declare function getBackendPid(target: Pick<Connection, "execute">): Promise<number>;
|
|
112
|
+
/**
|
|
113
|
+
* Waits until the backend `pid` waits for a lock, such as of a row or an advisory lock that another transaction holds, and rejects when it does not in time.
|
|
114
|
+
*
|
|
115
|
+
* `observer` runs the checks: a database, a driver, or a connection other than the one that waits.
|
|
116
|
+
* It reads the locks with `pg_blocking_pids`, which reads the current lock state even inside a transaction.
|
|
117
|
+
*/
|
|
118
|
+
export declare function waitForLock(observer: Pick<Connection, "execute">, pid: number, options?: LockWaitOptions): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Waits until a backend of the current database other than that of `observer` waits for a lock in a statement whose text contains `fragment`, and resolves to its backend PID.
|
|
121
|
+
*
|
|
122
|
+
* It finds a backend that the test does not know, such as one of the pool of the code under test.
|
|
123
|
+
* `observer` runs the checks: a database, a driver, or a connection other than the one that waits.
|
|
124
|
+
* It rejects when no such backend waits in time.
|
|
125
|
+
*/
|
|
126
|
+
export declare function waitForLockedStatement(observer: Pick<Connection, "execute">, fragment: string, options?: LockWaitOptions): Promise<number>;
|
|
127
|
+
export type { DriverContractOptions, DriverContractRunner, LockWaitOptions, OpenedTestFixture, PendingTestFixture, TestFixture, TestFixtureOptions, TestHelpers, TestHooks, TestTransactionOptions };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { r as boolean, t as bytea } from "./bytea-DNZ_ftio.mjs";
|
|
2
|
-
import { S as createStatement, _ as sql, h as param, m as identifier } from "./query-
|
|
2
|
+
import { S as createStatement, _ as sql, h as param, m as identifier } from "./query-FKBN7BMY.mjs";
|
|
3
3
|
import { n as QueryError, t as ConnectionError } from "./driver-error-26zcdHeU.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { a as TransactionConnectionError, n as getCoordinator, s as pause, t as createDatabase } from "./database-0i2749JZ.mjs";
|
|
5
5
|
import { t as array } from "./array-BLt8ZNR5.mjs";
|
|
6
6
|
import { _ as interval, a as bigint, b as timestamptz, c as numeric, f as inet, g as date, h as enumOf, m as jsonb, n as char, o as doublePrecision, r as text, s as integer, t as uuid, v as time, y as timestamp } from "./types-Dl2oicjV.mjs";
|
|
7
7
|
import assert from "node:assert/strict";
|
|
@@ -738,4 +738,80 @@ function describeDriverContract(options, runner) {
|
|
|
738
738
|
});
|
|
739
739
|
});
|
|
740
740
|
}
|
|
741
|
-
|
|
741
|
+
// src/lock-wait.ts
|
|
742
|
+
/** Time that the helpers wait by default, in milliseconds. */
|
|
743
|
+
const defaultTimeout = 2e3;
|
|
744
|
+
/** Pause between two checks in milliseconds, so that waiting does not keep the observer busy. */
|
|
745
|
+
const pollInterval = 5;
|
|
746
|
+
/**
|
|
747
|
+
* Resolves to the process ID of the backend of PostgreSQL that runs the statements of `target`, `pg_backend_pid()`.
|
|
748
|
+
*
|
|
749
|
+
* Call it on the connection that will wait for a lock, such as inside its transaction or session: on the pool of a database or a driver, it gives the backend of whichever connection runs the statement.
|
|
750
|
+
*/
|
|
751
|
+
async function getBackendPid(target) {
|
|
752
|
+
const { rows } = await target.execute(createStatement("SELECT pg_backend_pid()"));
|
|
753
|
+
return Number(rows[0][0]);
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Waits until the backend `pid` waits for a lock, such as of a row or an advisory lock that another transaction holds, and rejects when it does not in time.
|
|
757
|
+
*
|
|
758
|
+
* `observer` runs the checks: a database, a driver, or a connection other than the one that waits.
|
|
759
|
+
* It reads the locks with `pg_blocking_pids`, which reads the current lock state even inside a transaction.
|
|
760
|
+
*/
|
|
761
|
+
async function waitForLock(observer, pid, options = {}) {
|
|
762
|
+
const timeout = options.timeout ?? defaultTimeout;
|
|
763
|
+
const statement = createStatement("SELECT cardinality(pg_blocking_pids($1::integer)) > 0", [String(pid)]);
|
|
764
|
+
await poll(timeout, `Backend ${pid} did not wait for a lock within ${timeout} ms`, async () => {
|
|
765
|
+
const { rows } = await observer.execute(statement);
|
|
766
|
+
return rows[0][0] === "t" ? true : void 0;
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Waits until a backend of the current database other than that of `observer` waits for a lock in a statement whose text contains `fragment`, and resolves to its backend PID.
|
|
771
|
+
*
|
|
772
|
+
* It finds a backend that the test does not know, such as one of the pool of the code under test.
|
|
773
|
+
* `observer` runs the checks: a database, a driver, or a connection other than the one that waits.
|
|
774
|
+
* It rejects when no such backend waits in time.
|
|
775
|
+
*/
|
|
776
|
+
async function waitForLockedStatement(observer, fragment, options = {}) {
|
|
777
|
+
const timeout = options.timeout ?? defaultTimeout;
|
|
778
|
+
const clearSnapshot = createStatement("SELECT pg_stat_clear_snapshot()");
|
|
779
|
+
const statement = createStatement(`SELECT pid FROM pg_stat_activity
|
|
780
|
+
WHERE datname = current_database() AND pid <> pg_backend_pid()
|
|
781
|
+
AND position($1::text IN query) > 0 AND cardinality(pg_blocking_pids(pid)) > 0
|
|
782
|
+
ORDER BY pid LIMIT 1`, [fragment]);
|
|
783
|
+
return await poll(timeout, `No statement with "${fragment}" waited for a lock within ${timeout} ms`, async (isWaiting) => {
|
|
784
|
+
await observer.execute(clearSnapshot);
|
|
785
|
+
if (!isWaiting()) return;
|
|
786
|
+
const { rows } = await observer.execute(statement);
|
|
787
|
+
return rows.length > 0 ? Number(rows[0][0]) : void 0;
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Calls `check` until it resolves to a value other than `undefined`, pausing between the calls, and resolves to that value.
|
|
792
|
+
*
|
|
793
|
+
* It rejects with `message` once `timeout` passes, also while a check still runs, such as on a pool that has no free connection; such a check settles without the helper.
|
|
794
|
+
* A check must send no further statement once `isWaiting` returns `false`.
|
|
795
|
+
*/
|
|
796
|
+
async function poll(timeout, message, check) {
|
|
797
|
+
let waiting = true;
|
|
798
|
+
const isWaiting = () => waiting;
|
|
799
|
+
let timer;
|
|
800
|
+
const expired = new Promise((_, reject) => {
|
|
801
|
+
timer = setTimeout(() => {
|
|
802
|
+
waiting = false;
|
|
803
|
+
reject(new Error(message));
|
|
804
|
+
}, timeout);
|
|
805
|
+
});
|
|
806
|
+
try {
|
|
807
|
+
for (;;) {
|
|
808
|
+
const value = await Promise.race([check(isWaiting), expired]);
|
|
809
|
+
if (value !== void 0) return value;
|
|
810
|
+
await Promise.race([pause(pollInterval, void 0), expired]);
|
|
811
|
+
}
|
|
812
|
+
} finally {
|
|
813
|
+
waiting = false;
|
|
814
|
+
clearTimeout(timer);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
export { createTestHelpers, describeDriverContract, getBackendPid, waitForLock, waitForLockedStatement };
|