Skip to content

Commit fa85613

Browse files
authored
Fix unhandled ResultSet.json errors, prepare 1.6.0 (#311)
1 parent 9254e24 commit fa85613

File tree

7 files changed

+91
-24
lines changed

7 files changed

+91
-24
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
# Unreleased
1+
# 1.6.0 (Common, Node.js, Web)
22

33
## New features
44

55
- Added optional `real_time_microseconds` field to the `ClickHouseSummary` interface (see https://github.com/ClickHouse/ClickHouse/pull/69032)
66

7+
## Bug fixes
8+
9+
- Fixed unhandled exceptions produced when calling `ResultSet.json` if the response data was not in fact a valid JSON. ([#311](https://github.com/ClickHouse/clickhouse-js/pull/311))
10+
711
# 1.5.0 (Node.js)
812

913
## New features
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export default '1.5.0'
1+
export default '1.6.0'

packages/client-node/__tests__/unit/node_result_set.test.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Row } from '@clickhouse/client-common'
1+
import type { DataFormat, Row } from '@clickhouse/client-common'
22
import { guid } from '@test/utils'
33
import Stream, { Readable } from 'stream'
44
import { ResultSet } from '../../src'
@@ -63,10 +63,81 @@ describe('[Node.js] ResultSet', () => {
6363
expect(row.json()).toEqual({ foo: 'bar' })
6464
})
6565

66-
function makeResultSet(stream: Stream.Readable) {
66+
describe('unhandled exceptions with streamable JSON formats', () => {
67+
const logAndQuit = (err: Error | unknown, prefix: string) => {
68+
console.error(prefix, err)
69+
process.exit(1)
70+
}
71+
const uncaughtExceptionListener = (err: Error) =>
72+
logAndQuit(err, 'uncaughtException:')
73+
const unhandledRejectionListener = (err: unknown) =>
74+
logAndQuit(err, 'unhandledRejection:')
75+
76+
const invalidJSON = 'invalid":"foo"}\n'
77+
78+
beforeAll(() => {
79+
process.on('uncaughtException', uncaughtExceptionListener)
80+
process.on('unhandledRejection', unhandledRejectionListener)
81+
})
82+
afterAll(() => {
83+
process.off('uncaughtException', uncaughtExceptionListener)
84+
process.off('unhandledRejection', unhandledRejectionListener)
85+
})
86+
87+
describe('Streamable JSON formats - JSONEachRow', () => {
88+
it('should not be produced (ResultSet.text)', async () => {
89+
const rs = makeResultSet(
90+
Stream.Readable.from([Buffer.from(invalidJSON)]),
91+
)
92+
const text = await rs.text()
93+
expect(text).toEqual(invalidJSON)
94+
})
95+
96+
it('should not be produced (ResultSet.json)', async () => {
97+
const rs = makeResultSet(
98+
Stream.Readable.from([Buffer.from(invalidJSON)]),
99+
)
100+
const jsonPromise = rs.json()
101+
await expectAsync(jsonPromise).toBeRejectedWith(
102+
jasmine.objectContaining({
103+
name: 'SyntaxError',
104+
}),
105+
)
106+
})
107+
})
108+
109+
describe('Non-streamable JSON formats - JSON', () => {
110+
it('should not be produced (ResultSet.text)', async () => {
111+
const rs = makeResultSet(
112+
Stream.Readable.from([Buffer.from(invalidJSON)]),
113+
'JSON',
114+
)
115+
const text = await rs.text()
116+
expect(text).toEqual(invalidJSON)
117+
})
118+
119+
it('should not be produced (ResultSet.json)', async () => {
120+
const rs = makeResultSet(
121+
Stream.Readable.from([Buffer.from(invalidJSON)]),
122+
'JSON',
123+
)
124+
const jsonPromise = rs.json()
125+
await expectAsync(jsonPromise).toBeRejectedWith(
126+
jasmine.objectContaining({
127+
name: 'SyntaxError',
128+
}),
129+
)
130+
})
131+
})
132+
})
133+
134+
function makeResultSet(
135+
stream: Stream.Readable,
136+
format: DataFormat = 'JSONEachRow',
137+
) {
67138
return ResultSet.instance({
68139
stream,
69-
format: 'JSONEachRow',
140+
format,
70141
query_id: guid(),
71142
log_error: (err) => {
72143
console.error(err)

packages/client-node/src/result_set.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,12 @@ export class ResultSet<Format extends DataFormat | unknown>
8282
// JSONEachRow, etc.
8383
if (isStreamableJSONFamily(this.format as DataFormat)) {
8484
const result: T[] = []
85-
await new Promise((resolve, reject) => {
86-
const stream = this.stream<T>()
87-
stream.on('data', (rows: Row[]) => {
88-
for (const row of rows) {
89-
result.push(row.json())
90-
}
91-
})
92-
stream.on('end', resolve)
93-
stream.on('error', reject)
94-
})
85+
const stream = this.stream<T>()
86+
for await (const rows of stream) {
87+
for (const row of rows) {
88+
result.push(row.json())
89+
}
90+
}
9591
return result as any
9692
}
9793
// JSON, JSONObjectEachRow, etc.

packages/client-node/src/utils/stream.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@ export async function getAsText(stream: Stream.Readable): Promise<string> {
88
let text = ''
99

1010
const textDecoder = new TextDecoder()
11-
await new Promise((resolve, reject) => {
12-
stream.on('data', (chunk) => {
13-
text += textDecoder.decode(chunk, { stream: true })
14-
})
15-
stream.on('end', resolve)
16-
stream.on('error', reject)
17-
})
11+
for await (const chunk of stream) {
12+
text += textDecoder.decode(chunk, { stream: true })
13+
}
1814

1915
// flush
2016
const last = textDecoder.decode()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export default '1.5.0'
1+
export default '1.6.0'

packages/client-web/src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export default '1.5.0'
1+
export default '1.6.0'

0 commit comments

Comments
 (0)