pipework 0.283.0 → 0.285.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/README.md +6 -0
- package/dist/core/config/load.d.ts +8 -0
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +4 -0
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +4 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +8 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +5 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/statement-observer.d.ts +33 -9
- package/dist/core/config/statement-observer.d.ts.map +1 -1
- package/dist/core/surface/types.d.ts +4 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/data/db/namespace.d.ts +1 -1
- package/dist/data/db/namespace.d.ts.map +1 -1
- package/dist/data/db/namespace.js +1 -1
- package/dist/data/db/namespace.js.map +1 -1
- package/dist/data/db/pool.js +2 -2
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/query-size.d.ts +18 -0
- package/dist/data/db/query-size.d.ts.map +1 -0
- package/dist/data/db/query-size.js +68 -0
- package/dist/data/db/query-size.js.map +1 -0
- package/dist/data/db/query-watch.d.ts +16 -0
- package/dist/data/db/query-watch.d.ts.map +1 -1
- package/dist/data/db/query-watch.js +57 -9
- package/dist/data/db/query-watch.js.map +1 -1
- package/dist/test/plugin.js +3 -3
- package/dist/test/plugin.js.map +1 -1
- package/package.json +6 -2
- package/src/core/config/load.ts +12 -0
- package/src/core/config/schema.ts +5 -1
- package/src/core/config/statement-observer.ts +33 -9
- package/src/core/surface/types.ts +4 -0
- package/src/data/db/namespace.ts +1 -1
- package/src/data/db/pool.ts +2 -2
- package/src/data/db/query-size.ts +63 -0
- package/src/data/db/query-watch.ts +71 -9
- package/src/test/plugin.ts +3 -3
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Size measurements behind `query:large`. Each runs only when its budget is enabled (> 0), after the
|
|
2
|
+
// statement has settled, so a pool with every budget at 0 never calls into this file.
|
|
3
|
+
/** Bytes assumed for a number, bigint, boolean, Date or other fixed-width scalar parameter. */
|
|
4
|
+
const SCALAR_BYTES = 8;
|
|
5
|
+
/**
|
|
6
|
+
* Estimated bytes of the bind parameters of one statement. Strings count their UTF-8 length
|
|
7
|
+
* (no copy is made), Buffers/typed arrays their byte length, arrays the sum of their elements,
|
|
8
|
+
* numbers/booleans/dates a fixed 8, null/undefined 0. A plain object is a JSON value the driver
|
|
9
|
+
* will serialise: it is measured by `JSON.stringify` length, which serialises it once more than
|
|
10
|
+
* the driver does; ORM json columns arrive already stringified (counted as strings, free), so
|
|
11
|
+
* only a raw object parameter pays that. An unserialisable object counts as one scalar.
|
|
12
|
+
*/
|
|
13
|
+
export function parameterBytes(parameters) {
|
|
14
|
+
if (!Array.isArray(parameters))
|
|
15
|
+
return 0;
|
|
16
|
+
let total = 0;
|
|
17
|
+
for (const value of parameters)
|
|
18
|
+
total += valueBytes(value);
|
|
19
|
+
return total;
|
|
20
|
+
}
|
|
21
|
+
function valueBytes(value) {
|
|
22
|
+
if (value === null || value === undefined)
|
|
23
|
+
return 0;
|
|
24
|
+
switch (typeof value) {
|
|
25
|
+
case 'string': return Buffer.byteLength(value);
|
|
26
|
+
case 'object': break;
|
|
27
|
+
default: return SCALAR_BYTES;
|
|
28
|
+
}
|
|
29
|
+
if (value instanceof Date)
|
|
30
|
+
return SCALAR_BYTES;
|
|
31
|
+
if (ArrayBuffer.isView(value))
|
|
32
|
+
return value.byteLength;
|
|
33
|
+
if (value instanceof ArrayBuffer)
|
|
34
|
+
return value.byteLength;
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
let total = 0;
|
|
37
|
+
for (const item of value)
|
|
38
|
+
total += valueBytes(item);
|
|
39
|
+
return total;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const json = JSON.stringify(value);
|
|
43
|
+
return json === undefined ? 0 : json.length;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return SCALAR_BYTES;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Single left-to-right scan: a quoted literal or quoted identifier is consumed whole, so a keyword
|
|
50
|
+
// inside one is never counted; what is left are bare UNION / INTERSECT / EXCEPT words.
|
|
51
|
+
const SET_OPERATION = /'[^']*(?:''[^']*)*'|"[^"]*(?:""[^"]*)*"|\b(?:union|intersect|except)\b/gi;
|
|
52
|
+
/**
|
|
53
|
+
* Arms of the set operations in a statement's SQL text: set-operator keywords + 1, 0 when there
|
|
54
|
+
* are none. This is a text count at every nesting depth (a union inside a subquery or CTE costs the
|
|
55
|
+
* planner the same as one at the top), not a parse. Pipework sends values as bind parameters, so
|
|
56
|
+
* no keyword can appear inside a string value; a literal written with `sql.raw` is skipped by
|
|
57
|
+
* the scan. Comments and dollar-quoted bodies are not understood and can over-count.
|
|
58
|
+
*/
|
|
59
|
+
export function setOperationArms(sqlText) {
|
|
60
|
+
let operators = 0;
|
|
61
|
+
for (const match of sqlText.matchAll(SET_OPERATION)) {
|
|
62
|
+
const first = match[0][0];
|
|
63
|
+
if (first !== "'" && first !== '"')
|
|
64
|
+
operators++;
|
|
65
|
+
}
|
|
66
|
+
return operators === 0 ? 0 : operators + 1;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=query-size.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-size.js","sourceRoot":"","sources":["../../../src/data/db/query-size.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,sFAAsF;AAEtF,+FAA+F;AAC/F,MAAM,YAAY,GAAG,CAAC,CAAA;AAEtB;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,UAAmB;IAChD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QAAE,OAAO,CAAC,CAAA;IACxC,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,KAAK,IAAI,UAAU;QAAE,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1D,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IACnD,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QAC9C,KAAK,QAAQ,CAAC,CAAC,MAAK;QACpB,OAAO,CAAC,CAAC,OAAO,YAAY,CAAA;IAC9B,CAAC;IACD,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,YAAY,CAAA;IAC9C,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,UAAU,CAAA;IACtD,IAAI,KAAK,YAAY,WAAW;QAAE,OAAO,KAAK,CAAC,UAAU,CAAA;IACzD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,CAAA;QACnD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAuB,CAAA;QACxD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAA;IACrB,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,uFAAuF;AACvF,MAAM,aAAa,GAAG,0EAA0E,CAAA;AAEhG;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG;YAAE,SAAS,EAAE,CAAA;IACjD,CAAC;IACD,OAAO,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAA;AAC5C,CAAC"}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { StatementWatch } from '../query/postgres-js/session.js';
|
|
2
2
|
import type { StatementSettledObserver } from '../../core/config/statement-observer.js';
|
|
3
|
+
/** `observability.largeQuery*` budgets; 0 disables a budget and nothing is measured for it. */
|
|
4
|
+
export interface LargeQueryBudgets {
|
|
5
|
+
readonly parameterBytes: number;
|
|
6
|
+
readonly sqlChars: number;
|
|
7
|
+
readonly arms: number;
|
|
8
|
+
readonly rows: number;
|
|
9
|
+
}
|
|
10
|
+
/** The budgets of a resolved `observability` section. */
|
|
11
|
+
export declare function largeQueryBudgets(o: {
|
|
12
|
+
readonly largeQueryParamBytes: number;
|
|
13
|
+
readonly largeQuerySqlChars: number;
|
|
14
|
+
readonly largeQueryArms: number;
|
|
15
|
+
readonly largeQueryRows: number;
|
|
16
|
+
}): LargeQueryBudgets;
|
|
3
17
|
/** What a pool's watch needs: where it logs, how slow is slow, and how to reach the server for a wait-event sample. */
|
|
4
18
|
export interface StatementWatchOptions {
|
|
5
19
|
readonly database: string;
|
|
@@ -9,6 +23,8 @@ export interface StatementWatchOptions {
|
|
|
9
23
|
readonly inFlightQueryMs: number | undefined;
|
|
10
24
|
/** `observability.onStatementSettled`; undefined: nothing extra is tracked or fired. */
|
|
11
25
|
readonly onSettled?: StatementSettledObserver | undefined;
|
|
26
|
+
/** Undefined: no `query:large` check. */
|
|
27
|
+
readonly budgets?: LargeQueryBudgets | undefined;
|
|
12
28
|
}
|
|
13
29
|
export interface StatementWatchHandle {
|
|
14
30
|
readonly watch: StatementWatch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-watch.d.ts","sourceRoot":"","sources":["../../../src/data/db/query-watch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"query-watch.d.ts","sourceRoot":"","sources":["../../../src/data/db/query-watch.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,KAAK,EAAyB,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AAgB9G,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,yDAAyD;AACzD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE;IACnC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAChC,GAAG,iBAAiB,CAEpB;AAED,uHAAuH;AACvH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,wFAAwF;IACxF,QAAQ,CAAC,SAAS,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAA;IACzD,yCAAyC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAA;CACjD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,OAAO,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACrG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AA0CD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,oBAAoB,CAwIzF"}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import postgres from 'postgres';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { getLogger } from '../../core/logging/logger.js';
|
|
3
4
|
import { currentQueryLabel } from './query-label.js';
|
|
4
5
|
import { DrizzleQueryError } from '../query/errors.js';
|
|
6
|
+
import { parameterBytes, setOperationArms } from './query-size.js';
|
|
5
7
|
const LABEL_SQL_CHARS = 120;
|
|
8
|
+
const STATEMENT_ID_HEX_CHARS = 16;
|
|
6
9
|
const SAMPLE_TIMEOUT_MS = 2_000;
|
|
7
10
|
const PG_TRUNCATED_QUERY_LENGTH = 1_000;
|
|
8
11
|
const UNKNOWN_BACKEND = { pid: null, state: null, waitEventType: null, waitEvent: null };
|
|
12
|
+
/** The budgets of a resolved `observability` section. */
|
|
13
|
+
export function largeQueryBudgets(o) {
|
|
14
|
+
return { parameterBytes: o.largeQueryParamBytes, sqlChars: o.largeQuerySqlChars, arms: o.largeQueryArms, rows: o.largeQueryRows };
|
|
15
|
+
}
|
|
9
16
|
/** Rows a settled statement returned, or affected for a write: the driver's command-tag count, else the row count. */
|
|
10
17
|
function rowsOf(result) {
|
|
11
18
|
if (typeof result !== 'object' || result === null)
|
|
@@ -30,8 +37,20 @@ function settledError(raw) {
|
|
|
30
37
|
...(typeof code === 'string' ? { code } : {}),
|
|
31
38
|
};
|
|
32
39
|
}
|
|
33
|
-
function
|
|
34
|
-
return
|
|
40
|
+
function collapse(sqlText) {
|
|
41
|
+
return sqlText.replace(/\s+/g, ' ').trim();
|
|
42
|
+
}
|
|
43
|
+
/** The first 120 characters of the whitespace-collapsed statement text: the `sql` of the log lines and `statementPrefix`. */
|
|
44
|
+
function sqlPrefix(sqlText) {
|
|
45
|
+
return collapse(sqlText).slice(0, LABEL_SQL_CHARS);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `v1:` + the first 16 hex characters (64 bits) of the SHA-256 of the whitespace-collapsed
|
|
49
|
+
* parameterized text. 64 bits keeps a collision among an app's few thousand statement shapes
|
|
50
|
+
* negligible (birthday bound near 4 billion shapes) while the id stays short in a log line.
|
|
51
|
+
*/
|
|
52
|
+
function statementIdOf(sqlText) {
|
|
53
|
+
return `v1:${createHash('sha256').update(collapse(sqlText)).digest('hex').slice(0, STATEMENT_ID_HEX_CHARS)}`;
|
|
35
54
|
}
|
|
36
55
|
/**
|
|
37
56
|
* Wraps each statement of one database's pool: a `query:slow` warning when it settles past
|
|
@@ -40,7 +59,9 @@ function labelFor(sqlText) {
|
|
|
40
59
|
* dedicated connection, never the statement's own and never the app pool.
|
|
41
60
|
*/
|
|
42
61
|
export function createStatementWatch(options) {
|
|
43
|
-
const { database, url, slowQueryMs, inFlightQueryMs, onSettled } = options;
|
|
62
|
+
const { database, url, slowQueryMs, inFlightQueryMs, onSettled, budgets } = options;
|
|
63
|
+
const watchesSize = budgets !== undefined && (budgets.parameterBytes > 0 || budgets.sqlChars > 0 || budgets.arms > 0 || budgets.rows > 0);
|
|
64
|
+
const needsOutcome = onSettled !== undefined || (budgets?.rows ?? 0) > 0;
|
|
44
65
|
// Keyed on the parameter array the driver builds for a statement: the same array reaches the
|
|
45
66
|
// debug hook (with the connection ordinal) and sits on the pending query the session hands us.
|
|
46
67
|
const connections = onSettled ? new WeakMap() : undefined;
|
|
@@ -85,6 +106,28 @@ export function createStatementWatch(options) {
|
|
|
85
106
|
failed(err);
|
|
86
107
|
}
|
|
87
108
|
}
|
|
109
|
+
/** One `query:large` per statement over any enabled budget, carrying every enabled measurement. Never throws. */
|
|
110
|
+
function warnIfLarge(sqlText, parameters, outcome, b) {
|
|
111
|
+
try {
|
|
112
|
+
const measured = {};
|
|
113
|
+
const over = [];
|
|
114
|
+
const check = (name, budget, measure) => {
|
|
115
|
+
if (budget <= 0)
|
|
116
|
+
return;
|
|
117
|
+
const value = measure();
|
|
118
|
+
measured[name] = value;
|
|
119
|
+
if (value > budget)
|
|
120
|
+
over.push(name);
|
|
121
|
+
};
|
|
122
|
+
check('parameterBytes', b.parameterBytes, () => parameterBytes(parameters));
|
|
123
|
+
check('sqlChars', b.sqlChars, () => sqlText.length);
|
|
124
|
+
check('arms', b.arms, () => setOperationArms(sqlText));
|
|
125
|
+
check('rows', b.rows, () => (outcome !== undefined && 'result' in outcome ? rowsOf(outcome.result) : 0));
|
|
126
|
+
if (over.length > 0)
|
|
127
|
+
getLogger().warn({ label: currentQueryLabel() ?? null, sql: sqlPrefix(sqlText), database, over, ...measured }, 'query:large');
|
|
128
|
+
}
|
|
129
|
+
catch { /* a measurement must never affect the statement */ }
|
|
130
|
+
}
|
|
88
131
|
const watch = async (sqlText, run) => {
|
|
89
132
|
const startedMs = Date.now();
|
|
90
133
|
let pendingQuery;
|
|
@@ -94,14 +137,15 @@ export function createStatementWatch(options) {
|
|
|
94
137
|
let settled = false;
|
|
95
138
|
if (inFlightQueryMs !== undefined) {
|
|
96
139
|
const log = getLogger();
|
|
97
|
-
const label =
|
|
140
|
+
const label = currentQueryLabel() ?? null;
|
|
141
|
+
const sql = sqlPrefix(sqlText);
|
|
98
142
|
interval = setInterval(() => {
|
|
99
143
|
const elapsedS = Math.round((Date.now() - startedMs) / 100) / 10;
|
|
100
144
|
void sampleBackend(sqlText, startedMs).then(backend => {
|
|
101
145
|
if (settled)
|
|
102
146
|
return;
|
|
103
147
|
log.warn({
|
|
104
|
-
label, elapsedS, database,
|
|
148
|
+
label, sql, elapsedS, database,
|
|
105
149
|
pid: backend.pid, state: backend.state,
|
|
106
150
|
waitEventType: backend.waitEventType, waitEvent: backend.waitEvent,
|
|
107
151
|
}, 'query:in-flight');
|
|
@@ -111,12 +155,12 @@ export function createStatementWatch(options) {
|
|
|
111
155
|
}
|
|
112
156
|
try {
|
|
113
157
|
const result = await run(seen);
|
|
114
|
-
if (
|
|
158
|
+
if (needsOutcome)
|
|
115
159
|
outcome = { result };
|
|
116
160
|
return result;
|
|
117
161
|
}
|
|
118
162
|
catch (err) {
|
|
119
|
-
if (
|
|
163
|
+
if (needsOutcome)
|
|
120
164
|
outcome = { error: err };
|
|
121
165
|
throw err;
|
|
122
166
|
}
|
|
@@ -126,13 +170,17 @@ export function createStatementWatch(options) {
|
|
|
126
170
|
clearInterval(interval);
|
|
127
171
|
const durationMs = Date.now() - startedMs;
|
|
128
172
|
if (slowQueryMs > 0 && durationMs > slowQueryMs) {
|
|
129
|
-
getLogger().warn({ label:
|
|
173
|
+
getLogger().warn({ label: currentQueryLabel() ?? null, sql: sqlPrefix(sqlText), durationMs, database }, 'query:slow');
|
|
130
174
|
}
|
|
175
|
+
const parameters = pendingQuery === undefined ? undefined : pendingQuery.parameters;
|
|
176
|
+
if (watchesSize)
|
|
177
|
+
warnIfLarge(sqlText, parameters, outcome, budgets);
|
|
131
178
|
if (onSettled && outcome !== undefined) {
|
|
132
|
-
const parameters = pendingQuery === undefined ? undefined : pendingQuery.parameters;
|
|
133
179
|
const connectionId = typeof parameters === 'object' && parameters !== null ? connections?.get(parameters) ?? null : null;
|
|
134
180
|
fireSettled({
|
|
135
181
|
label: currentQueryLabel() ?? null,
|
|
182
|
+
statementId: statementIdOf(sqlText),
|
|
183
|
+
statementPrefix: sqlPrefix(sqlText),
|
|
136
184
|
durationMs,
|
|
137
185
|
rows: 'result' in outcome ? rowsOf(outcome.result) : 0,
|
|
138
186
|
connectionId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-watch.js","sourceRoot":"","sources":["../../../src/data/db/query-watch.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"query-watch.js","sourceRoot":"","sources":["../../../src/data/db/query-watch.ts"],"names":[],"mappings":"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"}
|
package/dist/test/plugin.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createTestContext as createCtx } from '../request/context/create.js';
|
|
|
6
6
|
import { runInContext } from '../request/context/store.js';
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
8
|
import { createDebugFn, isObserverOnly } from '../data/db/instrumentation.js';
|
|
9
|
-
import { createStatementWatch } from '../data/db/query-watch.js';
|
|
9
|
+
import { createStatementWatch, largeQueryBudgets } from '../data/db/query-watch.js';
|
|
10
10
|
import { markObservedConnection } from '../data/query/postgres-js/driver-error-boundary.js';
|
|
11
11
|
import { collapseTransactionsToSavepoints } from './savepoints.js';
|
|
12
12
|
import { splitLargeInserts } from '../data/db/insert-split.js';
|
|
@@ -80,7 +80,7 @@ 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 watch = createStatementWatch({ database: name, url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled });
|
|
83
|
+
const watch = createStatementWatch({ database: name, url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled, budgets: largeQueryBudgets(observability) });
|
|
84
84
|
const debug = createDebugFn(observability, watch.noteConnection);
|
|
85
85
|
const client = postgres(testDb.url, { max: 1, onnotice: () => { }, ...(debug ? { debug } : {}) });
|
|
86
86
|
if (isObserverOnly(observability))
|
|
@@ -120,7 +120,7 @@ export async function propagateTestSessionVars(instance, testConnections, tenant
|
|
|
120
120
|
}
|
|
121
121
|
export function createSetupConnection(testDb, observability) {
|
|
122
122
|
const watch = observability
|
|
123
|
-
? createStatementWatch({ database: 'setup', url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled })
|
|
123
|
+
? createStatementWatch({ database: 'setup', url: testDb.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled, budgets: largeQueryBudgets(observability) })
|
|
124
124
|
: undefined;
|
|
125
125
|
const debug = observability ? createDebugFn(observability, watch?.noteConnection) : undefined;
|
|
126
126
|
const client = postgres(testDb.url, { max: 1, onnotice: () => { }, ...(debug ? { debug } : {}) });
|
package/dist/test/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,OAAO,EAA2B,MAAM,oCAAoC,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAA;AAE5C,OAAO,EAAE,eAAe,EAAqB,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,iBAAiB,IAAI,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pipework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.285.0",
|
|
4
4
|
"description": "TypeScript framework for multi-tenant SaaS applications. PostgreSQL-only.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -203,6 +203,10 @@
|
|
|
203
203
|
"test:stress": "vitest run --config vitest.stress.config.ts",
|
|
204
204
|
"test:packages": "pnpm -r --filter='./packages/*' test",
|
|
205
205
|
"test:all": "pnpm test:packages && vitest run && vitest run --config vitest.isolation.config.ts && vitest run --config vitest.property.config.ts && vitest run --config vitest.stress.config.ts",
|
|
206
|
-
"mutate": "stryker run"
|
|
206
|
+
"mutate": "stryker run",
|
|
207
|
+
"rust:fmt": "cargo fmt --all --check",
|
|
208
|
+
"rust:clippy": "cargo clippy --workspace --all-targets -- -D warnings && cargo clippy -p pipework-query --all-targets --no-default-features --features all-types,audit,hashable-value,serde,thread-safe,postgres-driver -- -D warnings",
|
|
209
|
+
"rust:test": "cargo test --workspace && cargo test -p pipework-query --features all-types,hashable-value,serde",
|
|
210
|
+
"rust:check": "pnpm rust:fmt && pnpm rust:clippy && pnpm rust:test"
|
|
207
211
|
}
|
|
208
212
|
}
|
package/src/core/config/load.ts
CHANGED
|
@@ -10,6 +10,14 @@ export interface ResolvedObservability {
|
|
|
10
10
|
readonly slowQueryMs: number
|
|
11
11
|
/** Interval at which a still-running statement is reported; undefined = off. */
|
|
12
12
|
readonly inFlightQueryMs?: number | undefined
|
|
13
|
+
/** Estimated bind-parameter bytes above which a statement logs `query:large`; 0 = off. */
|
|
14
|
+
readonly largeQueryParamBytes: number
|
|
15
|
+
/** SQL text length (characters) above which a statement logs `query:large`; 0 = off. */
|
|
16
|
+
readonly largeQuerySqlChars: number
|
|
17
|
+
/** UNION/INTERSECT/EXCEPT arms (a text count) above which a statement logs `query:large`; 0 = off. */
|
|
18
|
+
readonly largeQueryArms: number
|
|
19
|
+
/** Rows returned (or affected) above which a statement logs `query:large`; 0 = off. */
|
|
20
|
+
readonly largeQueryRows: number
|
|
13
21
|
readonly logAllQueries: boolean
|
|
14
22
|
readonly poolMetricsInterval: number
|
|
15
23
|
/** Caller-supplied per-statement observer; undefined when not configured. */
|
|
@@ -246,6 +254,10 @@ function buildResolved(config: Blueprint, environment: Environment, baseDir: str
|
|
|
246
254
|
observability: {
|
|
247
255
|
slowQueryMs: config.observability?.slowQueryMs ?? 500,
|
|
248
256
|
inFlightQueryMs: config.observability?.inFlightQueryMs,
|
|
257
|
+
largeQueryParamBytes: config.observability?.largeQueryParamBytes ?? 8_388_608,
|
|
258
|
+
largeQuerySqlChars: config.observability?.largeQuerySqlChars ?? 131_072,
|
|
259
|
+
largeQueryArms: config.observability?.largeQueryArms ?? 50,
|
|
260
|
+
largeQueryRows: config.observability?.largeQueryRows ?? 100_000,
|
|
249
261
|
logAllQueries: config.observability?.logAllQueries ?? false,
|
|
250
262
|
poolMetricsInterval: config.observability?.poolMetricsInterval ?? 0,
|
|
251
263
|
onStatement: config.observability?.onStatement,
|
|
@@ -171,6 +171,10 @@ const fastPathSchema = z.object({
|
|
|
171
171
|
|
|
172
172
|
const observabilitySchema = z.object({
|
|
173
173
|
slowQueryMs: z.number().int().positive().optional().describe('Warn when a query exceeds this duration in ms'),
|
|
174
|
+
largeQueryParamBytes: z.number().int().nonnegative().optional().describe('Log `query:large` when a statement\'s bind parameters total more than this many bytes, estimated (default 8388608; 0 = off)'),
|
|
175
|
+
largeQuerySqlChars: z.number().int().nonnegative().optional().describe('Log `query:large` when a statement\'s SQL text is longer than this many characters (default 131072; 0 = off)'),
|
|
176
|
+
largeQueryArms: z.number().int().nonnegative().optional().describe('Log `query:large` when a statement\'s SQL has more than this many UNION/INTERSECT/EXCEPT arms, counted in the text (default 50; 0 = off)'),
|
|
177
|
+
largeQueryRows: z.number().int().nonnegative().optional().describe('Log `query:large` when a statement returns (or affects) more than this many rows (default 100000; 0 = off)'),
|
|
174
178
|
inFlightQueryMs: z.number().int().positive().optional().describe('While a statement is still running, log what it is waiting on every this many ms (unset = off)'),
|
|
175
179
|
logAllQueries: z.boolean().optional().describe('Log every query at debug level'),
|
|
176
180
|
poolMetricsInterval: z.number().int().nonnegative().optional().describe('Seconds between pool metric summaries (0 = disabled)'),
|
|
@@ -183,7 +187,7 @@ const observabilitySchema = z.object({
|
|
|
183
187
|
.describe('Per-statement observer — fires once per statement written to the wire, including driver-issued BEGIN/SAVEPOINT/COMMIT/ROLLBACK'),
|
|
184
188
|
onStatementSettled: z.custom<StatementSettledObserver>(v => typeof v === 'function', { message: 'Expected a function' })
|
|
185
189
|
.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'),
|
|
190
|
+
.describe('Per-statement completion hook — fires once per statement when it settles, with label, statementId, statementPrefix, durationMs, rows, connectionId and (on failure) a sanitized error. Covers every statement through pipework\'s query path (pipe(), pipe.* writes, connection.drizzle, transactions, savepoints, the test harness), not the driver\'s own BEGIN/COMMIT, the raw client door or the migration runner\'s connection'),
|
|
187
191
|
})
|
|
188
192
|
|
|
189
193
|
const envVarDefSchema = z.discriminatedUnion('type', [
|
|
@@ -63,14 +63,39 @@ export interface StatementSettledError {
|
|
|
63
63
|
/**
|
|
64
64
|
* One statement as it settles — the completion counterpart to `StatementEvent`.
|
|
65
65
|
* Fields are only ever added to this payload, never reshaped.
|
|
66
|
+
*
|
|
67
|
+
* Fires for every statement through pipework's query path (`pipe()`, `pipe.*`
|
|
68
|
+
* writes, `connection.drizzle`, transactions, savepoints, the test harness), not
|
|
69
|
+
* for the driver's own BEGIN/COMMIT, the raw `client` door or the migration
|
|
70
|
+
* runner's connection: a sum of `durationMs` leaves those out.
|
|
66
71
|
*/
|
|
67
72
|
export interface StatementSettledEvent {
|
|
68
73
|
/**
|
|
69
74
|
* The `pipe.labelled` label in effect when the statement settled; null when
|
|
70
|
-
* none. This is the label alone:
|
|
71
|
-
*
|
|
75
|
+
* none. This is the label alone: it never falls back to the statement text
|
|
76
|
+
* (`statementPrefix` carries that).
|
|
72
77
|
*/
|
|
73
78
|
readonly label: string | null
|
|
79
|
+
/**
|
|
80
|
+
* Which statement shape this was, stable across calls and processes:
|
|
81
|
+
* `v1:` followed by the first 16 hex characters of the SHA-256 of the
|
|
82
|
+
* statement text exactly as sent to the server (parameterized, `$1`, `$2`...
|
|
83
|
+
* placeholders) with every run of whitespace collapsed to one space and the
|
|
84
|
+
* ends trimmed, hashed as UTF-8. It holds no constants and no bound values, so
|
|
85
|
+
* the same statement with different parameter values or different
|
|
86
|
+
* formatting has the same id, and different SQL has a different one.
|
|
87
|
+
* `v1` names this exact definition: a change to the hash, the length or the
|
|
88
|
+
* normalization gets a new prefix (`v2:`), never a silent redefinition, so
|
|
89
|
+
* records keyed on an id never split without the prefix saying so.
|
|
90
|
+
*/
|
|
91
|
+
readonly statementId: string
|
|
92
|
+
/**
|
|
93
|
+
* The first 120 characters of the same whitespace-collapsed statement text
|
|
94
|
+
* (placeholders, no bound values): what `query:slow`, `query:in-flight` and
|
|
95
|
+
* `query:large` log as `sql`. A readable name for `statementId`; two shapes
|
|
96
|
+
* can share a prefix, so key on `statementId`.
|
|
97
|
+
*/
|
|
98
|
+
readonly statementPrefix: string
|
|
74
99
|
/**
|
|
75
100
|
* Wall-clock milliseconds the statement took, measured by the same timer as
|
|
76
101
|
* `query:slow` (so for one statement the two agree exactly).
|
|
@@ -93,13 +118,12 @@ export interface StatementSettledEvent {
|
|
|
93
118
|
}
|
|
94
119
|
|
|
95
120
|
/**
|
|
96
|
-
* Caller-supplied completion hook. Fires once per statement that settles,
|
|
97
|
-
* every statement
|
|
98
|
-
* `
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* connection; `onStatement` is the hook that sees wire truth.
|
|
121
|
+
* Caller-supplied completion hook. Fires once per statement that settles, for
|
|
122
|
+
* every statement through pipework's query path (`pipe()`, `pipe.*` writes,
|
|
123
|
+
* `connection.drizzle`, transactions, savepoints, the test harness), not for the
|
|
124
|
+
* driver's own BEGIN/COMMIT, the raw `client` door or the migration runner's
|
|
125
|
+
* connection; `onStatement` is the hook that sees wire truth. The event names the
|
|
126
|
+
* statement by `statementId` and `statementPrefix`, never by its parameters.
|
|
103
127
|
*
|
|
104
128
|
* Pure observation, fired after the statement has settled and its result is on
|
|
105
129
|
* its way back: a hook that throws, or returns a promise that rejects, is
|
|
@@ -70,6 +70,10 @@ export interface SurfaceContext {
|
|
|
70
70
|
readonly observability: {
|
|
71
71
|
readonly slowQueryMs: number
|
|
72
72
|
readonly inFlightQueryMs?: number | undefined
|
|
73
|
+
readonly largeQueryParamBytes: number
|
|
74
|
+
readonly largeQuerySqlChars: number
|
|
75
|
+
readonly largeQueryArms: number
|
|
76
|
+
readonly largeQueryRows: number
|
|
73
77
|
readonly logAllQueries: boolean
|
|
74
78
|
readonly poolMetricsInterval: number
|
|
75
79
|
readonly onStatement: import('../config/statement-observer.js').StatementObserver | undefined
|
package/src/data/db/namespace.ts
CHANGED
|
@@ -38,7 +38,7 @@ export const pipe = Object.assign(getDb, {
|
|
|
38
38
|
/** Query operators and aggregates — eq, gt, lt, and, or, count, sum, etc. */
|
|
39
39
|
filter,
|
|
40
40
|
|
|
41
|
-
/** Names every statement `fn` starts, in `query:slow` and `query:in-flight` log lines: `pipe.labelled('servable-footprints', async () => await ...)`. A query builder is lazy: `fn` must await it, not return it, or it runs outside the scope. */
|
|
41
|
+
/** Names every statement `fn` starts, in `query:slow`, `query:large` and `query:in-flight` log lines (as `label`, beside the `sql` prefix): `pipe.labelled('servable-footprints', async () => await ...)`. A query builder is lazy: `fn` must await it, not return it, or it runs outside the scope. */
|
|
42
42
|
labelled,
|
|
43
43
|
|
|
44
44
|
/** Raw statement tag — for a statement pipework does not model. Interpolated values are parameters; `sql.raw` is not. Runs on the caller's connection, so inside a request it is the request transaction. Writes still go through pipe.update / insert / delete, which is what audit hooks. */
|
package/src/data/db/pool.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { createDebugFn, isObserverOnly } from './instrumentation.js'
|
|
|
6
6
|
import { markObservedConnection } from '../query/postgres-js/driver-error-boundary.js'
|
|
7
7
|
import { warnRawClientIsUnsanitized } from './raw-client-warning.js'
|
|
8
8
|
import { splitLargeInserts } from './insert-split.js'
|
|
9
|
-
import { createStatementWatch } from './query-watch.js'
|
|
9
|
+
import { createStatementWatch, largeQueryBudgets } from './query-watch.js'
|
|
10
10
|
|
|
11
11
|
/** Which database role a pooled connection authenticates as. `owner` runs migrations/bootstrap and owns tables (bypasses RLS); `app` is the non-owner, non-BYPASSRLS runtime role that RLS policies enforce against; `system` is the platform role (`rls.platformRole`, via `systemUrl`), which reaches a tenant table only through a declared door. */
|
|
12
12
|
export type ConnectionRole = 'owner' | 'app' | 'system'
|
|
@@ -167,7 +167,7 @@ export function createManagedConnection(
|
|
|
167
167
|
applicationName?: string,
|
|
168
168
|
): ManagedConnection {
|
|
169
169
|
const statementWatch = observability
|
|
170
|
-
? createStatementWatch({ database: config.name, url: config.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled })
|
|
170
|
+
? createStatementWatch({ database: config.name, url: config.url, slowQueryMs: observability.slowQueryMs, inFlightQueryMs: observability.inFlightQueryMs, onSettled: observability.onStatementSettled, budgets: largeQueryBudgets(observability) })
|
|
171
171
|
: undefined
|
|
172
172
|
const baseDebug = observability ? createDebugFn(observability, statementWatch?.noteConnection) : undefined
|
|
173
173
|
const slot: ProbeSlot | undefined = baseDebug ? { current: null } : undefined
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Size measurements behind `query:large`. Each runs only when its budget is enabled (> 0), after the
|
|
2
|
+
// statement has settled, so a pool with every budget at 0 never calls into this file.
|
|
3
|
+
|
|
4
|
+
/** Bytes assumed for a number, bigint, boolean, Date or other fixed-width scalar parameter. */
|
|
5
|
+
const SCALAR_BYTES = 8
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Estimated bytes of the bind parameters of one statement. Strings count their UTF-8 length
|
|
9
|
+
* (no copy is made), Buffers/typed arrays their byte length, arrays the sum of their elements,
|
|
10
|
+
* numbers/booleans/dates a fixed 8, null/undefined 0. A plain object is a JSON value the driver
|
|
11
|
+
* will serialise: it is measured by `JSON.stringify` length, which serialises it once more than
|
|
12
|
+
* the driver does; ORM json columns arrive already stringified (counted as strings, free), so
|
|
13
|
+
* only a raw object parameter pays that. An unserialisable object counts as one scalar.
|
|
14
|
+
*/
|
|
15
|
+
export function parameterBytes(parameters: unknown): number {
|
|
16
|
+
if (!Array.isArray(parameters)) return 0
|
|
17
|
+
let total = 0
|
|
18
|
+
for (const value of parameters) total += valueBytes(value)
|
|
19
|
+
return total
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function valueBytes(value: unknown): number {
|
|
23
|
+
if (value === null || value === undefined) return 0
|
|
24
|
+
switch (typeof value) {
|
|
25
|
+
case 'string': return Buffer.byteLength(value)
|
|
26
|
+
case 'object': break
|
|
27
|
+
default: return SCALAR_BYTES
|
|
28
|
+
}
|
|
29
|
+
if (value instanceof Date) return SCALAR_BYTES
|
|
30
|
+
if (ArrayBuffer.isView(value)) return value.byteLength
|
|
31
|
+
if (value instanceof ArrayBuffer) return value.byteLength
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
let total = 0
|
|
34
|
+
for (const item of value) total += valueBytes(item)
|
|
35
|
+
return total
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const json = JSON.stringify(value) as string | undefined
|
|
39
|
+
return json === undefined ? 0 : json.length
|
|
40
|
+
} catch {
|
|
41
|
+
return SCALAR_BYTES
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Single left-to-right scan: a quoted literal or quoted identifier is consumed whole, so a keyword
|
|
46
|
+
// inside one is never counted; what is left are bare UNION / INTERSECT / EXCEPT words.
|
|
47
|
+
const SET_OPERATION = /'[^']*(?:''[^']*)*'|"[^"]*(?:""[^"]*)*"|\b(?:union|intersect|except)\b/gi
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Arms of the set operations in a statement's SQL text: set-operator keywords + 1, 0 when there
|
|
51
|
+
* are none. This is a text count at every nesting depth (a union inside a subquery or CTE costs the
|
|
52
|
+
* planner the same as one at the top), not a parse. Pipework sends values as bind parameters, so
|
|
53
|
+
* no keyword can appear inside a string value; a literal written with `sql.raw` is skipped by
|
|
54
|
+
* the scan. Comments and dollar-quoted bodies are not understood and can over-count.
|
|
55
|
+
*/
|
|
56
|
+
export function setOperationArms(sqlText: string): number {
|
|
57
|
+
let operators = 0
|
|
58
|
+
for (const match of sqlText.matchAll(SET_OPERATION)) {
|
|
59
|
+
const first = match[0][0]
|
|
60
|
+
if (first !== "'" && first !== '"') operators++
|
|
61
|
+
}
|
|
62
|
+
return operators === 0 ? 0 : operators + 1
|
|
63
|
+
}
|