diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c831d63..80dc467d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.2.10 (Common, Node.js, Web) + +### New features + +- If `InsertParams.values` is an empty array, no request is sent to the server and `ClickHouseClient.insert` short-circuits itself. In this scenario, the newly added `InsertResult.executed` flag will be `false`, and `InsertResult.query_id` will be an empty string. + +### Bug fixes + +- Client no longer produces `Code: 354. inflate failed: buffer error` exception if request compression is enabled and `InsertParams.values` is an empty array (see above). + ## 0.2.9 (Common, Node.js, Web) ### New features diff --git a/packages/client-common/__tests__/integration/insert.test.ts b/packages/client-common/__tests__/integration/insert.test.ts index f795e303..e1a6e50e 100644 --- a/packages/client-common/__tests__/integration/insert.test.ts +++ b/packages/client-common/__tests__/integration/insert.test.ts @@ -17,7 +17,7 @@ describe('insert', () => { }) it('inserts values using JSON format', async () => { - const { query_id } = await client.insert({ + const result = await client.insert({ table: tableName, values: { meta: [ @@ -39,12 +39,13 @@ describe('insert', () => { format: 'JSON', }) await assertJsonValues(client, tableName) - expect(validateUUID(query_id)).toBeTruthy() + expect(validateUUID(result.query_id)).toBeTruthy() + expect(result.executed).toBeTruthy() }) it('should use provide query_id', async () => { const query_id = guid() - const { query_id: q_id } = await client.insert({ + const result = await client.insert({ table: tableName, query_id, values: { @@ -67,7 +68,8 @@ describe('insert', () => { format: 'JSON', }) await assertJsonValues(client, tableName) - expect(query_id).toEqual(q_id) + expect(result.query_id).toEqual(query_id) + expect(result.executed).toBeTruthy() }) it('inserts values using JSONObjectEachRow format', async () => { diff --git a/packages/client-common/src/client.ts b/packages/client-common/src/client.ts index ecfa3f90..a489322c 100644 --- a/packages/client-common/src/client.ts +++ b/packages/client-common/src/client.ts @@ -4,7 +4,6 @@ import type { Connection, ConnectionParams, ConnExecResult, - ConnInsertResult, Logger, WithClickHouseSummary, } from '@clickhouse/client-common' @@ -128,7 +127,21 @@ export interface ExecParams extends BaseQueryParams { export type CommandParams = ExecParams export type CommandResult = { query_id: string } & WithClickHouseSummary -export type InsertResult = ConnInsertResult +export type InsertResult = { + /** + * Indicates whether the INSERT statement was executed on the server. + * Will be `false` if there was no data to insert. + * For example: if {@link InsertParams.values} was an empty array, + * the client does not any requests to the server, and {@link executed} is false. + */ + executed: boolean + /** + * Empty string if {@link executed} is false. + * Otherwise, either {@link InsertParams.query_id} if it was set, or the id that was generated by the client. + */ + query_id: string +} & WithClickHouseSummary + export type ExecResult = ConnExecResult export type PingResult = ConnPingResult @@ -249,15 +262,20 @@ export class ClickHouseClient { * consider using {@link ClickHouseClient.command}, passing the entire raw query there (including FORMAT clause). */ async insert(params: InsertParams): Promise { + if (Array.isArray(params.values) && params.values.length === 0) { + return { executed: false, query_id: '' } + } + const format = params.format || 'JSONCompactEachRow' this.valuesEncoder.validateInsertValues(params.values, format) const query = getInsertQuery(params, format) - return await this.connection.insert({ + const result = await this.connection.insert({ query, values: this.valuesEncoder.encodeValues(params.values, format), ...this.getQueryParams(params), }) + return { ...result, executed: true } } /** diff --git a/packages/client-common/src/version.ts b/packages/client-common/src/version.ts index e8618bef..f9b280fa 100644 --- a/packages/client-common/src/version.ts +++ b/packages/client-common/src/version.ts @@ -1 +1 @@ -export default '0.2.9' +export default '0.2.10' diff --git a/packages/client-node/__tests__/integration/node_insert.test.ts b/packages/client-node/__tests__/integration/node_insert.test.ts index e2d6e0ea..3f72e077 100644 --- a/packages/client-node/__tests__/integration/node_insert.test.ts +++ b/packages/client-node/__tests__/integration/node_insert.test.ts @@ -7,29 +7,57 @@ describe('[Node.js] insert', () => { let client: ClickHouseClient let tableName: string - beforeEach(async () => { - client = await createTestClient() - tableName = `insert_test_${guid()}` - await createSimpleTable(client, tableName) - }) afterEach(async () => { await client.close() }) - it('should provide error details about a dataset with an invalid type', async () => { - await expectAsync( - client.insert({ + + describe('without request compression', () => { + beforeEach(async () => { + client = createTestClient() + tableName = `node_insert_test_${guid()}` + await createSimpleTable(client, tableName) + }) + + it('should provide error details about a dataset with an invalid type', async () => { + await expectAsync( + client.insert({ + table: tableName, + values: Stream.Readable.from(['42,foobar,"[1,2]"'], { + objectMode: false, + }), + format: 'TabSeparated', + }) + ).toBeRejectedWith( + jasmine.objectContaining({ + message: jasmine.stringContaining('Cannot parse input'), + code: '27', + type: 'CANNOT_PARSE_INPUT_ASSERTION_FAILED', + }) + ) + }) + }) + + describe('with request compression', () => { + beforeEach(async () => { + client = createTestClient({ + compression: { + request: true, + }, + }) + tableName = `node_insert_test_${guid()}` + await createSimpleTable(client, tableName) + }) + + it('should not fail if the values array is empty', async () => { + const result = await client.insert({ table: tableName, - values: Stream.Readable.from(['42,foobar,"[1,2]"'], { - objectMode: false, - }), - format: 'TabSeparated', + values: [], + format: 'JSONEachRow', }) - ).toBeRejectedWith( - jasmine.objectContaining({ - message: jasmine.stringContaining('Cannot parse input'), - code: '27', - type: 'CANNOT_PARSE_INPUT_ASSERTION_FAILED', + expect(result).toEqual({ + executed: false, + query_id: '', }) - ) + }) }) }) diff --git a/packages/client-node/src/version.ts b/packages/client-node/src/version.ts index e8618bef..f9b280fa 100644 --- a/packages/client-node/src/version.ts +++ b/packages/client-node/src/version.ts @@ -1 +1 @@ -export default '0.2.9' +export default '0.2.10' diff --git a/packages/client-web/src/version.ts b/packages/client-web/src/version.ts index e8618bef..f9b280fa 100644 --- a/packages/client-web/src/version.ts +++ b/packages/client-web/src/version.ts @@ -1 +1 @@ -export default '0.2.9' +export default '0.2.10'