pipework 0.281.0 → 0.283.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/REFERENCE.md +13 -0
- package/dist/core/config/load.d.ts +3 -1
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +1 -0
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +3 -1
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +3 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/statement-observer.d.ts +58 -0
- package/dist/core/config/statement-observer.d.ts.map +1 -1
- package/dist/core/surface/types.d.ts +1 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/data/db/instrumentation.d.ts +6 -4
- package/dist/data/db/instrumentation.d.ts.map +1 -1
- package/dist/data/db/instrumentation.js +12 -14
- package/dist/data/db/instrumentation.js.map +1 -1
- package/dist/data/db/namespace.d.ts +3 -0
- package/dist/data/db/namespace.d.ts.map +1 -1
- package/dist/data/db/namespace.js +3 -0
- package/dist/data/db/namespace.js.map +1 -1
- package/dist/data/db/pool.js +4 -4
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/query-watch.d.ts +8 -0
- package/dist/data/db/query-watch.d.ts.map +1 -1
- package/dist/data/db/query-watch.js +70 -2
- package/dist/data/db/query-watch.js.map +1 -1
- package/dist/data/db/raw-client-warning.js +1 -1
- package/dist/data/db/raw-client-warning.js.map +1 -1
- package/dist/data/db/rows.d.ts +35 -0
- package/dist/data/db/rows.d.ts.map +1 -0
- package/dist/data/db/rows.js +91 -0
- package/dist/data/db/rows.js.map +1 -0
- package/dist/data/query/postgres-js/session.d.ts +1 -1
- package/dist/data/query/postgres-js/session.d.ts.map +1 -1
- package/dist/data/query/postgres-js/session.js +16 -9
- package/dist/data/query/postgres-js/session.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/test/plugin.js +6 -6
- package/dist/test/plugin.js.map +1 -1
- package/package.json +1 -1
- package/src/core/config/load.ts +4 -1
- package/src/core/config/schema.ts +4 -1
- package/src/core/config/statement-observer.ts +61 -0
- package/src/core/surface/types.ts +1 -0
- package/src/data/db/instrumentation.ts +15 -14
- package/src/data/db/namespace.ts +4 -0
- package/src/data/db/pool.ts +4 -4
- package/src/data/db/query-watch.ts +72 -3
- package/src/data/db/raw-client-warning.ts +1 -1
- package/src/data/db/rows.ts +120 -0
- package/src/data/query/postgres-js/session.ts +20 -11
- package/src/index.ts +2 -1
- package/src/test/plugin.ts +6 -6
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAA8C,MAAM,4BAA4B,CAAA;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAIxE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/test/plugin.js
CHANGED
|
@@ -80,11 +80,11 @@ export async function endTestIsolation(instance, ctx) {
|
|
|
80
80
|
instance.isolation.markEnd();
|
|
81
81
|
}
|
|
82
82
|
async function createIsolatedConnection(testDb, connections, name, observability, withSystem) {
|
|
83
|
-
const
|
|
83
|
+
const watch = createStatementWatch({ database: name, url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled });
|
|
84
|
+
const debug = createDebugFn(observability, watch.noteConnection);
|
|
84
85
|
const client = postgres(testDb.url, { max: 1, onnotice: () => { }, ...(debug ? { debug } : {}) });
|
|
85
86
|
if (isObserverOnly(observability))
|
|
86
87
|
markObservedConnection(client);
|
|
87
|
-
const watch = createStatementWatch({ database: name, url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs });
|
|
88
88
|
const db = drizzle(client, { statementWatch: watch.watch });
|
|
89
89
|
await db.execute(sql.raw('BEGIN'));
|
|
90
90
|
const join = collapseTransactionsToSavepoints(db, client);
|
|
@@ -119,13 +119,13 @@ export async function propagateTestSessionVars(instance, testConnections, tenant
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
export function createSetupConnection(testDb, observability) {
|
|
122
|
-
const
|
|
122
|
+
const watch = observability
|
|
123
|
+
? createStatementWatch({ database: 'setup', url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled })
|
|
124
|
+
: undefined;
|
|
125
|
+
const debug = observability ? createDebugFn(observability, watch?.noteConnection) : undefined;
|
|
123
126
|
const client = postgres(testDb.url, { max: 1, onnotice: () => { }, ...(debug ? { debug } : {}) });
|
|
124
127
|
if (observability && isObserverOnly(observability))
|
|
125
128
|
markObservedConnection(client);
|
|
126
|
-
const watch = observability
|
|
127
|
-
? createStatementWatch({ database: 'setup', url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs })
|
|
128
|
-
: undefined;
|
|
129
129
|
const db = splitLargeInserts(drizzle(client, watch ? { statementWatch: watch.watch } : {}));
|
|
130
130
|
return { drizzle: db, close: async () => { await watch?.close(); await client.end(); } };
|
|
131
131
|
}
|
package/dist/test/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/core/config/load.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { resolveEnvironment, resolveEnvironmentStrict, resolveDbUrl, resolveTest
|
|
|
4
4
|
import { ConfigError } from './errors.js'
|
|
5
5
|
import { resolveEnvVars } from './env-resolve.js'
|
|
6
6
|
import { consumeConfigBaseDir } from './base-dir.js'
|
|
7
|
-
import type { StatementObserver } from './statement-observer.js'
|
|
7
|
+
import type { StatementObserver, StatementSettledObserver } from './statement-observer.js'
|
|
8
8
|
|
|
9
9
|
export interface ResolvedObservability {
|
|
10
10
|
readonly slowQueryMs: number
|
|
@@ -14,6 +14,8 @@ export interface ResolvedObservability {
|
|
|
14
14
|
readonly poolMetricsInterval: number
|
|
15
15
|
/** Caller-supplied per-statement observer; undefined when not configured. */
|
|
16
16
|
readonly onStatement: StatementObserver | undefined
|
|
17
|
+
/** Caller-supplied per-statement completion hook; undefined when not configured. */
|
|
18
|
+
readonly onStatementSettled?: StatementSettledObserver | undefined
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface ResolvedTrace {
|
|
@@ -247,6 +249,7 @@ function buildResolved(config: Blueprint, environment: Environment, baseDir: str
|
|
|
247
249
|
logAllQueries: config.observability?.logAllQueries ?? false,
|
|
248
250
|
poolMetricsInterval: config.observability?.poolMetricsInterval ?? 0,
|
|
249
251
|
onStatement: config.observability?.onStatement,
|
|
252
|
+
onStatementSettled: config.observability?.onStatementSettled,
|
|
250
253
|
},
|
|
251
254
|
trace: {
|
|
252
255
|
hotRetentionDays: config.trace?.hotRetentionDays ?? 7,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod/v4'
|
|
2
|
-
import type { StatementObserver } from './statement-observer.js'
|
|
2
|
+
import type { StatementObserver, StatementSettledObserver } from './statement-observer.js'
|
|
3
3
|
|
|
4
4
|
const SETTING_NAME = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/
|
|
5
5
|
|
|
@@ -181,6 +181,9 @@ const observabilitySchema = z.object({
|
|
|
181
181
|
onStatement: z.custom<StatementObserver>(v => typeof v === 'function', { message: 'Expected a function' })
|
|
182
182
|
.optional()
|
|
183
183
|
.describe('Per-statement observer — fires once per statement written to the wire, including driver-issued BEGIN/SAVEPOINT/COMMIT/ROLLBACK'),
|
|
184
|
+
onStatementSettled: z.custom<StatementSettledObserver>(v => typeof v === 'function', { message: 'Expected a function' })
|
|
185
|
+
.optional()
|
|
186
|
+
.describe('Per-statement completion hook — fires once per statement when it settles, with label, durationMs, rows, connectionId and (on failure) a sanitized error'),
|
|
184
187
|
})
|
|
185
188
|
|
|
186
189
|
const envVarDefSchema = z.discriminatedUnion('type', [
|
|
@@ -46,3 +46,64 @@ export interface StatementEvent {
|
|
|
46
46
|
* database.
|
|
47
47
|
*/
|
|
48
48
|
export type StatementObserver = (event: StatementEvent) => void
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A failed statement's error, reduced to what is safe to hand to a hook:
|
|
52
|
+
* the driver's own error objects carry the statement text and bind parameters
|
|
53
|
+
* (and, on an observed connection, make them enumerable), so the hook never
|
|
54
|
+
* sees the error object itself.
|
|
55
|
+
*/
|
|
56
|
+
export interface StatementSettledError {
|
|
57
|
+
readonly name: string
|
|
58
|
+
readonly message: string
|
|
59
|
+
/** The PostgreSQL SQLSTATE (or the driver's error code) when the error carries one. */
|
|
60
|
+
readonly code?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One statement as it settles — the completion counterpart to `StatementEvent`.
|
|
65
|
+
* Fields are only ever added to this payload, never reshaped.
|
|
66
|
+
*/
|
|
67
|
+
export interface StatementSettledEvent {
|
|
68
|
+
/**
|
|
69
|
+
* The `pipe.labelled` label in effect when the statement settled; null when
|
|
70
|
+
* none. This is the label alone: unlike `query:slow`, it never falls back to
|
|
71
|
+
* the statement text.
|
|
72
|
+
*/
|
|
73
|
+
readonly label: string | null
|
|
74
|
+
/**
|
|
75
|
+
* Wall-clock milliseconds the statement took, measured by the same timer as
|
|
76
|
+
* `query:slow` (so for one statement the two agree exactly).
|
|
77
|
+
*/
|
|
78
|
+
readonly durationMs: number
|
|
79
|
+
/**
|
|
80
|
+
* Rows the statement returned, or, for a write with no `RETURNING`, rows it
|
|
81
|
+
* affected: the command tag's count. 0 for a statement with no count
|
|
82
|
+
* (DDL, `SET`, ...) and for a failed statement.
|
|
83
|
+
*/
|
|
84
|
+
readonly rows: number
|
|
85
|
+
/**
|
|
86
|
+
* The same ordinal `StatementEvent.connectionId` carries for this statement;
|
|
87
|
+
* null when the statement never reached a connection (it failed before being
|
|
88
|
+
* written).
|
|
89
|
+
*/
|
|
90
|
+
readonly connectionId: number | null
|
|
91
|
+
/** Present exactly when the statement failed. Never carries parameters. */
|
|
92
|
+
readonly error?: StatementSettledError
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Caller-supplied completion hook. Fires once per statement that settles, on
|
|
97
|
+
* every statement pipework runs through its own query path: `pipe()`, every
|
|
98
|
+
* `pipe.*` write, `connection.drizzle`, transactions and savepoints, and the
|
|
99
|
+
* test harness's connections. It does not fire for the driver's own
|
|
100
|
+
* transaction control (`BEGIN`/`SAVEPOINT`/`COMMIT`/`ROLLBACK`), nor for the
|
|
101
|
+
* raw `ManagedConnection.client` door, nor for the migration runner's own
|
|
102
|
+
* connection; `onStatement` is the hook that sees wire truth.
|
|
103
|
+
*
|
|
104
|
+
* Pure observation, fired after the statement has settled and its result is on
|
|
105
|
+
* its way back: a hook that throws, or returns a promise that rejects, is
|
|
106
|
+
* logged (`statement:settled-hook-failed`) and never affects the statement.
|
|
107
|
+
* A returned promise is not awaited.
|
|
108
|
+
*/
|
|
109
|
+
export type StatementSettledObserver = (event: StatementSettledEvent) => void | Promise<void>
|
|
@@ -73,6 +73,7 @@ export interface SurfaceContext {
|
|
|
73
73
|
readonly logAllQueries: boolean
|
|
74
74
|
readonly poolMetricsInterval: number
|
|
75
75
|
readonly onStatement: import('../config/statement-observer.js').StatementObserver | undefined
|
|
76
|
+
readonly onStatementSettled?: import('../config/statement-observer.js').StatementSettledObserver | undefined
|
|
76
77
|
}
|
|
77
78
|
readonly raw: unknown
|
|
78
79
|
}
|
|
@@ -5,6 +5,9 @@ import type { ResolvedObservability } from '../../core/config/load.js'
|
|
|
5
5
|
const DEADLOCK_CODE = '40P01'
|
|
6
6
|
const QUERY_TRUNCATE_LENGTH = 500
|
|
7
7
|
|
|
8
|
+
/** Records which connection a statement's parameter array was written on; see `createStatementWatch`. */
|
|
9
|
+
export type ConnectionNote = (connectionId: number, parameters: readonly unknown[]) => void
|
|
10
|
+
|
|
8
11
|
/** postgres.js's `debug` hook signature. */
|
|
9
12
|
type DebugFn = (connection: number, query: string, parameters: unknown[]) => void
|
|
10
13
|
|
|
@@ -15,30 +18,28 @@ type DebugFn = (connection: number, query: string, parameters: unknown[]) => voi
|
|
|
15
18
|
* are today, that mode having already opted into verbose errors.
|
|
16
19
|
*/
|
|
17
20
|
export function isObserverOnly(config: ResolvedObservability): boolean {
|
|
18
|
-
return config.onStatement !== undefined && !config.logAllQueries
|
|
21
|
+
return (config.onStatement !== undefined || config.onStatementSettled !== undefined) && !config.logAllQueries
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* The driver's `debug` hook for a connection, or undefined when nothing is
|
|
23
|
-
* observing. Query logging
|
|
24
|
-
*
|
|
25
|
-
* fire for every statement.
|
|
26
|
+
* observing. Query logging, the caller-supplied `onStatement` observer and the
|
|
27
|
+
* connection note behind `onStatementSettled` are independent: any one alone
|
|
28
|
+
* installs the hook, and with several configured all fire for every statement.
|
|
26
29
|
*/
|
|
27
|
-
export function createDebugFn(config: ResolvedObservability): DebugFn | undefined {
|
|
30
|
+
export function createDebugFn(config: ResolvedObservability, noteConnection?: ConnectionNote): DebugFn | undefined {
|
|
28
31
|
const logQuery = config.logAllQueries ? createQueryLogFn() : undefined
|
|
29
32
|
const observer = config.onStatement
|
|
33
|
+
// The completion hook needs the connection a statement ran on, which only this hook is told.
|
|
34
|
+
const note = config.onStatementSettled !== undefined ? noteConnection : undefined
|
|
30
35
|
|
|
31
|
-
if (!logQuery && !observer) return undefined
|
|
32
|
-
if (!observer) return logQuery
|
|
33
|
-
if (!logQuery) {
|
|
34
|
-
return (connection, query, parameters) => {
|
|
35
|
-
observer({ connectionId: connection, query, parameters })
|
|
36
|
-
}
|
|
37
|
-
}
|
|
36
|
+
if (!logQuery && !observer && !note) return undefined
|
|
37
|
+
if (!observer && !note) return logQuery
|
|
38
38
|
|
|
39
39
|
return (connection, query, parameters) => {
|
|
40
|
-
logQuery(connection, query, parameters)
|
|
41
|
-
|
|
40
|
+
logQuery?.(connection, query, parameters)
|
|
41
|
+
note?.(connection, parameters)
|
|
42
|
+
observer?.({ connectionId: connection, query, parameters })
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
package/src/data/db/namespace.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { serializable } from './serializable.js'
|
|
|
9
9
|
import { withTimeouts } from './timeouts.js'
|
|
10
10
|
import { bulkSet } from './bulk-set.js'
|
|
11
11
|
import { values } from './values.js'
|
|
12
|
+
import { rows } from './rows.js'
|
|
12
13
|
import { fn } from './function-source.js'
|
|
13
14
|
import { update, insert, remove, upsert } from '../write/index.js'
|
|
14
15
|
|
|
@@ -84,6 +85,9 @@ export const pipe = Object.assign(getDb, {
|
|
|
84
85
|
/** Produces a typed VALUES clause for bulk operations — the base primitive for bulkSet and bulkUpsert. */
|
|
85
86
|
values,
|
|
86
87
|
|
|
88
|
+
/** A typed rowset from parallel array parameters, one parameter per column whatever the row count, as a source for `.from()` and joins — `pipe.rows({ g: 'text', id: 'int' }, { g: [...], id: [...] })` renders `unnest($1::text[], $2::int[]) AS rows(g, id)`. For composite-key matches that would otherwise be a union or a JS-side tuple match. */
|
|
89
|
+
rows,
|
|
90
|
+
|
|
87
91
|
/** A set-returning SQL function as a typed source for `.from()` and joins — `pipe.fn('schema.f', { args: [field.uuid()], columns: { path: field.text() } })`. Arguments are bound and cast to their declared types. */
|
|
88
92
|
fn,
|
|
89
93
|
|
package/src/data/db/pool.ts
CHANGED
|
@@ -166,7 +166,10 @@ export function createManagedConnection(
|
|
|
166
166
|
observability?: ResolvedObservability,
|
|
167
167
|
applicationName?: string,
|
|
168
168
|
): ManagedConnection {
|
|
169
|
-
const
|
|
169
|
+
const statementWatch = observability
|
|
170
|
+
? createStatementWatch({ database: config.name, url: config.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled })
|
|
171
|
+
: undefined
|
|
172
|
+
const baseDebug = observability ? createDebugFn(observability, statementWatch?.noteConnection) : undefined
|
|
170
173
|
const slot: ProbeSlot | undefined = baseDebug ? { current: null } : undefined
|
|
171
174
|
const debug = baseDebug && slot
|
|
172
175
|
? ((connection, query, parameters) => {
|
|
@@ -187,9 +190,6 @@ export function createManagedConnection(
|
|
|
187
190
|
const observerOnly = observability !== undefined && isObserverOnly(observability)
|
|
188
191
|
if (observerOnly) markObservedConnection(client)
|
|
189
192
|
|
|
190
|
-
const statementWatch = observability
|
|
191
|
-
? createStatementWatch({ database: config.name, url: config.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs })
|
|
192
|
-
: undefined
|
|
193
193
|
const db = splitLargeInserts(drizzle(client, statementWatch ? { statementWatch: statementWatch.watch } : {}))
|
|
194
194
|
const canceller = createCanceller(config.url)
|
|
195
195
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import postgres from 'postgres'
|
|
2
2
|
import { getLogger } from '../../core/logging/logger.js'
|
|
3
3
|
import { currentQueryLabel } from './query-label.js'
|
|
4
|
+
import { DrizzleQueryError } from '../query/errors.js'
|
|
4
5
|
import type { StatementWatch } from '../query/postgres-js/session.js'
|
|
6
|
+
import type { StatementSettledError, StatementSettledObserver } from '../../core/config/statement-observer.js'
|
|
5
7
|
|
|
6
8
|
const LABEL_SQL_CHARS = 120
|
|
7
9
|
const SAMPLE_TIMEOUT_MS = 2_000
|
|
@@ -23,13 +25,42 @@ export interface StatementWatchOptions {
|
|
|
23
25
|
readonly slowQueryMs: number
|
|
24
26
|
/** Undefined: no timer is ever created. */
|
|
25
27
|
readonly inFlightQueryMs: number | undefined
|
|
28
|
+
/** `observability.onStatementSettled`; undefined: nothing extra is tracked or fired. */
|
|
29
|
+
readonly onSettled?: StatementSettledObserver | undefined
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export interface StatementWatchHandle {
|
|
29
33
|
readonly watch: StatementWatch
|
|
34
|
+
/**
|
|
35
|
+
* Pass to `createDebugFn` for the same pool: the driver's debug hook is the only place that
|
|
36
|
+
* learns a statement's connection. Undefined when no `onSettled` hook is configured.
|
|
37
|
+
*/
|
|
38
|
+
readonly noteConnection: ((connectionId: number, parameters: readonly unknown[]) => void) | undefined
|
|
30
39
|
close(): Promise<void>
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
/** Rows a settled statement returned, or affected for a write: the driver's command-tag count, else the row count. */
|
|
43
|
+
function rowsOf(result: unknown): number {
|
|
44
|
+
if (typeof result !== 'object' || result === null) return 0
|
|
45
|
+
const count = (result as { count?: unknown }).count
|
|
46
|
+
if (typeof count === 'number') return count
|
|
47
|
+
return Array.isArray(result) ? result.length : 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Name, message and code only: the driver's error object carries the statement text and bind parameters. */
|
|
51
|
+
function settledError(raw: unknown): StatementSettledError {
|
|
52
|
+
// Drizzle wraps a driver failure in an error whose message is `Failed query: <sql>\nparams: <values>`: report the cause.
|
|
53
|
+
let err = raw
|
|
54
|
+
while (err instanceof DrizzleQueryError && err.cause !== undefined) err = err.cause
|
|
55
|
+
if (typeof err !== 'object' || err === null) return { name: 'Error', message: String(err) }
|
|
56
|
+
const { name, message, code } = err as { name?: unknown; message?: unknown; code?: unknown }
|
|
57
|
+
return {
|
|
58
|
+
name: typeof name === 'string' ? name : 'Error',
|
|
59
|
+
message: typeof message === 'string' ? message : '',
|
|
60
|
+
...(typeof code === 'string' ? { code } : {}),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
function labelFor(sqlText: string): string {
|
|
34
65
|
return currentQueryLabel() ?? sqlText.replace(/\s+/g, ' ').trim().slice(0, LABEL_SQL_CHARS)
|
|
35
66
|
}
|
|
@@ -41,7 +72,10 @@ function labelFor(sqlText: string): string {
|
|
|
41
72
|
* dedicated connection, never the statement's own and never the app pool.
|
|
42
73
|
*/
|
|
43
74
|
export function createStatementWatch(options: StatementWatchOptions): StatementWatchHandle {
|
|
44
|
-
const { database, url, slowQueryMs, inFlightQueryMs } = options
|
|
75
|
+
const { database, url, slowQueryMs, inFlightQueryMs, onSettled } = options
|
|
76
|
+
// Keyed on the parameter array the driver builds for a statement: the same array reaches the
|
|
77
|
+
// debug hook (with the connection ordinal) and sits on the pending query the session hands us.
|
|
78
|
+
const connections = onSettled ? new WeakMap<object, number>() : undefined
|
|
45
79
|
let sampler: postgres.Sql | undefined
|
|
46
80
|
let sampling = false
|
|
47
81
|
let closed = false
|
|
@@ -72,8 +106,24 @@ export function createStatementWatch(options: StatementWatchOptions): StatementW
|
|
|
72
106
|
return Promise.race([run, timeout]).catch(() => UNKNOWN_BACKEND).finally(() => { clearTimeout(timer) })
|
|
73
107
|
}
|
|
74
108
|
|
|
75
|
-
|
|
109
|
+
function fireSettled(event: Parameters<StatementSettledObserver>[0]): void {
|
|
110
|
+
if (!onSettled) return
|
|
111
|
+
const failed = (err: unknown): void => {
|
|
112
|
+
getLogger().warn({ err, database }, 'statement:settled-hook-failed')
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const out = onSettled(event)
|
|
116
|
+
if (out !== undefined && typeof (out as Promise<void>).then === 'function') void Promise.resolve(out).catch(failed)
|
|
117
|
+
} catch (err) {
|
|
118
|
+
failed(err)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const watch: StatementWatch = async <T>(sqlText: string, run: (seen: (pending: object) => void) => Promise<T>): Promise<T> => {
|
|
76
123
|
const startedMs = Date.now()
|
|
124
|
+
let pendingQuery: object | undefined
|
|
125
|
+
let outcome: { result: unknown } | { error: unknown } | undefined
|
|
126
|
+
const seen = (pending: object): void => { pendingQuery = pending }
|
|
77
127
|
let interval: NodeJS.Timeout | undefined
|
|
78
128
|
let settled = false
|
|
79
129
|
if (inFlightQueryMs !== undefined) {
|
|
@@ -93,7 +143,12 @@ export function createStatementWatch(options: StatementWatchOptions): StatementW
|
|
|
93
143
|
interval.unref()
|
|
94
144
|
}
|
|
95
145
|
try {
|
|
96
|
-
|
|
146
|
+
const result = await run(seen)
|
|
147
|
+
if (onSettled) outcome = { result }
|
|
148
|
+
return result
|
|
149
|
+
} catch (err) {
|
|
150
|
+
if (onSettled) outcome = { error: err }
|
|
151
|
+
throw err
|
|
97
152
|
} finally {
|
|
98
153
|
settled = true
|
|
99
154
|
if (interval !== undefined) clearInterval(interval)
|
|
@@ -101,11 +156,25 @@ export function createStatementWatch(options: StatementWatchOptions): StatementW
|
|
|
101
156
|
if (slowQueryMs > 0 && durationMs > slowQueryMs) {
|
|
102
157
|
getLogger().warn({ label: labelFor(sqlText), durationMs, database }, 'query:slow')
|
|
103
158
|
}
|
|
159
|
+
if (onSettled && outcome !== undefined) {
|
|
160
|
+
const parameters = pendingQuery === undefined ? undefined : (pendingQuery as { parameters?: unknown }).parameters
|
|
161
|
+
const connectionId = typeof parameters === 'object' && parameters !== null ? connections?.get(parameters) ?? null : null
|
|
162
|
+
fireSettled({
|
|
163
|
+
label: currentQueryLabel() ?? null,
|
|
164
|
+
durationMs,
|
|
165
|
+
rows: 'result' in outcome ? rowsOf(outcome.result) : 0,
|
|
166
|
+
connectionId,
|
|
167
|
+
...('error' in outcome ? { error: settledError(outcome.error) } : {}),
|
|
168
|
+
})
|
|
169
|
+
}
|
|
104
170
|
}
|
|
105
171
|
}
|
|
106
172
|
|
|
107
173
|
return {
|
|
108
174
|
watch,
|
|
175
|
+
noteConnection: connections
|
|
176
|
+
? (connectionId, parameters) => { connections.set(parameters, connectionId) }
|
|
177
|
+
: undefined,
|
|
109
178
|
async close() {
|
|
110
179
|
closed = true
|
|
111
180
|
const client = sampler
|
|
@@ -23,7 +23,7 @@ export function warnRawClientIsUnsanitized(database: string): void {
|
|
|
23
23
|
|
|
24
24
|
getBaseLogger().warn(
|
|
25
25
|
{ database },
|
|
26
|
-
'[pipework] raw .client read on a connection with observability.onStatement configured. ' +
|
|
26
|
+
'[pipework] raw .client read on a connection with observability.onStatement or onStatementSettled configured. ' +
|
|
27
27
|
'Queries issued through .client bypass pipework\'s driver error boundary, so their errors ' +
|
|
28
28
|
'serialize bind parameters (postgres.js marks query/parameters/args enumerable whenever its ' +
|
|
29
29
|
'debug hook is installed). Use connection.drizzle — or pipe() inside a managed context — to ' +
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { sql } from './sql.js'
|
|
2
|
+
import type { SQL } from './types.js'
|
|
3
|
+
import { Subquery } from '../query/subquery.js'
|
|
4
|
+
import { SelectionProxyHandler } from '../query/selection-proxy.js'
|
|
5
|
+
|
|
6
|
+
const CAST = /^[a-z_][a-z0-9_]*( [a-z_][a-z0-9_]*)*(\([0-9, ]+\))?$/i
|
|
7
|
+
const COLUMN_NAME = /^[a-z_][a-z0-9_]*$/i
|
|
8
|
+
|
|
9
|
+
type TextLike = 'text' | 'uuid' | 'varchar' | 'char' | 'citext'
|
|
10
|
+
type NumberLike = 'int' | 'integer' | 'int4' | 'smallint' | 'int2' | 'real' | 'float4' | 'double precision' | 'float8'
|
|
11
|
+
type DateLike = 'timestamptz' | 'timestamp' | 'date'
|
|
12
|
+
|
|
13
|
+
/** The JS type a column of this SQL type reads as: text and uuid as string, ints and floats as number, bigint and numeric as string (as postgres.js returns them), dates as Date, boolean as boolean; any other type name as unknown. A cell may be null. */
|
|
14
|
+
export type RowsColumnValue<T extends string> =
|
|
15
|
+
T extends TextLike ? string
|
|
16
|
+
: T extends NumberLike ? number
|
|
17
|
+
: T extends 'bigint' | 'int8' | 'numeric' ? string
|
|
18
|
+
: T extends 'boolean' | 'bool' ? boolean
|
|
19
|
+
: T extends DateLike ? Date | string
|
|
20
|
+
: unknown
|
|
21
|
+
|
|
22
|
+
/** Column name to SQL type name: `{ g: 'text', id: 'int' }`. */
|
|
23
|
+
export type RowsColumns = Readonly<Record<string, string>>
|
|
24
|
+
|
|
25
|
+
/** One array per column, all the same length: `{ g: ['a', 'b'], id: [1, 2] }` is two rows. */
|
|
26
|
+
export type RowsData<TColumns extends RowsColumns> = { readonly [K in keyof TColumns]: ReadonlyArray<RowsColumnValue<TColumns[K]> | null> }
|
|
27
|
+
|
|
28
|
+
type RowsFields<TColumns extends RowsColumns> = {
|
|
29
|
+
[K in keyof TColumns]: SQL<RowsColumnValue<TColumns[K]> | null>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A rowset usable in `.from()` and joins; its columns are properties of it. */
|
|
33
|
+
export type RowsSource<TAlias extends string, TColumns extends RowsColumns> =
|
|
34
|
+
& Subquery<TAlias, RowsFields<TColumns>>
|
|
35
|
+
& RowsFields<TColumns>
|
|
36
|
+
& {
|
|
37
|
+
/** The same rowset under another alias, to use it twice in a query. */
|
|
38
|
+
as<TNew extends string>(alias: TNew): RowsSource<TNew, TColumns>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fail(problem: string, fix: string): never {
|
|
42
|
+
throw new Error(`[pipework] rows: ${problem}\n\n Fix: ${fix}\n`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function quote(name: string): string {
|
|
46
|
+
return `"${name.replace(/"/g, '""')}"`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// One column as a Postgres array literal, sent as a single text parameter and cast to `T[]`. postgres.js
|
|
50
|
+
// serialises a JS array only for the few element types it knows (text, int, uuid ...), so a `timestamptz[]`
|
|
51
|
+
// parameter would throw; a literal string is parsed by Postgres for any type.
|
|
52
|
+
function arrayLiteral(cells: readonly unknown[]): string {
|
|
53
|
+
const item = (v: unknown): string => {
|
|
54
|
+
if (v === null || v === undefined) return 'NULL'
|
|
55
|
+
const text = v instanceof Date ? v.toISOString()
|
|
56
|
+
: typeof v === 'string' ? v
|
|
57
|
+
: typeof v === 'number' || typeof v === 'bigint' || typeof v === 'boolean' ? String(v)
|
|
58
|
+
: JSON.stringify(v)
|
|
59
|
+
return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
|
|
60
|
+
}
|
|
61
|
+
return `{${cells.map(item).join(',')}}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A typed rowset from parallel array parameters, as a source for `.from()` and joins:
|
|
66
|
+
* `rows({ g: 'text', id: 'int' }, { g: ['a', 'b'], id: [1, 2] })` renders
|
|
67
|
+
* `(select * from unnest($1::text[], $2::int[]) as "rows"("g", "id")) "rows"`, one parameter per column
|
|
68
|
+
* whatever the row count, so a 5,000-row key list is two parameters. Each column is read by name, like a
|
|
69
|
+
* table's: `rs.g`. Nulls in an array are NULL cells. Zero rows is valid and returns no rows. Each column
|
|
70
|
+
* is sent as one array-literal text parameter, so any element type Postgres can parse works: Dates as ISO text,
|
|
71
|
+
* objects as JSON (for jsonb). Column names are used as written (not snake-cased); the default alias is `rows`, change it with `.as()`.
|
|
72
|
+
* Throws when the columns are empty, a type is not a plain type name, the data and columns name different keys,
|
|
73
|
+
* or the arrays differ in length.
|
|
74
|
+
*/
|
|
75
|
+
export function rows<const TColumns extends RowsColumns>(columns: TColumns, data: RowsData<TColumns>): RowsSource<'rows', TColumns> {
|
|
76
|
+
const names = Object.keys(columns)
|
|
77
|
+
if (names.length === 0) fail('the column spec is empty.', "declare at least one column, e.g. { id: 'uuid' }.")
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
if (!COLUMN_NAME.test(name) || name === 'as' || name === '_') {
|
|
80
|
+
fail(`column name "${name}" is not usable.`, 'use letters, digits and underscores, not starting with a digit, and not "as" or "_".')
|
|
81
|
+
}
|
|
82
|
+
const type = columns[name]
|
|
83
|
+
if (typeof type !== 'string' || type === '') fail(`column "${name}" has no type.`, `give it a type name: { ${name}: 'text' }.`)
|
|
84
|
+
if (!CAST.test(type)) fail(`column "${name}" has type "${type}", which is not a plain type name.`, "pass a type name such as 'uuid' or 'int'.")
|
|
85
|
+
}
|
|
86
|
+
const given = Object.keys(data)
|
|
87
|
+
const unknownKeys = given.filter(k => !names.includes(k))
|
|
88
|
+
if (unknownKeys.length > 0) fail(`values for ${unknownKeys.map(k => `"${k}"`).join(', ')} have no column in the spec.`, 'add the column to the spec or drop its values.')
|
|
89
|
+
const missing = names.filter(k => !given.includes(k))
|
|
90
|
+
if (missing.length > 0) fail(`the spec names ${missing.map(k => `"${k}"`).join(', ')} but the data has no values for it.`, 'pass an array for every column, [] for none.')
|
|
91
|
+
const lengths = new Set<number>()
|
|
92
|
+
for (const name of names) {
|
|
93
|
+
const col = (data as Record<string, unknown>)[name]
|
|
94
|
+
if (!Array.isArray(col)) fail(`values for "${name}" are not an array.`, 'pass one array per column.')
|
|
95
|
+
lengths.add(col.length)
|
|
96
|
+
}
|
|
97
|
+
if (lengths.size > 1) {
|
|
98
|
+
const detail = names.map(n => `${n}: ${((data as Record<string, readonly unknown[]>)[n]!).length}`).join(', ')
|
|
99
|
+
fail(`the column arrays differ in length (${detail}).`, 'pass arrays of one length; row i is the i-th element of each.')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const build = (alias: string): RowsSource<string, TColumns> => {
|
|
103
|
+
const arrays = names.map(n => {
|
|
104
|
+
return sql`${sql.param(arrayLiteral((data as Record<string, readonly unknown[]>)[n]!))}::${sql.raw(columns[n]!)}[]`
|
|
105
|
+
})
|
|
106
|
+
const nameList = sql.raw(names.map(quote).join(', '))
|
|
107
|
+
const inner = sql`select * from unnest(${sql.join(arrays, sql`, `)}) as ${sql.raw(quote(alias))}(${nameList})`
|
|
108
|
+
const fields: Record<string, SQL> = {}
|
|
109
|
+
for (const n of names) fields[n] = sql.raw(`${quote(alias)}.${quote(n)}`)
|
|
110
|
+
const subquery = new Subquery(inner, fields, alias)
|
|
111
|
+
const selection = new SelectionProxyHandler<Subquery>({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' })
|
|
112
|
+
return new Proxy(subquery, {
|
|
113
|
+
get(target, prop) {
|
|
114
|
+
if (prop === 'as') return (next: string) => build(next)
|
|
115
|
+
return selection.get(target, prop)
|
|
116
|
+
},
|
|
117
|
+
}) as unknown as RowsSource<string, TColumns>
|
|
118
|
+
}
|
|
119
|
+
return build('rows') as RowsSource<'rows', TColumns>
|
|
120
|
+
}
|