Skip to content

Commit 46b7e2a

Browse files
authored
Add ignore_error_response option for exec calls (#483)
1 parent 2f96232 commit 46b7e2a

File tree

5 files changed

+64
-8
lines changed

5 files changed

+64
-8
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 1.14.0
2+
3+
## New features
4+
5+
- Added an `ignore_error_response` key to `client.exec`, which allows callers to manually handle errors. ([#483])
6+
17
# 1.13.0
28

39
## New features

packages/client-common/src/client.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ export type ExecParams = BaseQueryParams & {
8888
* @note 2) In case of an error, the stream will be decompressed anyway, regardless of this setting.
8989
* @default true */
9090
decompress_response_stream?: boolean
91+
/**
92+
* If set to `true`, the client will ignore error responses from the server and return them as-is in the response stream.
93+
* This could be useful if you want to handle error responses manually.
94+
* @note 1) Node.js only. This setting will have no effect on the Web version.
95+
* @note 2) Default behavior is to not ignore error responses, and throw an error when an error response
96+
* is received. This includes decompressing the error response stream if it is compressed.
97+
* @default false
98+
*/
99+
ignore_error_response?: boolean
91100
}
92101
export type ExecParamsWithValues<Stream> = ExecParams & {
93102
/** If you have a custom INSERT statement to run with `exec`, the data from this stream will be inserted.
@@ -277,10 +286,12 @@ export class ClickHouseClient<Stream = unknown> {
277286
const query = removeTrailingSemi(params.query.trim())
278287
const values = 'values' in params ? params.values : undefined
279288
const decompress_response_stream = params.decompress_response_stream ?? true
289+
const ignore_error_response = params.ignore_error_response ?? false
280290
return await this.connection.exec({
281291
query,
282292
values,
283293
decompress_response_stream,
294+
ignore_error_response,
284295
...this.withClientQueryParams(params),
285296
})
286297
}

packages/client-common/src/connection.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface ConnInsertParams<Stream> extends ConnBaseQueryParams {
5454
export interface ConnExecParams<Stream> extends ConnBaseQueryParams {
5555
values?: Stream
5656
decompress_response_stream?: boolean
57+
ignore_error_response?: boolean
5758
}
5859

5960
export interface ConnBaseResult extends WithResponseHeaders {

packages/client-node/__tests__/integration/node_exec.test.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,42 @@ describe('[Node.js] exec', () => {
196196
}),
197197
)
198198
})
199+
})
199200

200-
function decompress(stream: Stream.Readable) {
201-
return Stream.pipeline(stream, Zlib.createGunzip(), (err) => {
202-
if (err) {
203-
console.error(err)
204-
}
201+
describe('ignore error response', () => {
202+
beforeEach(() => {
203+
client = createTestClient({
204+
compression: {
205+
response: true,
206+
},
205207
})
206-
}
208+
})
209+
210+
it('should get a decompressed response stream if ignore_error_response is true and default decompression config is passed', async () => {
211+
const result = await client.exec({
212+
query: 'invalid',
213+
ignore_error_response: true,
214+
})
215+
const text = await getAsText(result.stream)
216+
expect(text).toContain('Syntax error')
217+
})
218+
219+
it('should get a compressed response stream if ignore_error_response is true and decompression is disabled', async () => {
220+
const result = await client.exec({
221+
query: 'invalid',
222+
decompress_response_stream: false,
223+
ignore_error_response: true,
224+
})
225+
const text = await getAsText(decompress(result.stream))
226+
expect(text).toContain('Syntax error')
227+
})
207228
})
208229
})
230+
231+
function decompress(stream: Stream.Readable) {
232+
return Stream.pipeline(stream, Zlib.createGunzip(), (err) => {
233+
if (err) {
234+
console.error(err)
235+
}
236+
})
237+
}

packages/client-node/src/connection/node_base_connection.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export interface RequestParams {
7373
enable_request_compression?: boolean
7474
// if there are compression headers, attempt to decompress it
7575
try_decompress_response_stream?: boolean
76+
// if the response contains an error, ignore it and return the stream as-is
77+
ignore_error_response?: boolean
7678
parse_summary?: boolean
7779
query: string
7880
}
@@ -468,6 +470,7 @@ export abstract class NodeBaseConnection
468470
: // there is nothing useful in the response stream for the `Command` operation,
469471
// and it is immediately destroyed; never decompress it
470472
false
473+
const ignoreErrorResponse = params.ignore_error_response ?? false
471474
try {
472475
const { stream, summary, response_headers } = await this.request(
473476
{
@@ -480,6 +483,7 @@ export abstract class NodeBaseConnection
480483
enable_response_compression:
481484
this.params.compression.decompress_response,
482485
try_decompress_response_stream: tryDecompressResponseStream,
486+
ignore_error_response: ignoreErrorResponse,
483487
headers: this.buildRequestHeaders(params),
484488
query: params.query,
485489
},
@@ -537,9 +541,13 @@ export abstract class NodeBaseConnection
537541
this.logResponse(op, request, params, _response, start)
538542
const tryDecompressResponseStream =
539543
params.try_decompress_response_stream ?? true
544+
const ignoreErrorResponse = params.ignore_error_response ?? false
540545
// even if the stream decompression is disabled, we have to decompress it in case of an error
541546
const isFailedResponse = !isSuccessfulResponse(_response.statusCode)
542-
if (tryDecompressResponseStream || isFailedResponse) {
547+
if (
548+
tryDecompressResponseStream ||
549+
(isFailedResponse && !ignoreErrorResponse)
550+
) {
543551
const decompressionResult = decompressResponse(_response, this.logger)
544552
if (isDecompressionError(decompressionResult)) {
545553
const err = enhanceStackTrace(
@@ -552,7 +560,7 @@ export abstract class NodeBaseConnection
552560
} else {
553561
responseStream = _response
554562
}
555-
if (isFailedResponse) {
563+
if (isFailedResponse && !ignoreErrorResponse) {
556564
try {
557565
const errorMessage = await getAsText(responseStream)
558566
const err = enhanceStackTrace(
@@ -795,6 +803,7 @@ type RunExecParams = ConnBaseQueryParams & {
795803
op: 'Exec' | 'Command'
796804
values?: ConnExecParams<Stream.Readable>['values']
797805
decompress_response_stream?: boolean
806+
ignore_error_response?: boolean
798807
}
799808

800809
const PingQuery = `SELECT 'ping'`

0 commit comments

Comments
 (0)