Skip to content

Commit 97db905

Browse files
committed
Fix command and related tests, use more flexible test env, rename configs
1 parent dc29eec commit 97db905

File tree

12 files changed

+153
-64
lines changed

12 files changed

+153
-64
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ jobs:
5252
CLICKHOUSE_CLOUD_HOST: ${{ secrets.CLICKHOUSE_CLOUD_HOST }}
5353
CLICKHOUSE_CLOUD_USERNAME: ${{ secrets.CLICKHOUSE_CLOUD_USERNAME }}
5454
CLICKHOUSE_CLOUD_PASSWORD: ${{ secrets.CLICKHOUSE_CLOUD_PASSWORD }}
55-
CLICKHOUSE_CLOUD_ENABLED: 'true'
55+
CLICKHOUSE_TEST_ENVIRONMENT: 'cloud'
5656
run: |
5757
npm test

__tests__/integration/command.test.ts

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,71 @@
11
import { expect } from 'chai';
2-
import { createClient, type ClickHouseClient } from '../../src';
3-
import type { ResponseJSON } from '../../src/clickhouse_types';
2+
import type { ResponseJSON } from '../../src';
3+
import { type ClickHouseClient } from '../../src';
4+
import {
5+
createTestClient,
6+
getClickHouseTestEnvironment,
7+
TestEnv,
8+
} from '../utils/client';
9+
import { guid } from '../utils';
410

