Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .docker/clickhouse/single_node_tls/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM clickhouse/clickhouse-server:25.8-alpine
FROM clickhouse/clickhouse-server:25.10-alpine
COPY .docker/clickhouse/single_node_tls/certificates /etc/clickhouse-server/certs
RUN chown clickhouse:clickhouse -R /etc/clickhouse-server/certs \
&& chmod 600 /etc/clickhouse-server/certs/* \
Expand Down
20 changes: 12 additions & 8 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

<!-- delete unnecessary items -->

### Describe the bug

### Steps to reproduce

1.
2.
3.
Expand All @@ -22,13 +23,16 @@ assignees: ''
### Error log

### Configuration

#### Environment
* Client version:
* Language version:
* OS:

- Client version:
- Language version:
- OS:

#### ClickHouse server
* ClickHouse Server version:
* ClickHouse Server non-default settings, if any:
* `CREATE TABLE` statements for tables involved:
* Sample data for all these tables, use [clickhouse-obfuscator](https://github.com/ClickHouse/ClickHouse/blob/master/programs/obfuscator/Obfuscator.cpp#L42-L80) if necessary

- ClickHouse Server version:
- ClickHouse Server non-default settings, if any:
- `CREATE TABLE` statements for tables involved:
- Sample data for all these tables, use [clickhouse-obfuscator](https://github.com/ClickHouse/ClickHouse/blob/master/programs/obfuscator/Obfuscator.cpp#L42-L80) if necessary
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ about: Suggest an idea for the client
title: ''
labels: enhancement
assignees: ''

---

<!-- delete unnecessary items -->

### Use case

### Describe the solution you'd like
Expand Down
3 changes: 3 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
## Summary

A short description of the changes with a link to an open issue.

## Checklist

Delete items not relevant to your PR:

- [ ] Unit and integration tests covering the common scenarios were added
- [ ] A human-readable description of the changes was provided to include in CHANGELOG
- [ ] For significant changes, documentation in https://github.com/ClickHouse/clickhouse-docs was updated with further explanations or tutorials
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 1.13.0

## New features

- Server-side exceptions that occur in the middle of the HTTP stream are now handled correctly. This requires [ClickHouse 25.11+](https://github.com/ClickHouse/ClickHouse/pull/88818). Previous ClickHouse versions are unaffected by this change. ([#478])

[#478]: https://github.com/ClickHouse/clickhouse-js/pull/478

# 1.12.1

## Improvements
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.cluster.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '2.3'

services:
clickhouse1:
image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-25.8-alpine}'
image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-25.10-alpine}'
ulimits:
nofile:
soft: 262144
Expand All @@ -21,7 +21,7 @@ services:
- './.docker/clickhouse/users.xml:/etc/clickhouse-server/users.xml'

clickhouse2:
image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-25.8-alpine}'
image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-25.10-alpine}'
ulimits:
nofile:
soft: 262144
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#version: '3.8'
services:
clickhouse:
image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-25.8-alpine}'
image: 'clickhouse/clickhouse-server:${CLICKHOUSE_VERSION-25.10-alpine}'
container_name: 'clickhouse-js-clickhouse-server'
environment:
CLICKHOUSE_SKIP_USER_SETUP: 1
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ export default defineConfig(
},
// Ignore build artifacts and externals
{
ignores: ['out', 'dist', 'node_modules', 'webpack'],
ignores: ['coverage', 'out', 'dist', 'node_modules', 'webpack'],
},
)
4 changes: 4 additions & 0 deletions karma.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export default function (config) {
],
},
reporters: ['mocha'],
mochaReporter: {
output: 'minimal',
ignoreSkipped: true,
},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"test": ".scripts/jasmine.sh jasmine.all.json",
"test:common:unit": ".scripts/jasmine.sh jasmine.common.unit.json",
"test:common:unit": "CLICKHOUSE_TEST_SKIP_INIT=1 .scripts/jasmine.sh jasmine.common.unit.json",
"test:common:integration": ".scripts/jasmine.sh jasmine.common.integration.json",
"test:node:unit": ".scripts/jasmine.sh jasmine.node.unit.json",
"test:node:unit": "CLICKHOUSE_TEST_SKIP_INIT=1 .scripts/jasmine.sh jasmine.node.unit.json",
"test:node:tls": ".scripts/jasmine.sh jasmine.node.tls.json",
"test:node:integration": ".scripts/jasmine.sh jasmine.node.integration.json",
"test:node:integration:local_cluster": "CLICKHOUSE_TEST_ENVIRONMENT=local_cluster npm run test:node:integration",
Expand All @@ -42,7 +42,7 @@
"prepare": "husky"
},
"devDependencies": {
"@eslint/js": "^9.34.0",
"@eslint/js": "^9.39.1",
"@faker-js/faker": "^10.0.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/jasmine": "^5.1.8",
Expand All @@ -52,7 +52,7 @@
"@types/split2": "^4.2.3",
"@types/uuid": "^11.0.0",
"apache-arrow": "^21.0.0",
"eslint": "^9.34.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-expect-type": "^0.6.2",
"eslint-plugin-prettier": "^5.5.4",
Expand Down Expand Up @@ -81,8 +81,8 @@
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"tsconfig-paths-webpack-plugin": "^4.2.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.43.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4",
"uuid": "^13.0.0",
"webpack": "^5.101.1",
"webpack-cli": "^6.0.1",
Expand Down
10 changes: 6 additions & 4 deletions packages/client-common/__tests__/fixtures/read_only_user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ClickHouseClient } from '@clickhouse/client-common'
import { PRINT_DDL } from '@test/utils/test_env'
import {
getClickHouseTestEnvironment,
getTestDatabaseName,
Expand Down Expand Up @@ -57,18 +58,19 @@ export async function createReadOnlyUser(client: ClickHouseClient) {
`
break
}

for (const query of [createUser, grant]) {
await client.command({
query,
clickhouse_settings: {
wait_end_of_query: 1,
},
})

if (PRINT_DDL) {
console.info(`\nCreated read-only user with DDL:\n${query}`)
}
}
console.log(
`Created user ${username} with default database ${database} ` +
'and restricted access to the system database',
)

return {
username,
Expand Down
26 changes: 26 additions & 0 deletions packages/client-common/__tests__/fixtures/stream_errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { QueryParamsWithFormat } from '@clickhouse/client-common'
import { ClickHouseError } from '@clickhouse/client-common'

export function streamErrorQueryParams(): QueryParamsWithFormat<'JSONEachRow'> {
return {
query: `SELECT toInt32(number) AS n,
throwIf(number = 10, 'boom') AS e
FROM system.numbers LIMIT 10000000`,
format: 'JSONEachRow',
clickhouse_settings: {
// enforcing at least a few blocks, so that the response code is 200 OK
max_block_size: '1',
// this is false by default since 25.11; no need to set it explicitly
// http_write_exception_in_output_format: false,
},
}
}

export function assertError(err: Error | null) {
expect(err).not.toBeNull()
expect(err).toBeInstanceOf(ClickHouseError)

const chErr = err as ClickHouseError
expect(chErr.message).toContain(`boom: while executing 'FUNCTION throwIf`)
expect(chErr.code).toBe('395')
}
57 changes: 57 additions & 0 deletions packages/client-common/__tests__/unit/stream_utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { checkErrorInChunkAtIndex } from '@clickhouse/client-common'

describe('utils/stream', () => {
const errMsg = 'boom'
const tag = 'FOOBAR'

it('should handle a valid error chunk', async () => {
const chunk = buildValidErrorChunk(errMsg, tag)
const newLineIdx = 1

const err = checkErrorInChunkAtIndex(chunk, newLineIdx, tag)
expect(err).toBeDefined()
expect(err).toBeInstanceOf(Error)
expect(err!.message).toBe(errMsg)
})

it('should handle a broken chunk', async () => {
const chunk = new TextEncoder().encode(
'\r\nsome random data \nthat does not conform\r\t to the protocol\r\n',
)
const newLineIdx = 1

const err = checkErrorInChunkAtIndex(chunk, newLineIdx, tag)
expect(err).toBeDefined()
expect(err).toBeInstanceOf(Error)
expect(err?.message).toContain('error in the stream')
})

it('should handle a partial of a valid chunk', async () => {
const chunk = buildValidErrorChunk(errMsg, tag).slice(0, 20)
const newLineIdx = 1

const err = checkErrorInChunkAtIndex(chunk, newLineIdx, tag)
expect(err).toBeDefined()
expect(err).toBeInstanceOf(Error)
expect(err?.message).toContain('error in the stream')
})
})

/**
* \r\n__exception__\r\nFOOBAR
* boom
* 5 FOOBAR\r\n__exception__\r\n
*/
export function buildValidErrorChunk(errMsg: string, tag: string): Uint8Array {
const chunkStr =
'\r\n__exception__\r\n' +
tag +
'\n' +
errMsg +
'\n' +
(errMsg.length + 1) + // +1 to len for the newline character
' ' +
tag +
'\r\n__exception__\r\n'
return new TextEncoder().encode(chunkStr)
}
27 changes: 20 additions & 7 deletions packages/client-common/__tests__/utils/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import type {
ClickHouseClient,
ClickHouseSettings,
} from '@clickhouse/client-common'
import { cacheServerVersion } from '@test/utils/server_version'
import { EnvKeys, getFromEnv } from './env'
import { guid } from './guid'
import {
getClickHouseTestEnvironment,
isCloudTestEnv,
PRINT_DDL,
SKIP_INIT,
TestEnv,
} from './test_env'
import { TestLogger } from './test_logger'
Expand All @@ -17,19 +20,26 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 400_000

