Skip to content

Commit

Permalink
Expose full response headers object (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
slvrtrn authored Jul 8, 2024
1 parent 2c40ffa commit 30ce583
Show file tree
Hide file tree
Showing 22 changed files with 178 additions and 40 deletions.
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
# 1.3.0 (Common, Node.js, Web)

## New features

- It is now possible to get the entire response headers object from the `query`/`insert`/`command`/`exec` methods. With `query`, you can access the `ResultSet.response_headers` property; other methods (`insert`/`command`/`exec`) return it as parts of their response objects as well.
For example:

```ts
const rs = await client.query({
query: 'SELECT * FROM system.numbers LIMIT 1',
format: 'JSONEachRow',
})
console.log(rs.response_headers['content-type'])
```

This will print: `application/x-ndjson; charset=UTF-8`. It can be used in a similar way with the other methods.

## Improvements

- Re-exported several constants from the `@clickhouse/client-common` package for convenience:

- `SupportedJSONFormats`
- `SupportedRawFormats`
- `StreamableFormats`
- `StreamableJSONFormats`
- `SingleDocumentJSONFormats`
- `RecordsJSONFormats`

# 1.2.0 (Node.js)

## New features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
validateUUID,
} from '../utils'

describe('exec', () => {
describe('exec and command', () => {
let client: ClickHouseClient
beforeEach(() => {
client = createTestClient()
Expand Down Expand Up @@ -72,6 +72,29 @@ describe('exec', () => {
)
})

it('should get the response headers with command', async () => {
// does not actually return anything, but still sends us the headers
const result = await client.command({
query: 'SELECT 42 FORMAT TSV',
})

expect(
result.response_headers['Content-Type'] ??
result.response_headers['content-type'],
).toEqual('text/tab-separated-values; charset=UTF-8')
})

it('should get the response headers with exec', async () => {
const result = await client.exec({
query: 'SELECT 42 FORMAT CSV',
})

expect(
result.response_headers['Content-Type'] ??
result.response_headers['content-type'],
).toEqual('text/csv; charset=UTF-8; header=absent')
})

it('can specify a parameterized query', async () => {
const result = await client.query({
query: `SELECT * from system.tables where name = 'numbers'`,
Expand Down
9 changes: 8 additions & 1 deletion packages/client-common/__tests__/integration/insert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('insert', () => {
await client.close()
})

it('inserts values using JSON format', async () => {
it('inserts values using JSON format and get the response headers', async () => {
const result = await client.insert({
table: tableName,
values: {
Expand All @@ -41,6 +41,13 @@ describe('insert', () => {
await assertJsonValues(client, tableName)
expect(validateUUID(result.query_id)).toBeTruthy()
expect(result.executed).toBeTruthy()

// Surprisingly, SMT Cloud instances have a different Content-Type here.
// Expected 'text/tab-separated-values; charset=UTF-8' to equal 'text/plain; charset=UTF-8'
expect(
result.response_headers['Content-Type'] ??
result.response_headers['content-type'],
).toEqual(jasmine.stringMatching(/text\/.+?; charset=UTF-8/))
})

it('should use provide query_id', async () => {
Expand Down
12 changes: 12 additions & 0 deletions packages/client-common/__tests__/integration/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ describe('select', () => {
expect(results.sort((a, b) => a - b)).toEqual([1, 3, 6, 10, 15])
})

it('should get the response headers', async () => {
const rs = await client.query({
query: 'SELECT * FROM system.numbers LIMIT 1',
format: 'JSONEachRow',
})

expect(
rs.response_headers['Content-Type'] ??
rs.response_headers['content-type'],
).toEqual('application/x-ndjson; charset=UTF-8')
})

describe('trailing semi', () => {
it('should allow queries with trailing semicolon', async () => {
const numbers = await client.query({
Expand Down
6 changes: 6 additions & 0 deletions packages/client-common/src/clickhouse_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export interface ClickHouseSummary {
elapsed_ns: string
}

export type ResponseHeaders = Record<string, string | string[] | undefined>

export interface WithClickHouseSummary {
summary?: ClickHouseSummary
}

export interface WithResponseHeaders {
response_headers: ResponseHeaders
}
41 changes: 25 additions & 16 deletions packages/client-common/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
LogWriter,
MakeResultSet,
WithClickHouseSummary,
WithResponseHeaders,
} from '@clickhouse/client-common'
import { type DataFormat, DefaultLogger } from '@clickhouse/client-common'
import type { InputJSON, InputJSONObjectEachRow } from './clickhouse_types'
Expand Down Expand Up @@ -66,7 +67,8 @@ export interface ExecParams extends BaseQueryParams {
}

export type CommandParams = ExecParams
export type CommandResult = { query_id: string } & WithClickHouseSummary
export type CommandResult = { query_id: string } & WithClickHouseSummary &
WithResponseHeaders

export type InsertResult = {
/**
Expand All @@ -81,7 +83,8 @@ export type InsertResult = {
* Otherwise, either {@link InsertParams.query_id} if it was set, or the id that was generated by the client.
*/
query_id: string
} & WithClickHouseSummary
} & WithClickHouseSummary &
WithResponseHeaders

export type ExecResult<Stream> = ConnExecResult<Stream>
export type PingResult = ConnPingResult
Expand Down Expand Up @@ -167,22 +170,28 @@ export class ClickHouseClient<Stream = unknown> {
const format = params.format ?? 'JSON'
const query = formatQuery(params.query, format)
const queryParams = this.withClientQueryParams(params)
const { stream, query_id } = await this.connection.query({
const { stream, query_id, response_headers } = await this.connection.query({
query,
...queryParams,
})
return this.makeResultSet(stream, format, query_id, (err) => {
this.logWriter.error({
err,
module: 'Client',
message: 'Error while processing the ResultSet.',
args: {
session_id: queryParams.session_id,
query,
query_id,
},
})
})
return this.makeResultSet(
stream,
format,
query_id,
(err) => {
this.logWriter.error({
err,
module: 'Client',
message: 'Error while processing the ResultSet.',
args: {
session_id: queryParams.session_id,
query,
query_id,
},
})
},
response_headers,
)
}

/**
Expand Down Expand Up @@ -222,7 +231,7 @@ export class ClickHouseClient<Stream = unknown> {
*/
async insert<T>(params: InsertParams<Stream, T>): Promise<InsertResult> {
if (Array.isArray(params.values) && params.values.length === 0) {
return { executed: false, query_id: '' }
return { executed: false, query_id: '', response_headers: {} }
}

const format = params.format || 'JSONCompactEachRow'
Expand Down
2 changes: 2 additions & 0 deletions packages/client-common/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ResponseHeaders } from './clickhouse_types'
import type { InsertValues } from './client'
import type { Connection, ConnectionParams } from './connection'
import type { DataFormat } from './data_formatter'
Expand Down Expand Up @@ -91,6 +92,7 @@ export type MakeResultSet<Stream> = <
format: Format,
query_id: string,
log_error: (err: Error) => void,
response_headers: ResponseHeaders,
) => ResultSet

export interface ValuesEncoder<Stream> {
Expand Down
7 changes: 5 additions & 2 deletions packages/client-common/src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { WithClickHouseSummary } from './clickhouse_types'
import type {
WithClickHouseSummary,
WithResponseHeaders,
} from './clickhouse_types'
import type { LogWriter } from './logger'
import type { ClickHouseSettings } from './settings'

Expand Down Expand Up @@ -36,7 +39,7 @@ export interface ConnInsertParams<Stream> extends ConnBaseQueryParams {
values: string | Stream
}

export interface ConnBaseResult {
export interface ConnBaseResult extends WithResponseHeaders {
query_id: string
}

Expand Down
6 changes: 4 additions & 2 deletions packages/client-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ export {
} from './logger'
export type {
ClickHouseSummary,
WithClickHouseSummary,
ResponseJSON,
InputJSON,
InputJSONObjectEachRow,
ResponseJSON,
ResponseHeaders,
WithClickHouseSummary,
WithResponseHeaders,
} from './clickhouse_types'
export {
type ClickHouseSettings,
Expand Down
5 changes: 4 additions & 1 deletion packages/client-common/src/result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ResponseJSON } from './clickhouse_types'
import type { ResponseHeaders, ResponseJSON } from './clickhouse_types'
import type {
DataFormat,
RawDataFormat,
Expand Down Expand Up @@ -133,4 +133,7 @@ export interface BaseResultSet<Stream, Format extends DataFormat | unknown> {

/** ClickHouse server QueryID. */
query_id: string

/** Response headers. */
response_headers: ResponseHeaders
}
2 changes: 1 addition & 1 deletion packages/client-common/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export default '1.2.1'
export default '1.3.0'
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('[Node.js] exec result streaming', () => {
log_error: (err) => {
console.error(err)
},
response_headers: {},
})
expect(await rs.json()).toEqual([{ number: '0' }])
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('[Node.js] insert', () => {
expect(result).toEqual({
executed: false,
query_id: '',
response_headers: {},
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('[Node.js] ResultSet', () => {
log_error: (err) => {
console.error(err)
},
response_headers: {},
})
}

Expand Down
3 changes: 3 additions & 0 deletions packages/client-node/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
DataFormat,
ImplementationDetails,
ResponseHeaders,
} from '@clickhouse/client-common'
import {
type BaseClickHouseClientConfigOptions,
Expand Down Expand Up @@ -122,11 +123,13 @@ export const NodeConfigImpl: Required<
format: DataFormat,
query_id: string,
log_error: (err: Error) => void,
response_headers: ResponseHeaders,
) =>
ResultSet.instance({
stream,
format,
query_id,
log_error,
response_headers,
})) as any,
}
Loading

0 comments on commit 30ce583

Please sign in to comment.