511
describe('command', () => {
612
let client: ClickHouseClient;
13+
beforeEach(() => {
14+
client = createTestClient();
15+
});
716
afterEach(async () => {
8-
await client.command({ query: 'DROP TABLE example' });
917
await client.close();
1018
});
1119

1220
it('sends a command to execute', async () => {
13-
client = createClient();
14-
const ddl =
15-
'CREATE TABLE example (id UInt64, name String, sku Array(UInt8), timestamp DateTime) Engine = Memory';
21+
const { ddl, tableName, engine } = getDDL();
1622

17-
await client.command({ query: ddl });
23+
const commandResult = await client.command({
24+
query: ddl,
25+
format: 'TabSeparated',
26+
});
27+
await commandResult.text();
1828

19-
const result = await client.select({
20-
query: `SELECT * from system.tables where name = 'example'`,
29+
const selectResult = await client.select({
30+
query: `SELECT * from system.tables where name = '${tableName}'`,
2131
format: 'JSON',
2232
});
2333

24-
const { data, rows } = await result.json<
34+
const { data, rows } = await selectResult.json<
2535
ResponseJSON<{ name: string; engine: string; create_table_query: string }>
2636
>();
2737

2838
expect(rows).to.equal(1);
2939
const table = data[0];
30-
expect(table.name).equal('example');
31-
expect(table.engine).equal('Memory');
40+
expect(table.name).equal(tableName);
41+
expect(table.engine).equal(engine);
3242
expect(table.create_table_query).to.be.a('string');
3343
});
3444

3545
it('does not swallow ClickHouse error', (done) => {
36-
client = createClient();
37-
38-
const ddl =
39-
'CREATE TABLE example (id UInt64, name String, sku Array(UInt8), timestamp DateTime) Engine = Memory';
40-
46+
const { ddl, tableName } = getDDL();
4147
Promise.resolve()
4248
.then(() => client.command({ query: ddl }))
4349
.then(() => client.command({ query: ddl }))
4450
.catch((e: any) => {
4551
expect(e.code).to.equal('57');
4652
expect(e.type).to.equal('TABLE_ALREADY_EXISTS');
4753
// TODO remove whitespace from end
48-
expect(e.message).equal('Table default.example already exists. ');
54+
expect(e.message).equal(`Table default.${tableName} already exists. `);
4955
done();
5056
});
5157
});
5258

5359
it.skip('can specify a parameterized query', async () => {
54-
client = createClient();
55-
await client.command({
56-
query:
57-
'CREATE TABLE {table_name: String} (id UInt64, name String, sku Array(UInt8), timestamp DateTime) Engine = Memory',
60+
const commandResult = await client.command({
61+
query: '',
5862
query_params: {
5963
table_name: 'example',
6064
},
6165
});
66+
await commandResult.text();
6267

68+
// FIXME: use different DDL based on the TestEnv
6369
const result = await client.select({
6470
query: `SELECT * from system.tables where name = 'example'`,
6571
format: 'JSON',
@@ -74,3 +80,43 @@ describe('command', () => {
7480
expect(table.name).to.equal('example');
7581
});
7682
});
83+
84+
function getDDL(): {
85+
ddl: string;
86+
tableName: string;
87+
engine: string;
88+
} {
89+
const env = getClickHouseTestEnvironment();
90+
const tableName = `command_test_${guid()}`;
91+
switch (env) {
92+
// ENGINE can be omitted in the cloud statements:
93+
// it will use ReplicatedMergeTree and will add ON CLUSTER as well
94+
case TestEnv.Cloud: {
95+
const ddl = `
96+
CREATE TABLE ${tableName}
97+
(id UInt64, name String, sku Array(UInt8), timestamp DateTime)
98+
ORDER BY (id)
99+
`;
100+
return { ddl, tableName, engine: 'ReplicatedMergeTree' };
101+
}
102+
case TestEnv.LocalSingleNode: {
103+
const ddl = `
104+
CREATE TABLE ${tableName}
105+
(id UInt64, name String, sku Array(UInt8), timestamp DateTime)
106+
ENGINE = MergeTree()
107+
ORDER BY (id)
108+
`;
109+
return { ddl, tableName, engine: 'MergeTree' };
110+
}
111+
112+
case TestEnv.LocalCluster: {
113+
const ddl = `
114+
CREATE TABLE ${tableName} ON CLUSTER '{cluster}'
115+
(id UInt64, name String, sku Array(UInt8), timestamp DateTime)
116+
ENGINE ReplicatedMergeTree('/clickhouse/{cluster}/tables/{database}/{table}/{shard}', '{replica}')
117+
ORDER BY (id)
118+
`;
119+
return { ddl, tableName, engine: 'ReplicatedMergeTree' };
120+
}
121+
}
122+
}

__tests__/integration/insert.test.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import type { ResponseJSON } from '../../src';
33
import { type ClickHouseClient } from '../../src';
44
import { createTable, createTestClient, guid } from '../utils';
5+
import { TestEnv } from '../utils/client';
56

67
describe('insert', () => {
78
let client: ClickHouseClient;
@@ -13,18 +14,32 @@ describe('insert', () => {
1314
},
1415
});
1516
tableName = `test_table_${guid()}`;
16-
await createTable(
17-
client,
18-
(engine) => {
19-
return `
20-
CREATE TABLE ${tableName} ON CLUSTER '{cluster}'
21-
(id UInt64, name String, sku Array(UInt8))
22-
${engine}
23-
ORDER BY (id)
24-
`;
25-
},
26-
`ReplicatedMergeTree('/clickhouse/{cluster}/tables/{database}/{table}/{shard}', '{replica}')`
27-
);
17+
await createTable(client, (env) => {
18+
switch (env) {
19+
// ENGINE can be omitted in the cloud statements:
20+
// it will use ReplicatedMergeTree and will add ON CLUSTER as well
21+
case TestEnv.Cloud:
22+
return `
23+
CREATE TABLE ${tableName}
24+
(id UInt64, name String, sku Array(UInt8))
25+
ORDER BY (id)
26+
`;
27+
case TestEnv.LocalSingleNode:
28+
return `
29+
CREATE TABLE ${tableName}
30+
(id UInt64, name String, sku Array(UInt8))
31+
ENGINE MergeTree()
32+
ORDER BY (id)
33+
`;
34+
case TestEnv.LocalCluster:
35+
return `
36+
CREATE TABLE ${tableName} ON CLUSTER '{cluster}'
37+
(id UInt64, name String, sku Array(UInt8))
38+
ENGINE ReplicatedMergeTree('/clickhouse/{cluster}/tables/{database}/{table}/{shard}', '{replica}')
39+
ORDER BY (id)
40+
`;
41+
}
42+
});
2843
});
2944
afterEach(async () => {
3045
await client.close();

__tests__/utils/client.ts

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
11
import {
22
ClickHouseClient,
33
ClickHouseClientConfigOptions,
4+
ClickHouseSettings,
45
createClient,
56
} from '../../src';
67
import { guid } from './guid';
78

9+
export enum TestEnv {
10+
Cloud = 'CLOUD',
11+
LocalSingleNode = 'LOCAL_SINGLE_NODE',
12+
LocalCluster = 'LOCAL_CLUSTER',
13+
}
14+
815
export function createTestClient(
9-
config?: ClickHouseClientConfigOptions
16+
config: ClickHouseClientConfigOptions = {}
1017
): ClickHouseClient {
11-
if (isClickHouseCloudEnabled()) {
18+
const env = getClickHouseTestEnvironment();
19+
const clickHouseSettings: ClickHouseSettings = {};
20+
if (env === TestEnv.LocalCluster || env === TestEnv.Cloud) {
21+
clickHouseSettings.insert_quorum = 2;
22+
}
23+
if (env === TestEnv.Cloud) {
1224
console.log('Using ClickHouse Cloud client');
1325
return createClient({
1426
host: getFromEnv('CLICKHOUSE_CLOUD_HOST'),
1527
username: getFromEnv('CLICKHOUSE_CLOUD_USERNAME'),
1628
password: getFromEnv('CLICKHOUSE_CLOUD_PASSWORD'),
17-
clickhouse_settings: {
18-
insert_quorum: 2,
19-
},
29+
...clickHouseSettings,
2030
...config,
2131
});
2232
} else {
23-
return createClient(config);
33+
return createClient({
34+
...clickHouseSettings,
35+
...config,
36+
});
2437
}
2538
}
2639

@@ -37,12 +50,10 @@ export async function createRandomDatabase(
3750

3851
export async function createTable(
3952
client: ClickHouseClient,
40-
definition: (engine: string) => string,
41-
engine = 'MergeTree()'
53+
definition: (environment: TestEnv) => string
4254
) {
43-
const ddl = isClickHouseCloudEnabled()
44-
? definition('')
45-
: definition(`ENGINE ${engine}`);
55+
const env = getClickHouseTestEnvironment();
56+
const ddl = definition(env);
4657
await client.command({
4758
query: ddl,
4859
});
@@ -57,6 +68,19 @@ function getFromEnv(key: string): string {
5768
return value;
5869
}
5970

60-
function isClickHouseCloudEnabled() {
61-
return process.env['CLICKHOUSE_CLOUD_ENABLED'] === 'true';
71+
export function getClickHouseTestEnvironment(): TestEnv {
72+
let env;
73+
switch (process.env['CLICKHOUSE_TEST_ENVIRONMENT']) {
74+
case 'CLOUD':
75+
env = TestEnv.Cloud;
76+
break;
77+
case 'LOCAL_CLUSTER':
78+
env = TestEnv.LocalCluster;
79+
break;
80+
default:
81+
env = TestEnv.LocalSingleNode;
82+
break;
83+
}
84+
console.log(`Using ${env} test environment`);
85+
return env;
6286
}

docker-compose.cluster.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ services:
1414
- '9000:9000'
1515
- '9181:9181'
1616
volumes:
17-
- './.docker/clickhouse/cluster/config1.xml:/etc/clickhouse-server/config.xml'
18-
- './.docker/clickhouse/cluster/macros1.xml:/etc/clickhouse-server/config.d/macros.xml'
17+
- './.docker/clickhouse/cluster/server1_config.xml:/etc/clickhouse-server/config.xml'
18+
- './.docker/clickhouse/cluster/server1_macros.xml:/etc/clickhouse-server/config.d/macros.xml'
1919
- './.docker/clickhouse/cluster/users.xml:/etc/clickhouse-server/users.xml'
2020

2121
clickhouse2:
@@ -31,8 +31,8 @@ services:
3131
- '9001:9000'
3232
- '9182:9181'
3333
volumes:
34-
- './.docker/clickhouse/cluster/config2.xml:/etc/clickhouse-server/config.xml'
35-
- './.docker/clickhouse/cluster/macros2.xml:/etc/clickhouse-server/config.d/macros.xml'
34+
- './.docker/clickhouse/cluster/server2_config.xml:/etc/clickhouse-server/config.xml'
35+
- './.docker/clickhouse/cluster/server2_macros.xml:/etc/clickhouse-server/config.d/macros.xml'
3636
- './.docker/clickhouse/cluster/users.xml:/etc/clickhouse-server/users.xml'
3737

3838
# Using Nginx as a cluster entrypoint and a round-robin load balancer for HTTP requests

src/client.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface SelectParams extends BaseParams {
4444

4545
export interface CommandParams extends BaseParams {
4646
query: string;
47+
format?: DataFormat;
4748
}
4849

4950
export interface InsertParams extends BaseParams {
@@ -140,13 +141,16 @@ export class ClickHouseClient {
140141
return new Rows(stream, format);
141142
}
142143

143-
async command(params: CommandParams): Promise<void> {
144-
const query = params.query.trim();
144+
async command(params: CommandParams): Promise<Rows> {
145+
const format = params.format ?? 'JSON';
146+
const query = formatCommandQuery(params.query, format);
145147

146-
await this.connection.command({
148+
const stream = await this.connection.command({
147149
query,
148150
...this.getBaseParams(params),
149151
});
152+
153+
return new Rows(stream, format);
150154
}
151155

152156
async insert(params: InsertParams): Promise<void> {
@@ -184,6 +188,12 @@ function formatSelectQuery(query: string, format: DataFormat): string {
184188
return query + ' \nFORMAT ' + format;
185189
}
186190

191+
// it is a duplicate of `formatSelectQuery`, but it might differ in the future
192+
function formatCommandQuery(query: string, format: DataFormat): string {
193+
query = query.trim();
194+
return query + ' \nFORMAT ' + format;
195+
}
196+
187197
function validateInsertValues(
188198
values: ReadonlyArray<any> | Stream.Readable
189199
): void {

src/connection/adapter/base_http_adapter.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import type {
1515
import { toSearchParams } from './http_search_params';
1616
import { transformUrl } from './transform_url';
1717
import { getAsText, isStream } from '../../utils';
18-
import { Rows } from '../../result';
1918

2019
export interface RequestParams {
2120
method: 'GET' | 'POST';
@@ -253,23 +252,18 @@ export abstract class BaseHttpAdapter implements Connection {
253252
});
254253
}
255254

256-
async command(params: BaseParams): Promise<void> {
255+
async command(params: BaseParams): Promise<Stream.Readable> {
257256
const searchParams = toSearchParams(
258257
params.clickhouse_settings,
259258
params.query_params
260259
);
261260

262-
const stream = await this.request({
261+
return await this.request({
263262
method: 'POST',
264263
url: transformUrl({ url: this.config.host, pathname: '/', searchParams }),
265264
body: params.query,
266265
abort_signal: params.abort_signal,
267266
});
268-
269-
const rows = new Rows(stream, 'TabSeparated');
270-
const text = await rows.text();
271-
console.log(`Command returned:\n${text}`);
272-
// return await getAsText(result);
273267
}
274268

275269
async insert(params: InsertParams): Promise<void> {

src/connection/connection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface Connection {
3434
ping(): Promise<boolean>;
3535
close(): Promise<void>;
3636
select(params: BaseParams): Promise<Stream.Readable>;
37-
command(params: BaseParams): Promise<void>;
37+
command(params: BaseParams): Promise<Stream.Readable>;
3838
insert(params: InsertParams): Promise<void>;
3939
}
4040

0 commit comments

Comments
 (0)