let databaseName: string
beforeAll(async () => {
if (SKIP_INIT) {
// it will be skipped for unit tests that don't require DB setup
console.log('\nSkipping test environment initialization')
return
}

console.log(
`\nTest environment: ${getClickHouseTestEnvironment()}, database: ${
databaseName ?? 'default'
}`,
)
const initClient = createTestClient({
request_timeout: 10_000,
})
if (isCloudTestEnv() && databaseName === undefined) {
const cloudInitClient = createTestClient({
request_timeout: 10_000,
})
await wakeUpPing(cloudInitClient)
databaseName = await createRandomDatabase(cloudInitClient)
await cloudInitClient.close()
await wakeUpPing(initClient)
databaseName = await createRandomDatabase(initClient)
}
await cacheServerVersion(initClient)
await initClient.close()
})

export function createTestClient<Stream = unknown>(
Expand Down Expand Up @@ -128,7 +138,10 @@ export async function createTable<Stream = unknown>(
...(clickhouse_settings || {}),
},
})
console.log(`\nCreated a table using DDL:\n${ddl}`)

if (PRINT_DDL) {
console.info(`\nCreated a table using DDL:\n${ddl}`)
}
}

export function getTestDatabaseName(): string {
Expand Down
3 changes: 2 additions & 1 deletion packages/client-common/__tests__/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { guid, validateUUID } from './guid'
export { getClickHouseTestEnvironment } from './test_env'
export { TestEnv } from './test_env'
export { sleep } from './sleep'
export { whenOnEnv } from './jasmine'
export { whenOnEnv, requireServerVersionAtLeast } from './jasmine'
export { getRandomInt } from './random'
export * from './permutations'
export * from './server_version'
Loading