@proteinjs/db-driver-knex 1.15.0 → 1.15.1
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 +13 -0
- package/README.md +5 -3
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/generated/test/index.js +1 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/KnexDriver.d.ts +0 -7
- package/dist/src/KnexDriver.d.ts.map +1 -1
- package/dist/src/KnexDriver.js +37 -34
- package/dist/src/KnexDriver.js.map +1 -1
- package/dist/src/KnexFailureLine.d.ts +58 -0
- package/dist/src/KnexFailureLine.d.ts.map +1 -0
- package/dist/src/KnexFailureLine.js +124 -0
- package/dist/src/KnexFailureLine.js.map +1 -0
- package/dist/test/LogValuesSwitch.test.js +13 -7
- package/dist/test/LogValuesSwitch.test.js.map +1 -1
- package/dist/test/StatementLogNeverThrows.test.js +23 -29
- package/dist/test/StatementLogNeverThrows.test.js.map +1 -1
- package/dist/test/StatementParamLogging.test.js +16 -41
- package/dist/test/StatementParamLogging.test.js.map +1 -1
- package/dist/test/VendorTextNeverOnALine.test.d.ts +2 -0
- package/dist/test/VendorTextNeverOnALine.test.d.ts.map +1 -0
- package/dist/test/VendorTextNeverOnALine.test.js +330 -0
- package/dist/test/VendorTextNeverOnALine.test.js.map +1 -0
- package/generated/index.ts +1 -1
- package/generated/test/index.ts +1 -1
- package/index.ts +1 -0
- package/package.json +5 -5
- package/src/KnexDriver.ts +23 -36
- package/src/KnexFailureLine.ts +127 -0
- package/test/LogValuesSwitch.test.ts +3 -1
- package/test/StatementLogNeverThrows.test.ts +24 -20
- package/test/StatementParamLogging.test.ts +12 -17
- package/test/VendorTextNeverOnALine.test.ts +257 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db-driver-knex",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"main": "./dist/generated/index.js",
|
|
5
5
|
"types": "./dist/generated/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -41,10 +41,10 @@
|
|
|
41
41
|
"test": "jest --runInBand --passWithNoTests"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@proteinjs/db": "^1.
|
|
44
|
+
"@proteinjs/db": "^1.49.0",
|
|
45
45
|
"@proteinjs/db-query": "^1.10.1",
|
|
46
|
-
"@proteinjs/db-transaction-context": "^0.5.
|
|
47
|
-
"@proteinjs/logger": "^1.0
|
|
46
|
+
"@proteinjs/db-transaction-context": "^0.5.37",
|
|
47
|
+
"@proteinjs/logger": "^1.2.0",
|
|
48
48
|
"@proteinjs/reflection": "^1.2.0",
|
|
49
49
|
"@proteinjs/util": "^1.6.1",
|
|
50
50
|
"knex": "0.21.5",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"ts-jest": "29.1.1",
|
|
68
68
|
"typescript": "5.2.2"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "5ef7c5a2f3eb6ef3b774aa93cc74cc1754cfa011"
|
|
71
71
|
}
|
package/src/KnexDriver.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@proteinjs/db';
|
|
11
11
|
import { KnexConfig } from './KnexConfig';
|
|
12
12
|
import { KnexLogValues } from './KnexLogValues';
|
|
13
|
+
import { KnexFailureLine } from './KnexFailureLine';
|
|
13
14
|
import { Logger } from '@proteinjs/logger';
|
|
14
15
|
import { Statement } from '@proteinjs/db-query';
|
|
15
16
|
import { KnexSchemaOperations } from './KnexSchemaOperations';
|
|
@@ -24,9 +25,6 @@ type ParamDescription = { type: string; length?: number; null?: true };
|
|
|
24
25
|
*/
|
|
25
26
|
type ParamsDescription = ParamDescription[] | { [name: string]: ParamDescription } | 'unreadable';
|
|
26
27
|
|
|
27
|
-
/** A failure as the log line carries it: the error's name, the vendor's codes and the server's own message. */
|
|
28
|
-
type FailureCauseSummary = { name?: string; code?: string; errno?: number; sqlState?: string; sqlMessage?: string };
|
|
29
|
-
|
|
30
28
|
/**
|
|
31
29
|
* Knex driver (configured for MariaDb) for ProteinJs Db
|
|
32
30
|
*/
|
|
@@ -177,6 +175,8 @@ export class KnexDriver implements DbDriver {
|
|
|
177
175
|
// returns 2 arrays, first is records, second is metadata per record
|
|
178
176
|
return (await runner.raw(sql, params as any).timeout(this.getOperationDeadlineMs(), { cancel: true }))[0];
|
|
179
177
|
} catch (error: unknown) {
|
|
178
|
+
// Rethrown as it is; marked, so no line about it — this driver's or a caller's — prints its text.
|
|
179
|
+
KnexFailureLine.mark(error, 'Failed when executing sql');
|
|
180
180
|
this.logFailure(sql, params, error);
|
|
181
181
|
throw error;
|
|
182
182
|
}
|
|
@@ -196,10 +196,25 @@ export class KnexDriver implements DbDriver {
|
|
|
196
196
|
* @returns the return value of the `fn`
|
|
197
197
|
*/
|
|
198
198
|
async runTransaction<T>(fn: (transaction: Transaction) => Promise<T>): Promise<T> {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
202
|
-
|
|
199
|
+
const bodyErrors = new Set<unknown>();
|
|
200
|
+
try {
|
|
201
|
+
return await this.getKnex().transaction(async (trx) => {
|
|
202
|
+
try {
|
|
203
|
+
return await fn(trx);
|
|
204
|
+
} catch (error: unknown) {
|
|
205
|
+
bodyErrors.add(error);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
} catch (error: unknown) {
|
|
210
|
+
// What the BODY threw is the caller's own error, or a failure already marked at the
|
|
211
|
+
// statement's door. Anything else was raised around the body — beginning, committing —
|
|
212
|
+
// and words itself as the vendor did. Rethrown as it is, either way.
|
|
213
|
+
if (!bodyErrors.has(error)) {
|
|
214
|
+
KnexFailureLine.mark(error, 'Transaction failed');
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
203
218
|
}
|
|
204
219
|
|
|
205
220
|
/**
|
|
@@ -238,7 +253,7 @@ export class KnexDriver implements DbDriver {
|
|
|
238
253
|
sql,
|
|
239
254
|
params: this.describeParams(params),
|
|
240
255
|
...KnexLogValues.ofStatement(params),
|
|
241
|
-
cause:
|
|
256
|
+
cause: KnexFailureLine.causeOf(error),
|
|
242
257
|
},
|
|
243
258
|
...KnexLogValues.ofFailure(error),
|
|
244
259
|
})
|
|
@@ -267,25 +282,6 @@ export class KnexDriver implements DbDriver {
|
|
|
267
282
|
}
|
|
268
283
|
}
|
|
269
284
|
|
|
270
|
-
/**
|
|
271
|
-
* The name, codes and server message of any thrown value — never the query layer's rewritten
|
|
272
|
-
* `message` or `sql`. Total: a fact that cannot be read (vendorFact) is left out.
|
|
273
|
-
*/
|
|
274
|
-
private causeSummary(error: unknown): FailureCauseSummary {
|
|
275
|
-
const name = this.vendorFact(error, 'name');
|
|
276
|
-
const code = this.vendorFact(error, 'code');
|
|
277
|
-
const errno = this.vendorFact(error, 'errno');
|
|
278
|
-
const sqlState = this.vendorFact(error, 'sqlState');
|
|
279
|
-
const sqlMessage = this.vendorFact(error, 'sqlMessage');
|
|
280
|
-
return {
|
|
281
|
-
...(typeof name === 'string' ? { name } : {}),
|
|
282
|
-
...(typeof code === 'string' ? { code } : {}),
|
|
283
|
-
...(typeof errno === 'number' ? { errno } : {}),
|
|
284
|
-
...(typeof sqlState === 'string' ? { sqlState } : {}),
|
|
285
|
-
...(typeof sqlMessage === 'string' ? { sqlMessage } : {}),
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
285
|
/**
|
|
290
286
|
* A statement's bound parameters as a LOG LINE carries them — the one owner of that form: by
|
|
291
287
|
* position, the KIND of each value and, for strings, arrays and bytes, its LENGTH. Never a
|
|
@@ -377,13 +373,4 @@ export class KnexDriver implements DbDriver {
|
|
|
377
373
|
const prototype = Object.getPrototypeOf(params);
|
|
378
374
|
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
|
379
375
|
}
|
|
380
|
-
|
|
381
|
-
/** One fact of a thrown value, or nothing when it cannot be read (nothing thrown, a scalar, a throwing accessor). */
|
|
382
|
-
private vendorFact(error: unknown, fact: keyof FailureCauseSummary): unknown {
|
|
383
|
-
try {
|
|
384
|
-
return (error as { [fact: string]: unknown } | null | undefined)?.[fact];
|
|
385
|
-
} catch {
|
|
386
|
-
return undefined;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
376
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ErrorLine, LogLineErrors } from '@proteinjs/logger';
|
|
2
|
+
import { KnexLogValues } from './KnexLogValues';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A failure as the driver's own line carries it under `cause`: the error's name, the vendor's
|
|
6
|
+
* codes and, as `message`, the driver's sentence — the field a reader of any driver's line parses.
|
|
7
|
+
*/
|
|
8
|
+
export type FailureCause = {
|
|
9
|
+
name?: string;
|
|
10
|
+
code?: string;
|
|
11
|
+
errno?: number;
|
|
12
|
+
sqlState?: string;
|
|
13
|
+
message: string;
|
|
14
|
+
/** The server's own message — behind the dev-only values switch, never otherwise. */
|
|
15
|
+
sqlMessage?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The ONE owner of how a failure of the database reads on a LOG LINE: the vendor's codes and a
|
|
20
|
+
* sentence of the driver's own — never the vendor's text.
|
|
21
|
+
*
|
|
22
|
+
* What the driver THROWS is the vendor's error itself, untouched, and it is not safe to print:
|
|
23
|
+
* the query layer rewrites its `message` to the SQL with every binding INTERPOLATED, the client
|
|
24
|
+
* library carries the same text as `sql`, and the server's own message (`sqlMessage`) quotes the
|
|
25
|
+
* value it refused (`Duplicate entry '<key>' for key 'PRIMARY'`). The driver's own line never
|
|
26
|
+
* carried the first two; a CALLER's line about the error it caught carried all three. So every
|
|
27
|
+
* failure that leaves the driver is marked with the logger (`mark`), which never touches the
|
|
28
|
+
* error; from then on any line about it, the driver's or a caller's, carries the codes and the
|
|
29
|
+
* sentence in place of the error's own text.
|
|
30
|
+
*
|
|
31
|
+
* The vendor's text rides a line only behind the dev-only values switch (KnexLogValues): with
|
|
32
|
+
* both gates open a marked error prints as it is, and `causeOf` adds the server's message.
|
|
33
|
+
* Asked at each line, like the switch itself.
|
|
34
|
+
*/
|
|
35
|
+
export class KnexFailureLine {
|
|
36
|
+
private static readonly UNNAMED = 'the database refused the statement';
|
|
37
|
+
private static readonly UNREACHABLE = 'the database could not be reached';
|
|
38
|
+
private static readonly SENTENCES: { [code: string]: string } = {
|
|
39
|
+
ER_DUP_ENTRY: 'a row with that key already exists',
|
|
40
|
+
ER_NO_SUCH_TABLE: 'a table the statement names does not exist',
|
|
41
|
+
ER_BAD_FIELD_ERROR: 'a column the statement names does not exist',
|
|
42
|
+
ER_PARSE_ERROR: 'the statement does not parse',
|
|
43
|
+
ER_LOCK_DEADLOCK: 'the transaction was chosen as a deadlock victim',
|
|
44
|
+
ER_LOCK_WAIT_TIMEOUT: 'a lock wait timed out',
|
|
45
|
+
ER_DATA_TOO_LONG: 'a value is longer than its column',
|
|
46
|
+
ER_TRUNCATED_WRONG_VALUE: 'a value does not convert to the type of its column',
|
|
47
|
+
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD: 'a value does not convert to the type of its column',
|
|
48
|
+
ER_WARN_DATA_OUT_OF_RANGE: 'a value is out of range for its column',
|
|
49
|
+
ER_BAD_NULL_ERROR: 'a column that cannot be null was given null',
|
|
50
|
+
ER_NO_REFERENCED_ROW_2: 'a foreign key refused the statement',
|
|
51
|
+
ER_ROW_IS_REFERENCED_2: 'a foreign key refused the statement',
|
|
52
|
+
ER_ACCESS_DENIED_ERROR: 'the credentials of the caller were not accepted',
|
|
53
|
+
ER_NET_PACKET_TOO_LARGE: 'the statement is larger than the server accepts',
|
|
54
|
+
ECONNREFUSED: KnexFailureLine.UNREACHABLE,
|
|
55
|
+
ECONNRESET: KnexFailureLine.UNREACHABLE,
|
|
56
|
+
ETIMEDOUT: KnexFailureLine.UNREACHABLE,
|
|
57
|
+
PROTOCOL_CONNECTION_LOST: KnexFailureLine.UNREACHABLE,
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The failures the query layer raises itself, which carry no vendor code — by the error's name.
|
|
61
|
+
* Its timeout is one error for the statement's operation deadline and for the wait for a free
|
|
62
|
+
* connection; either way it carries the statement's bindings, so it is marked like any other.
|
|
63
|
+
*/
|
|
64
|
+
private static readonly SENTENCES_BY_NAME: { [name: string]: string } = {
|
|
65
|
+
KnexTimeoutError: 'the statement or its wait for a connection timed out',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Marks `error` — whatever the client library or the query layer rejected with — as never
|
|
70
|
+
* printing its own text, and answers the same error. `what` is the driver's words for what
|
|
71
|
+
* failed. An error already marked keeps its line.
|
|
72
|
+
*/
|
|
73
|
+
static mark<T>(error: T, what: string): T {
|
|
74
|
+
if (!LogLineErrors.isMarked(error)) {
|
|
75
|
+
LogLineErrors.mark(error, () => (KnexLogValues.enabled() ? undefined : KnexFailureLine.lineOf(error, what)));
|
|
76
|
+
}
|
|
77
|
+
return error;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The failure as the driver's own line carries it under `cause` — never the query layer's
|
|
82
|
+
* rewritten `message` or `sql`, and the server's own message only behind the values switch.
|
|
83
|
+
* Total: a fact that cannot be read is left out.
|
|
84
|
+
*/
|
|
85
|
+
static causeOf(error: unknown): FailureCause {
|
|
86
|
+
const name = KnexFailureLine.vendorFact(error, 'name');
|
|
87
|
+
const code = KnexFailureLine.vendorFact(error, 'code');
|
|
88
|
+
const errno = KnexFailureLine.vendorFact(error, 'errno');
|
|
89
|
+
const sqlState = KnexFailureLine.vendorFact(error, 'sqlState');
|
|
90
|
+
const sqlMessage = KnexLogValues.enabled() ? KnexFailureLine.vendorFact(error, 'sqlMessage') : undefined;
|
|
91
|
+
return {
|
|
92
|
+
...(typeof name === 'string' ? { name } : {}),
|
|
93
|
+
...(typeof code === 'string' ? { code } : {}),
|
|
94
|
+
...(typeof errno === 'number' ? { errno } : {}),
|
|
95
|
+
...(typeof sqlState === 'string' ? { sqlState } : {}),
|
|
96
|
+
message: KnexFailureLine.sentenceOf(code, name),
|
|
97
|
+
...(typeof sqlMessage === 'string' ? { sqlMessage } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static lineOf(error: unknown, what: string): ErrorLine {
|
|
102
|
+
const { name: _name, message, sqlMessage: _sqlMessage, ...codes } = KnexFailureLine.causeOf(error);
|
|
103
|
+
const named = [codes.code, codes.errno !== undefined ? `errno ${codes.errno}` : undefined].filter(Boolean);
|
|
104
|
+
return {
|
|
105
|
+
...(codes.code !== undefined ? { code: codes.code } : {}),
|
|
106
|
+
sentence: `${what}${named.length ? ` (${named.join(', ')})` : ''}: ${message}`,
|
|
107
|
+
facts: codes,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static sentenceOf(code: unknown, name: unknown): string {
|
|
112
|
+
return (
|
|
113
|
+
(typeof code === 'string' ? KnexFailureLine.SENTENCES[code] : undefined) ??
|
|
114
|
+
(typeof name === 'string' ? KnexFailureLine.SENTENCES_BY_NAME[name] : undefined) ??
|
|
115
|
+
KnexFailureLine.UNNAMED
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** One fact of a thrown value, or nothing when it cannot be read (nothing thrown, a scalar, a throwing accessor). */
|
|
120
|
+
private static vendorFact(error: unknown, fact: string): unknown {
|
|
121
|
+
try {
|
|
122
|
+
return (error as { [fact: string]: unknown } | null | undefined)?.[fact];
|
|
123
|
+
} catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -141,12 +141,14 @@ describe('the dev-only values switch: real values ride the driver`s failure line
|
|
|
141
141
|
expect(facts(caught)).toEqual(baseline);
|
|
142
142
|
// The description and the summary ride the line at every gate.
|
|
143
143
|
expect(failure.obj.params).toEqual([{ type: 'string', length: VALUE.length }, { type: 'number' }]);
|
|
144
|
+
// The server's own message rides the summary only behind the switch.
|
|
144
145
|
expect(failure.obj.cause).toEqual({
|
|
145
146
|
name: 'Error',
|
|
146
147
|
code: 'ER_NO_SUCH_TABLE',
|
|
147
148
|
errno: 1146,
|
|
148
149
|
sqlState: '42S02',
|
|
149
|
-
|
|
150
|
+
message: 'a table the statement names does not exist',
|
|
151
|
+
...(on ? { sqlMessage: `Table 'test.credential' doesn't exist` } : {}),
|
|
150
152
|
});
|
|
151
153
|
if (!on) {
|
|
152
154
|
for (const log of captured) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import knex from 'knex';
|
|
2
2
|
import { inspect } from 'util';
|
|
3
|
-
import { KnexDriver } from '@proteinjs/db-driver-knex';
|
|
3
|
+
import { KnexDriver, KnexFailureLine } from '@proteinjs/db-driver-knex';
|
|
4
4
|
import { Logger } from '@proteinjs/logger';
|
|
5
5
|
import { CapturedLog, lineOf } from './util/printedLine';
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ import { CapturedLog, lineOf } from './util/printedLine';
|
|
|
12
12
|
*
|
|
13
13
|
* Two layers hold that, and this suite holds both:
|
|
14
14
|
*
|
|
15
|
-
* - the helpers that build the line (`describeParams`, `
|
|
15
|
+
* - the helpers that build the line (`describeParams`, `KnexFailureLine.causeOf`) are TOTAL: whatever they
|
|
16
16
|
* are handed — positional bindings, a bindings dictionary, nothing, null, a scalar, a Map, a
|
|
17
17
|
* value whose accessor or `toJSON` throws, a proxy that refuses everything — they return a
|
|
18
18
|
* description and never a value;
|
|
@@ -34,7 +34,6 @@ type ParamsDescription = ParamDescription[] | { [name: string]: ParamDescription
|
|
|
34
34
|
type DriverInternals = {
|
|
35
35
|
logger: Logger;
|
|
36
36
|
describeParams: (params?: unknown) => ParamsDescription | undefined;
|
|
37
|
-
causeSummary: (error: unknown) => { [fact: string]: unknown };
|
|
38
37
|
};
|
|
39
38
|
type DriverStatics = { KNEX?: unknown };
|
|
40
39
|
|
|
@@ -164,13 +163,8 @@ describe('describeParams is total: any bindings, a description, never a value',
|
|
|
164
163
|
});
|
|
165
164
|
});
|
|
166
165
|
|
|
167
|
-
describe('
|
|
168
|
-
const
|
|
169
|
-
host: 'localhost',
|
|
170
|
-
user: 'root',
|
|
171
|
-
password: '',
|
|
172
|
-
dbName: 'test',
|
|
173
|
-
}) as unknown as DriverInternals;
|
|
166
|
+
describe('KnexFailureLine.causeOf is total: anything thrown, a summary of codes and the driver`s sentence, never the vendor`s text', () => {
|
|
167
|
+
const UNNAMED = 'the database refused the statement';
|
|
174
168
|
const vendorFacts = {
|
|
175
169
|
code: 'ER_NO_SUCH_TABLE',
|
|
176
170
|
errno: 1146,
|
|
@@ -182,27 +176,37 @@ describe('causeSummary is total: anything thrown, a summary of codes, never the
|
|
|
182
176
|
[
|
|
183
177
|
'a vendor error whose message and sql carry the bindings',
|
|
184
178
|
() => Object.assign(new Error(`insert ... '${CANARY}'`), vendorFacts, { sql: `insert ... '${CANARY}'` }),
|
|
185
|
-
{
|
|
179
|
+
{
|
|
180
|
+
name: 'Error',
|
|
181
|
+
code: 'ER_NO_SUCH_TABLE',
|
|
182
|
+
errno: 1146,
|
|
183
|
+
sqlState: '42S02',
|
|
184
|
+
message: 'a table the statement names does not exist',
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
['nothing', () => undefined, { message: UNNAMED }],
|
|
188
|
+
['null', () => null, { message: UNNAMED }],
|
|
189
|
+
['a thrown string', () => CANARY, { message: UNNAMED }],
|
|
190
|
+
['a thrown number', () => 1146, { message: UNNAMED }],
|
|
191
|
+
[
|
|
192
|
+
'facts of the wrong kind',
|
|
193
|
+
() => ({ name: 5, code: 1146, errno: '1146', sqlState: null, sqlMessage: {} }),
|
|
194
|
+
{ message: UNNAMED },
|
|
186
195
|
],
|
|
187
|
-
['nothing', () => undefined, {}],
|
|
188
|
-
['null', () => null, {}],
|
|
189
|
-
['a thrown string', () => CANARY, {}],
|
|
190
|
-
['a thrown number', () => 1146, {}],
|
|
191
|
-
['facts of the wrong kind', () => ({ name: 5, code: 1146, errno: '1146', sqlState: null, sqlMessage: {} }), {}],
|
|
192
196
|
[
|
|
193
197
|
'an error with a fact whose accessor throws',
|
|
194
198
|
() => throwingEntry(Object.assign(new Error(CANARY), vendorFacts), 'code'),
|
|
195
|
-
{ name: 'Error', errno: 1146, sqlState: '42S02',
|
|
199
|
+
{ name: 'Error', errno: 1146, sqlState: '42S02', message: UNNAMED },
|
|
196
200
|
],
|
|
197
|
-
['an error that refuses every read', () => refusesEverything(new Error(CANARY)), {}],
|
|
198
|
-
['a revoked error', () => revoked(new Error(CANARY)), {}],
|
|
201
|
+
['an error that refuses every read', () => refusesEverything(new Error(CANARY)), { message: UNNAMED }],
|
|
202
|
+
['a revoked error', () => revoked(new Error(CANARY)), { message: UNNAMED }],
|
|
199
203
|
];
|
|
200
204
|
|
|
201
205
|
test.each(THROWN)('%s', (_shape, thrown, expected) => {
|
|
202
206
|
let summary: { [fact: string]: unknown } | undefined;
|
|
203
207
|
|
|
204
208
|
expect(() => {
|
|
205
|
-
summary =
|
|
209
|
+
summary = KnexFailureLine.causeOf(thrown());
|
|
206
210
|
}).not.toThrow();
|
|
207
211
|
|
|
208
212
|
expect(summary).toEqual(expected);
|
|
@@ -14,8 +14,8 @@ import { CapturedLog, lineOf } from './util/printedLine';
|
|
|
14
14
|
* The contract: the failure line carries the SQL text (placeholders only), the parameters
|
|
15
15
|
* DESCRIBED — each one's position, the kind of its value and, for strings, arrays and bytes, its
|
|
16
16
|
* length; one private helper (`describeParams`) owns that form — and a summary of the failure (the
|
|
17
|
-
* error's name, the vendor's codes, the
|
|
18
|
-
* rides the line. (The dev-only switch that adds the values back is LogValuesSwitch.test.ts; it is
|
|
17
|
+
* error's name, the vendor's codes, the driver's own sentence — KnexFailureLine). The vendor error
|
|
18
|
+
* itself no longer rides the line. (The dev-only switch that adds the values back is LogValuesSwitch.test.ts; it is
|
|
19
19
|
* off here.) A statement's bindings are typed as an array, but the query layer also accepts a
|
|
20
20
|
* bindings DICTIONARY (`:name` placeholders) and the driver forwards what it was handed: a
|
|
21
21
|
* dictionary is described by entry name. (Every other shape the driver can be handed, and a log
|
|
@@ -26,13 +26,10 @@ import { CapturedLog, lineOf } from './util/printedLine';
|
|
|
26
26
|
* that prints what it catches still prints the interpolated SQL; that is the query layer's
|
|
27
27
|
* behaviour and this contract does not change it.
|
|
28
28
|
*
|
|
29
|
-
* SCOPE, by name: this suite holds what the DRIVER prints of the parameters it was handed.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* work is parked on the branch `fix/driver-logs-never-carry-param-values`, pending a decision. So
|
|
34
|
-
* the canaries below are bound to parameters the server does not echo, and the one value it does
|
|
35
|
-
* echo — the colliding key — is left out of the assertions on purpose.
|
|
29
|
+
* SCOPE, by name: this suite holds what the DRIVER prints of the parameters it was handed. The
|
|
30
|
+
* vendor-echo class — the server's own message quoting a bound value (`Duplicate entry '…' for key
|
|
31
|
+
* 'PRIMARY'`) — is VendorTextNeverOnALine.test.ts: no line carries the server's message, and a
|
|
32
|
+
* caller's line about the error it caught carries the driver's sentence in its place.
|
|
36
33
|
*
|
|
37
34
|
* No server is needed: the REAL query layer and dialect run over a stub connection that rejects
|
|
38
35
|
* the way the client library does (its own `format` builds the interpolated `sql`), so the
|
|
@@ -140,11 +137,10 @@ describe('The driver never prints a bound value; what it throws is the vendor`s
|
|
|
140
137
|
|
|
141
138
|
const failures = captured.filter((log) => log.logLevel === 'error' && log.message === 'Failed when executing sql');
|
|
142
139
|
expect(failures).toHaveLength(1);
|
|
143
|
-
// (`ECHOED_KEY` is NOT asserted: the server quotes the colliding key in its own message — the
|
|
144
|
-
// vendor-echo class this suite leaves out by name; see the header.)
|
|
145
140
|
for (const log of captured) {
|
|
146
141
|
expect(lineOf(log)).not.toContain(SECRET_TOKEN);
|
|
147
142
|
expect(lineOf(log)).not.toContain(SECRET_EMAIL);
|
|
143
|
+
expect(lineOf(log)).not.toContain(ECHOED_KEY);
|
|
148
144
|
}
|
|
149
145
|
const [failure] = failures;
|
|
150
146
|
expect(failure.obj.sql).toBe(INSERT);
|
|
@@ -160,7 +156,7 @@ describe('The driver never prints a bound value; what it throws is the vendor`s
|
|
|
160
156
|
code: 'ER_DUP_ENTRY',
|
|
161
157
|
errno: 1062,
|
|
162
158
|
sqlState: '23000',
|
|
163
|
-
|
|
159
|
+
message: 'a row with that key already exists',
|
|
164
160
|
});
|
|
165
161
|
expect(Object.keys(failure.obj)).not.toContain('paramValues');
|
|
166
162
|
// The vendor error itself — the carrier of the interpolated SQL — is not on the line.
|
|
@@ -188,11 +184,10 @@ describe('The driver never prints a bound value; what it throws is the vendor`s
|
|
|
188
184
|
const failures = captured.filter((log) => log.logLevel === 'error');
|
|
189
185
|
expect(failures).toHaveLength(1);
|
|
190
186
|
expect(failures[0].obj.params).toEqual([{ type: 'string', length: SECRET_TOKEN.length }]);
|
|
191
|
-
// This statement's first binding IS the token, so the stub server echoes it
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
expect(
|
|
195
|
-
expect(lineOf({ ...failures[0], obj: { ...failures[0].obj, cause: ownFacts } })).not.toContain(SECRET_TOKEN);
|
|
187
|
+
// This statement's first binding IS the token, so the stub server echoes it in its own
|
|
188
|
+
// message — which no line carries.
|
|
189
|
+
expect(Object.keys(failures[0].obj.cause)).not.toContain('sqlMessage');
|
|
190
|
+
expect(lineOf(failures[0])).not.toContain(SECRET_TOKEN);
|
|
196
191
|
});
|
|
197
192
|
|
|
198
193
|
test('a failed statement inside a transaction: the same line, and the transaction rejects with the vendor`s error itself', async () => {
|