@clickhouse/client 1.17.0 → 1.18.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/dist/connection/compression.d.ts +2 -1
- package/dist/connection/compression.js +8 -5
- package/dist/connection/compression.js.map +1 -1
- package/dist/connection/node_base_connection.d.ts +11 -3
- package/dist/connection/node_base_connection.js +379 -157
- package/dist/connection/node_base_connection.js.map +1 -1
- package/dist/connection/node_custom_agent_connection.js +1 -6
- package/dist/connection/node_custom_agent_connection.js.map +1 -1
- package/dist/connection/stream.d.ts +15 -1
- package/dist/connection/stream.js +179 -2
- package/dist/connection/stream.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/result_set.js +10 -42
- package/dist/result_set.js.map +1 -1
- package/dist/utils/encoder.js +1 -6
- package/dist/utils/encoder.js.map +1 -1
- package/dist/utils/runtime.js +3 -18
- package/dist/utils/runtime.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
|
@@ -12,55 +12,31 @@ const utils_1 = require("../utils");
|
|
|
12
12
|
const compression_1 = require("./compression");
|
|
13
13
|
const stream_2 = require("./stream");
|
|
14
14
|
class NodeBaseConnection {
|
|
15
|
+
params;
|
|
16
|
+
agent;
|
|
17
|
+
defaultAuthHeader;
|
|
18
|
+
defaultHeaders;
|
|
19
|
+
jsonHandling;
|
|
20
|
+
knownSockets = new WeakMap();
|
|
21
|
+
idleSocketTTL;
|
|
22
|
+
connectionId = crypto_1.default.randomUUID();
|
|
23
|
+
socketCounter = 0;
|
|
24
|
+
// For overflow concerns:
|
|
25
|
+
// node -e 'console.log(Number.MAX_SAFE_INTEGER / (1_000_000 * 60 * 60 * 24 * 366))'
|
|
26
|
+
// gives 284 years of continuous operation at 1M requests per second
|
|
27
|
+
// before overflowing the 53-bit integer
|
|
28
|
+
requestCounter = 0;
|
|
29
|
+
getNewSocketId() {
|
|
30
|
+
this.socketCounter += 1;
|
|
31
|
+
return `${this.connectionId}:${this.socketCounter}`;
|
|
32
|
+
}
|
|
33
|
+
getNewRequestId() {
|
|
34
|
+
this.requestCounter += 1;
|
|
35
|
+
return `${this.connectionId}:${this.requestCounter}`;
|
|
36
|
+
}
|
|
15
37
|
constructor(params, agent) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
configurable: true,
|
|
19
|
-
writable: true,
|
|
20
|
-
value: params
|
|
21
|
-
});
|
|
22
|
-
Object.defineProperty(this, "agent", {
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
writable: true,
|
|
26
|
-
value: agent
|
|
27
|
-
});
|
|
28
|
-
Object.defineProperty(this, "defaultAuthHeader", {
|
|
29
|
-
enumerable: true,
|
|
30
|
-
configurable: true,
|
|
31
|
-
writable: true,
|
|
32
|
-
value: void 0
|
|
33
|
-
});
|
|
34
|
-
Object.defineProperty(this, "defaultHeaders", {
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true,
|
|
37
|
-
writable: true,
|
|
38
|
-
value: void 0
|
|
39
|
-
});
|
|
40
|
-
Object.defineProperty(this, "jsonHandling", {
|
|
41
|
-
enumerable: true,
|
|
42
|
-
configurable: true,
|
|
43
|
-
writable: true,
|
|
44
|
-
value: void 0
|
|
45
|
-
});
|
|
46
|
-
Object.defineProperty(this, "logger", {
|
|
47
|
-
enumerable: true,
|
|
48
|
-
configurable: true,
|
|
49
|
-
writable: true,
|
|
50
|
-
value: void 0
|
|
51
|
-
});
|
|
52
|
-
Object.defineProperty(this, "knownSockets", {
|
|
53
|
-
enumerable: true,
|
|
54
|
-
configurable: true,
|
|
55
|
-
writable: true,
|
|
56
|
-
value: new WeakMap()
|
|
57
|
-
});
|
|
58
|
-
Object.defineProperty(this, "idleSocketTTL", {
|
|
59
|
-
enumerable: true,
|
|
60
|
-
configurable: true,
|
|
61
|
-
writable: true,
|
|
62
|
-
value: void 0
|
|
63
|
-
});
|
|
38
|
+
this.params = params;
|
|
39
|
+
this.agent = agent;
|
|
64
40
|
if (params.auth.type === 'Credentials') {
|
|
65
41
|
this.defaultAuthHeader = `Basic ${Buffer.from(`${params.auth.username}:${params.auth.password}`).toString('base64')}`;
|
|
66
42
|
}
|
|
@@ -75,7 +51,6 @@ class NodeBaseConnection {
|
|
|
75
51
|
Connection: this.params.keep_alive.enabled ? 'keep-alive' : 'close',
|
|
76
52
|
'User-Agent': (0, utils_1.getUserAgent)(this.params.application_id),
|
|
77
53
|
};
|
|
78
|
-
this.logger = params.log_writer;
|
|
79
54
|
this.idleSocketTTL = params.keep_alive.idle_socket_ttl;
|
|
80
55
|
this.jsonHandling = params.json ?? {
|
|
81
56
|
parse: JSON.parse,
|
|
@@ -83,10 +58,11 @@ class NodeBaseConnection {
|
|
|
83
58
|
};
|
|
84
59
|
}
|
|
85
60
|
async ping(params) {
|
|
61
|
+
const { log_writer, log_level } = this.params;
|
|
86
62
|
const query_id = this.getQueryId(params.query_id);
|
|
87
63
|
const { controller, controllerCleanup } = this.getAbortController(params);
|
|
88
|
-
let result;
|
|
89
64
|
try {
|
|
65
|
+
let result;
|
|
90
66
|
if (params.select) {
|
|
91
67
|
const searchParams = (0, client_common_1.toSearchParams)({
|
|
92
68
|
database: undefined,
|
|
@@ -94,23 +70,34 @@ class NodeBaseConnection {
|
|
|
94
70
|
query_id,
|
|
95
71
|
});
|
|
96
72
|
result = await this.request({
|
|
73
|
+
query: PingQuery,
|
|
97
74
|
method: 'GET',
|
|
98
75
|
url: (0, client_common_1.transformUrl)({ url: this.params.url, searchParams }),
|
|
99
|
-
query: PingQuery,
|
|
100
76
|
abort_signal: controller.signal,
|
|
101
77
|
headers: this.buildRequestHeaders(),
|
|
78
|
+
query_id,
|
|
79
|
+
log_writer,
|
|
80
|
+
log_level,
|
|
102
81
|
}, 'Ping');
|
|
103
82
|
}
|
|
104
83
|
else {
|
|
105
84
|
result = await this.request({
|
|
85
|
+
query: 'ping',
|
|
106
86
|
method: 'GET',
|
|
107
87
|
url: (0, client_common_1.transformUrl)({ url: this.params.url, pathname: '/ping' }),
|
|
108
88
|
abort_signal: controller.signal,
|
|
109
89
|
headers: this.buildRequestHeaders(),
|
|
110
|
-
|
|
90
|
+
query_id,
|
|
91
|
+
log_writer,
|
|
92
|
+
log_level,
|
|
111
93
|
}, 'Ping');
|
|
112
94
|
}
|
|
113
|
-
await (0, stream_2.
|
|
95
|
+
await (0, stream_2.drainStreamInternal)({
|
|
96
|
+
op: 'Ping',
|
|
97
|
+
log_writer,
|
|
98
|
+
query_id,
|
|
99
|
+
log_level,
|
|
100
|
+
}, result.stream);
|
|
114
101
|
return { success: true };
|
|
115
102
|
}
|
|
116
103
|
catch (error) {
|
|
@@ -118,13 +105,16 @@ class NodeBaseConnection {
|
|
|
118
105
|
// and we don't get unhandled error propagation later
|
|
119
106
|
controller.abort('Ping failed');
|
|
120
107
|
// not an error, as this might be semi-expected
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
108
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.WARN) {
|
|
109
|
+
log_writer.warn({
|
|
110
|
+
message: this.httpRequestErrorMessage('Ping'),
|
|
111
|
+
err: error,
|
|
112
|
+
args: {
|
|
113
|
+
connection_id: this.connectionId,
|
|
114
|
+
query_id,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
128
118
|
return {
|
|
129
119
|
success: false,
|
|
130
120
|
error: error, // should NOT be propagated to the user
|
|
@@ -135,6 +125,7 @@ class NodeBaseConnection {
|
|
|
135
125
|
}
|
|
136
126
|
}
|
|
137
127
|
async query(params) {
|
|
128
|
+
const { log_writer, log_level } = this.params;
|
|
138
129
|
const query_id = this.getQueryId(params.query_id);
|
|
139
130
|
const clickhouse_settings = (0, client_common_1.withHttpSettings)(params.clickhouse_settings, this.params.compression.decompress_response);
|
|
140
131
|
const searchParams = (0, client_common_1.toSearchParams)({
|
|
@@ -157,6 +148,9 @@ class NodeBaseConnection {
|
|
|
157
148
|
enable_response_compression: enableResponseCompression,
|
|
158
149
|
headers: this.buildRequestHeaders(params),
|
|
159
150
|
query: params.query,
|
|
151
|
+
query_id,
|
|
152
|
+
log_writer,
|
|
153
|
+
log_level,
|
|
160
154
|
}, 'Query');
|
|
161
155
|
return {
|
|
162
156
|
stream,
|
|
@@ -185,6 +179,7 @@ class NodeBaseConnection {
|
|
|
185
179
|
}
|
|
186
180
|
}
|
|
187
181
|
async insert(params) {
|
|
182
|
+
const { log_writer, log_level } = this.params;
|
|
188
183
|
const query_id = this.getQueryId(params.query_id);
|
|
189
184
|
const searchParams = (0, client_common_1.toSearchParams)({
|
|
190
185
|
database: this.params.database,
|
|
@@ -206,8 +201,16 @@ class NodeBaseConnection {
|
|
|
206
201
|
parse_summary: true,
|
|
207
202
|
headers: this.buildRequestHeaders(params),
|
|
208
203
|
query: params.query,
|
|
204
|
+
query_id,
|
|
205
|
+
log_writer,
|
|
206
|
+
log_level,
|
|
209
207
|
}, 'Insert');
|
|
210
|
-
await (0, stream_2.
|
|
208
|
+
await (0, stream_2.drainStreamInternal)({
|
|
209
|
+
op: 'Insert',
|
|
210
|
+
log_writer,
|
|
211
|
+
query_id,
|
|
212
|
+
log_level,
|
|
213
|
+
}, stream);
|
|
211
214
|
return { query_id, summary, response_headers, http_status_code };
|
|
212
215
|
}
|
|
213
216
|
catch (err) {
|
|
@@ -229,18 +232,71 @@ class NodeBaseConnection {
|
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
async exec(params) {
|
|
235
|
+
const query_id = this.getQueryId(params.query_id);
|
|
232
236
|
return this.runExec({
|
|
233
237
|
...params,
|
|
238
|
+
query_id,
|
|
234
239
|
op: 'Exec',
|
|
235
240
|
});
|
|
236
241
|
}
|
|
237
242
|
async command(params) {
|
|
238
|
-
const {
|
|
243
|
+
const { log_writer, log_level } = this.params;
|
|
244
|
+
const query_id = this.getQueryId(params.query_id);
|
|
245
|
+
const commandStartTime = Date.now();
|
|
246
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
247
|
+
log_writer.trace({
|
|
248
|
+
message: 'Command: operation started',
|
|
249
|
+
args: {
|
|
250
|
+
operation: 'Command',
|
|
251
|
+
connection_id: this.connectionId,
|
|
252
|
+
query_id,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const { stream, summary, response_headers } = await this.runExec({
|
|
239
257
|
...params,
|
|
258
|
+
query_id,
|
|
240
259
|
op: 'Command',
|
|
241
260
|
});
|
|
261
|
+
const runExecDuration = Date.now() - commandStartTime;
|
|
262
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
263
|
+
log_writer.trace({
|
|
264
|
+
message: 'Command: runExec completed, starting stream drain',
|
|
265
|
+
args: {
|
|
266
|
+
operation: 'Command',
|
|
267
|
+
connection_id: this.connectionId,
|
|
268
|
+
query_id,
|
|
269
|
+
runExec_duration_ms: runExecDuration,
|
|
270
|
+
stream_state: {
|
|
271
|
+
readable: stream.readable,
|
|
272
|
+
readableEnded: stream.readableEnded,
|
|
273
|
+
readableLength: stream.readableLength,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
242
278
|
// ignore the response stream and release the socket immediately
|
|
243
|
-
|
|
279
|
+
const drainStartTime = Date.now();
|
|
280
|
+
await (0, stream_2.drainStreamInternal)({
|
|
281
|
+
op: 'Command',
|
|
282
|
+
log_writer,
|
|
283
|
+
query_id,
|
|
284
|
+
log_level,
|
|
285
|
+
}, stream);
|
|
286
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
287
|
+
const drainDuration = Date.now() - drainStartTime;
|
|
288
|
+
const totalDuration = Date.now() - commandStartTime;
|
|
289
|
+
log_writer.trace({
|
|
290
|
+
message: 'Command: operation completed',
|
|
291
|
+
args: {
|
|
292
|
+
operation: 'Command',
|
|
293
|
+
connection_id: this.connectionId,
|
|
294
|
+
query_id,
|
|
295
|
+
drain_duration_ms: drainDuration,
|
|
296
|
+
total_duration_ms: totalDuration,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
244
300
|
return { query_id, summary, response_headers };
|
|
245
301
|
}
|
|
246
302
|
async close() {
|
|
@@ -303,37 +359,21 @@ class NodeBaseConnection {
|
|
|
303
359
|
},
|
|
304
360
|
};
|
|
305
361
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
logRequestError({ op, err, query_id, query_params, search_params, extra_args, }) {
|
|
325
|
-
this.logger.error({
|
|
326
|
-
message: this.httpRequestErrorMessage(op),
|
|
327
|
-
err: err,
|
|
328
|
-
args: {
|
|
329
|
-
query: query_params.query,
|
|
330
|
-
search_params: search_params?.toString() ?? '',
|
|
331
|
-
with_abort_signal: query_params.abort_signal !== undefined,
|
|
332
|
-
session_id: query_params.session_id,
|
|
333
|
-
query_id: query_id,
|
|
334
|
-
...extra_args,
|
|
335
|
-
},
|
|
336
|
-
});
|
|
362
|
+
logRequestError({ op, err, query_id, query_params, extra_args, }) {
|
|
363
|
+
if (this.params.log_level <= client_common_1.ClickHouseLogLevel.ERROR) {
|
|
364
|
+
this.params.log_writer.error({
|
|
365
|
+
message: this.httpRequestErrorMessage(op),
|
|
366
|
+
err: err,
|
|
367
|
+
args: {
|
|
368
|
+
operation: op,
|
|
369
|
+
connection_id: this.connectionId,
|
|
370
|
+
query_id,
|
|
371
|
+
with_abort_signal: query_params.abort_signal !== undefined,
|
|
372
|
+
session_id: query_params.session_id,
|
|
373
|
+
...extra_args,
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
337
377
|
}
|
|
338
378
|
httpRequestErrorMessage(op) {
|
|
339
379
|
return `${op}: HTTP request error.`;
|
|
@@ -345,18 +385,23 @@ class NodeBaseConnection {
|
|
|
345
385
|
return this.jsonHandling.parse(summaryHeader);
|
|
346
386
|
}
|
|
347
387
|
catch (err) {
|
|
348
|
-
this.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
388
|
+
if (this.params.log_level <= client_common_1.ClickHouseLogLevel.ERROR) {
|
|
389
|
+
this.params.log_writer.error({
|
|
390
|
+
message: `${op}: failed to parse X-ClickHouse-Summary header.`,
|
|
391
|
+
args: {
|
|
392
|
+
operation: op,
|
|
393
|
+
connection_id: this.connectionId,
|
|
394
|
+
'X-ClickHouse-Summary': summaryHeader,
|
|
395
|
+
},
|
|
396
|
+
err: err,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
355
399
|
}
|
|
356
400
|
}
|
|
357
401
|
}
|
|
358
402
|
async runExec(params) {
|
|
359
|
-
const
|
|
403
|
+
const { log_writer, log_level } = this.params;
|
|
404
|
+
const query_id = params.query_id;
|
|
360
405
|
const sendQueryInParams = params.values !== undefined;
|
|
361
406
|
const clickhouse_settings = (0, client_common_1.withHttpSettings)(params.clickhouse_settings, this.params.compression.decompress_response);
|
|
362
407
|
const toSearchParamsOptions = {
|
|
@@ -391,6 +436,9 @@ class NodeBaseConnection {
|
|
|
391
436
|
ignore_error_response: ignoreErrorResponse,
|
|
392
437
|
headers: this.buildRequestHeaders(params),
|
|
393
438
|
query: params.query,
|
|
439
|
+
query_id,
|
|
440
|
+
log_writer,
|
|
441
|
+
log_level,
|
|
394
442
|
}, params.op);
|
|
395
443
|
return {
|
|
396
444
|
stream,
|
|
@@ -422,13 +470,15 @@ class NodeBaseConnection {
|
|
|
422
470
|
// allows the event loop to process the idle socket timers, if the CPU load is high
|
|
423
471
|
// otherwise, we can occasionally get an expired socket, see https://github.com/ClickHouse/clickhouse-js/issues/294
|
|
424
472
|
await (0, client_common_1.sleep)(0);
|
|
473
|
+
const { log_writer, query_id, log_level } = params;
|
|
425
474
|
const currentStackTrace = this.params.capture_enhanced_stack_trace
|
|
426
475
|
? (0, client_common_1.getCurrentStackTrace)()
|
|
427
476
|
: undefined;
|
|
428
|
-
const
|
|
477
|
+
const requestTimeout = this.params.request_timeout;
|
|
429
478
|
return new Promise((resolve, reject) => {
|
|
430
479
|
const start = Date.now();
|
|
431
480
|
const request = this.createClientRequest(params);
|
|
481
|
+
const request_id = this.getNewRequestId();
|
|
432
482
|
function onError(e) {
|
|
433
483
|
removeRequestListeners();
|
|
434
484
|
const err = (0, client_common_1.enhanceStackTrace)(e, currentStackTrace);
|
|
@@ -436,14 +486,30 @@ class NodeBaseConnection {
|
|
|
436
486
|
}
|
|
437
487
|
let responseStream;
|
|
438
488
|
const onResponse = async (_response) => {
|
|
439
|
-
this.
|
|
489
|
+
if (this.params.log_level <= client_common_1.ClickHouseLogLevel.DEBUG) {
|
|
490
|
+
const duration = Date.now() - start;
|
|
491
|
+
this.params.log_writer.debug({
|
|
492
|
+
module: 'HTTP Adapter',
|
|
493
|
+
message: `${op}: got a response from ClickHouse`,
|
|
494
|
+
args: {
|
|
495
|
+
operation: op,
|
|
496
|
+
connection_id: this.connectionId,
|
|
497
|
+
query_id,
|
|
498
|
+
request_id,
|
|
499
|
+
request_method: params.method,
|
|
500
|
+
request_path: params.url.pathname,
|
|
501
|
+
response_status: _response.statusCode,
|
|
502
|
+
response_time_ms: duration,
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
}
|
|
440
506
|
const tryDecompressResponseStream = params.try_decompress_response_stream ?? true;
|
|
441
507
|
const ignoreErrorResponse = params.ignore_error_response ?? false;
|
|
442
508
|
// even if the stream decompression is disabled, we have to decompress it in case of an error
|
|
443
509
|
const isFailedResponse = !(0, client_common_1.isSuccessfulResponse)(_response.statusCode);
|
|
444
510
|
if (tryDecompressResponseStream ||
|
|
445
511
|
(isFailedResponse && !ignoreErrorResponse)) {
|
|
446
|
-
const decompressionResult = (0, compression_1.decompressResponse)(_response,
|
|
512
|
+
const decompressionResult = (0, compression_1.decompressResponse)(_response, log_writer, log_level);
|
|
447
513
|
if ((0, compression_1.isDecompressionError)(decompressionResult)) {
|
|
448
514
|
const err = (0, client_common_1.enhanceStackTrace)(decompressionResult.error, currentStackTrace);
|
|
449
515
|
return reject(err);
|
|
@@ -453,6 +519,24 @@ class NodeBaseConnection {
|
|
|
453
519
|
else {
|
|
454
520
|
responseStream = _response;
|
|
455
521
|
}
|
|
522
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
523
|
+
log_writer.trace({
|
|
524
|
+
message: `${op}: response stream created`,
|
|
525
|
+
args: {
|
|
526
|
+
operation: op,
|
|
527
|
+
connection_id: this.connectionId,
|
|
528
|
+
query_id,
|
|
529
|
+
request_id,
|
|
530
|
+
stream_state: {
|
|
531
|
+
readable: responseStream.readable,
|
|
532
|
+
readableEnded: responseStream.readableEnded,
|
|
533
|
+
readableLength: responseStream.readableLength,
|
|
534
|
+
},
|
|
535
|
+
is_failed_response: isFailedResponse,
|
|
536
|
+
will_decompress: tryDecompressResponseStream,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
}
|
|
456
540
|
if (isFailedResponse && !ignoreErrorResponse) {
|
|
457
541
|
try {
|
|
458
542
|
const errorMessage = await (0, utils_1.getAsText)(responseStream);
|
|
@@ -525,96 +609,226 @@ class NodeBaseConnection {
|
|
|
525
609
|
// It is the first time we've encountered this socket,
|
|
526
610
|
// so it doesn't have the idle timeout handler attached to it
|
|
527
611
|
if (socketInfo === undefined) {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
612
|
+
const socket_id = this.getNewSocketId();
|
|
613
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
614
|
+
log_writer.trace({
|
|
615
|
+
message: `${op}: using a fresh socket, setting up a new 'free' listener`,
|
|
616
|
+
args: {
|
|
617
|
+
operation: op,
|
|
618
|
+
connection_id: this.connectionId,
|
|
619
|
+
query_id,
|
|
620
|
+
request_id,
|
|
621
|
+
socket_id,
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
const newSocketInfo = {
|
|
626
|
+
id: socket_id,
|
|
534
627
|
idle_timeout_handle: undefined,
|
|
535
|
-
|
|
628
|
+
usage_count: 1,
|
|
629
|
+
};
|
|
630
|
+
this.knownSockets.set(socket, newSocketInfo);
|
|
536
631
|
// When the request is complete and the socket is released,
|
|
537
632
|
// make sure that the socket is removed after `idleSocketTTL`.
|
|
538
633
|
socket.on('free', () => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
634
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
635
|
+
log_writer.trace({
|
|
636
|
+
message: `${op}: socket was released`,
|
|
637
|
+
args: {
|
|
638
|
+
operation: op,
|
|
639
|
+
connection_id: this.connectionId,
|
|
640
|
+
query_id,
|
|
641
|
+
request_id,
|
|
642
|
+
socket_id,
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
}
|
|
542
646
|
// Avoiding the built-in socket.timeout() method usage here,
|
|
543
647
|
// as we don't want to clash with the actual request timeout.
|
|
544
648
|
const idleTimeoutHandle = setTimeout(() => {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
649
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
650
|
+
log_writer.trace({
|
|
651
|
+
message: `${op}: removing idle socket`,
|
|
652
|
+
args: {
|
|
653
|
+
operation: op,
|
|
654
|
+
connection_id: this.connectionId,
|
|
655
|
+
query_id,
|
|
656
|
+
request_id,
|
|
657
|
+
socket_id,
|
|
658
|
+
idle_socket_ttl_ms: this.idleSocketTTL,
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
}
|
|
548
662
|
this.knownSockets.delete(socket);
|
|
549
663
|
socket.destroy();
|
|
550
664
|
}, this.idleSocketTTL).unref();
|
|
551
|
-
|
|
552
|
-
id: socketId,
|
|
553
|
-
idle_timeout_handle: idleTimeoutHandle,
|
|
554
|
-
});
|
|
665
|
+
newSocketInfo.idle_timeout_handle = idleTimeoutHandle;
|
|
555
666
|
});
|
|
556
|
-
const cleanup = () => {
|
|
667
|
+
const cleanup = (eventName) => () => {
|
|
557
668
|
const maybeSocketInfo = this.knownSockets.get(socket);
|
|
558
669
|
// clean up a possibly dangling idle timeout handle (preventing leaks)
|
|
559
670
|
if (maybeSocketInfo?.idle_timeout_handle) {
|
|
560
671
|
clearTimeout(maybeSocketInfo.idle_timeout_handle);
|
|
561
672
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (responseStream && !responseStream.readableEnded) {
|
|
566
|
-
this.logger.warn({
|
|
567
|
-
message: `${op}: socket was closed or ended before the response was fully read. ` +
|
|
568
|
-
'This can potentially result in an uncaught ECONNRESET error! ' +
|
|
569
|
-
'Consider fully consuming, draining, or destroying the response stream.',
|
|
673
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
674
|
+
log_writer.trace({
|
|
675
|
+
message: `${op}: received '${eventName}' event, 'free' listener removed`,
|
|
570
676
|
args: {
|
|
571
|
-
|
|
572
|
-
|
|
677
|
+
operation: op,
|
|
678
|
+
connection_id: this.connectionId,
|
|
679
|
+
query_id,
|
|
680
|
+
request_id,
|
|
681
|
+
socket_id,
|
|
682
|
+
event: eventName,
|
|
573
683
|
},
|
|
574
684
|
});
|
|
575
685
|
}
|
|
686
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.WARN) {
|
|
687
|
+
if (responseStream && !responseStream.readableEnded) {
|
|
688
|
+
log_writer.warn({
|
|
689
|
+
message: `${op}: socket was closed or ended before the response was fully read. ` +
|
|
690
|
+
'This can potentially result in an uncaught ECONNRESET error! ' +
|
|
691
|
+
'Consider fully consuming, draining, or destroying the response stream.',
|
|
692
|
+
args: {
|
|
693
|
+
operation: op,
|
|
694
|
+
connection_id: this.connectionId,
|
|
695
|
+
query_id,
|
|
696
|
+
request_id,
|
|
697
|
+
socket_id,
|
|
698
|
+
event: eventName,
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
576
703
|
};
|
|
577
|
-
socket.once('end', cleanup);
|
|
578
|
-
socket.once('close', cleanup);
|
|
704
|
+
socket.once('end', cleanup('end'));
|
|
705
|
+
socket.once('close', cleanup('close'));
|
|
579
706
|
}
|
|
580
707
|
else {
|
|
581
708
|
clearTimeout(socketInfo.idle_timeout_handle);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
709
|
+
socketInfo.idle_timeout_handle = undefined;
|
|
710
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
711
|
+
log_writer.trace({
|
|
712
|
+
message: `${op}: reusing socket`,
|
|
713
|
+
args: {
|
|
714
|
+
operation: op,
|
|
715
|
+
connection_id: this.connectionId,
|
|
716
|
+
query_id,
|
|
717
|
+
request_id,
|
|
718
|
+
socket_id: socketInfo.id,
|
|
719
|
+
usage_count: socketInfo.usage_count,
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
socketInfo.usage_count++;
|
|
589
724
|
}
|
|
590
725
|
}
|
|
591
726
|
}
|
|
592
727
|
catch (e) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
728
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.ERROR) {
|
|
729
|
+
log_writer.error({
|
|
730
|
+
message: `${op}: an error occurred while housekeeping the idle sockets`,
|
|
731
|
+
err: e,
|
|
732
|
+
args: {
|
|
733
|
+
operation: op,
|
|
734
|
+
connection_id: this.connectionId,
|
|
735
|
+
query_id,
|
|
736
|
+
request_id,
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
}
|
|
597
740
|
}
|
|
598
741
|
// Socket is "prepared" with idle handlers, continue with our request
|
|
599
742
|
pipeStream();
|
|
600
743
|
// This is for request timeout only. Surprisingly, it is not always enough to set in the HTTP request.
|
|
601
744
|
// The socket won't be destroyed, and it will be returned to the pool.
|
|
745
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
746
|
+
const socketInfo = this.knownSockets.get(socket);
|
|
747
|
+
if (socketInfo) {
|
|
748
|
+
log_writer.trace({
|
|
749
|
+
message: `${op}: setting up request timeout`,
|
|
750
|
+
args: {
|
|
751
|
+
operation: op,
|
|
752
|
+
connection_id: this.connectionId,
|
|
753
|
+
query_id,
|
|
754
|
+
request_id,
|
|
755
|
+
socket_id: socketInfo.id,
|
|
756
|
+
timeout_ms: requestTimeout,
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
log_writer.trace({
|
|
762
|
+
message: `${op}: setting up request timeout on a socket`,
|
|
763
|
+
args: {
|
|
764
|
+
operation: op,
|
|
765
|
+
connection_id: this.connectionId,
|
|
766
|
+
query_id,
|
|
767
|
+
request_id,
|
|
768
|
+
timeout_ms: requestTimeout,
|
|
769
|
+
},
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
602
773
|
socket.setTimeout(this.params.request_timeout, onTimeout);
|
|
603
774
|
};
|
|
604
|
-
|
|
605
|
-
const err = (0, client_common_1.enhanceStackTrace)(new Error('Timeout error.'), currentStackTrace);
|
|
775
|
+
const onTimeout = () => {
|
|
606
776
|
removeRequestListeners();
|
|
777
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.TRACE) {
|
|
778
|
+
const socket = request.socket;
|
|
779
|
+
const maybeSocketInfo = socket
|
|
780
|
+
? this.knownSockets.get(socket)
|
|
781
|
+
: undefined;
|
|
782
|
+
const socketState = request.socket
|
|
783
|
+
? {
|
|
784
|
+
connecting: request.socket.connecting,
|
|
785
|
+
pending: request.socket.pending,
|
|
786
|
+
destroyed: request.socket.destroyed,
|
|
787
|
+
readyState: request.socket.readyState,
|
|
788
|
+
}
|
|
789
|
+
: undefined;
|
|
790
|
+
const responseStreamState = responseStream
|
|
791
|
+
? {
|
|
792
|
+
readable: responseStream.readable,
|
|
793
|
+
readableEnded: responseStream.readableEnded,
|
|
794
|
+
readableLength: responseStream.readableLength,
|
|
795
|
+
}
|
|
796
|
+
: undefined;
|
|
797
|
+
log_writer.trace({
|
|
798
|
+
message: `${op}: timeout occurred`,
|
|
799
|
+
args: {
|
|
800
|
+
operation: op,
|
|
801
|
+
connection_id: this.connectionId,
|
|
802
|
+
query_id,
|
|
803
|
+
request_id,
|
|
804
|
+
socket_id: maybeSocketInfo?.id,
|
|
805
|
+
timeout_ms: requestTimeout,
|
|
806
|
+
socket_state: socketState,
|
|
807
|
+
response_stream_state: responseStreamState,
|
|
808
|
+
has_response_stream: responseStream !== undefined,
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
const err = (0, client_common_1.enhanceStackTrace)(new Error('Timeout error.'), currentStackTrace);
|
|
607
813
|
try {
|
|
608
814
|
request.destroy();
|
|
609
815
|
}
|
|
610
816
|
catch (e) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
817
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.ERROR) {
|
|
818
|
+
log_writer.error({
|
|
819
|
+
message: `${op}: An error occurred while destroying the request`,
|
|
820
|
+
err: e,
|
|
821
|
+
args: {
|
|
822
|
+
operation: op,
|
|
823
|
+
connection_id: this.connectionId,
|
|
824
|
+
query_id,
|
|
825
|
+
request_id,
|
|
826
|
+
},
|
|
827
|
+
});
|
|
828
|
+
}
|
|
615
829
|
}
|
|
616
830
|
reject(err);
|
|
617
|
-
}
|
|
831
|
+
};
|
|
618
832
|
function removeRequestListeners() {
|
|
619
833
|
if (request.socket !== null) {
|
|
620
834
|
request.socket.setTimeout(0); // reset previously set timeout
|
|
@@ -642,10 +856,18 @@ class NodeBaseConnection {
|
|
|
642
856
|
return request.end();
|
|
643
857
|
}
|
|
644
858
|
catch (e) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
859
|
+
if (log_level <= client_common_1.ClickHouseLogLevel.ERROR) {
|
|
860
|
+
log_writer.error({
|
|
861
|
+
message: `${op}: an error occurred while ending the request without body`,
|
|
862
|
+
err: e,
|
|
863
|
+
args: {
|
|
864
|
+
operation: op,
|
|
865
|
+
connection_id: this.connectionId,
|
|
866
|
+
query_id,
|
|
867
|
+
request_id,
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
}
|
|
649
871
|
}
|
|
650
872
|
}
|
|
651
873
|
});
|