Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 1.12.2

## Bug fixes

- Fixed boolean value formatting in query parameters. Boolean values within `Array`, `Tuple`, and `Map` types are now correctly formatted as `TRUE`/`FALSE` instead of `1`/`0` to ensure proper type compatibility with ClickHouse.

# 1.12.1

## Improvements
Expand Down Expand Up @@ -198,7 +204,7 @@ A minor release to allow further investigation regarding uncaught error issues w

## New features

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

## Bug fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatQueryParams } from '@clickhouse/client-common'
import { formatQueryParams, TupleParam } from '@clickhouse/client-common'

describe('formatQueryParams', () => {
it('formats null', () => {
Expand Down Expand Up @@ -158,4 +158,76 @@ describe('formatQueryParams', () => {
}),
).toBe("{'name':'custom','id':42,'params':{'refs':[44]}}")
})

it('formats booleans in arrays as TRUE/FALSE', () => {
expect(formatQueryParams({ value: [true, false] })).toBe('[TRUE,FALSE]')
expect(formatQueryParams({ value: [true] })).toBe('[TRUE]')
expect(formatQueryParams({ value: [false] })).toBe('[FALSE]')
})

it('formats booleans in nested arrays as TRUE/FALSE', () => {
expect(
formatQueryParams({
value: [
[true, false],
[false, true],
],
}),
).toBe('[[TRUE,FALSE],[FALSE,TRUE]]')
expect(
formatQueryParams({
value: [[[true]], [[false]]],
}),
).toBe('[[[TRUE]],[[FALSE]]]')
})

it('formats booleans in arrays with mixed types', () => {
expect(formatQueryParams({ value: [1, true, 'test', false, null] })).toBe(
"[1,TRUE,'test',FALSE,NULL]",
)
})

it('formats booleans in tuples as TRUE/FALSE', () => {
expect(
formatQueryParams({
value: new TupleParam([true, false]),
}),
).toBe('(TRUE,FALSE)')
expect(
formatQueryParams({
value: new TupleParam([1, true, 'test', false]),
}),
).toBe("(1,TRUE,'test',FALSE)")
})

it('formats booleans in nested tuples as TRUE/FALSE', () => {
expect(
formatQueryParams({
value: new TupleParam([new TupleParam([true, false]), true]),
}),
).toBe('((TRUE,FALSE),TRUE)')
})

it('formats booleans in objects (Maps) as TRUE/FALSE', () => {
expect(
formatQueryParams({
value: {
isActive: true,
isDeleted: false,
},
}),
).toBe("{'isActive':TRUE,'isDeleted':FALSE}")
})

it('formats booleans in nested structures', () => {
expect(
formatQueryParams({
value: {
name: 'test',
flags: [true, false],
tuple: new TupleParam([false, true]),
},
}),
).toBe("{'name':'test','flags':[TRUE,FALSE],'tuple':(FALSE,TRUE)}")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ export function formatQueryParams({
wrapStringInQuotes,
printNullAsKeyword,
}: FormatQueryParamsOptions): string {
return formatQueryParamsInternal({
value,
wrapStringInQuotes,
printNullAsKeyword,
isInArrayOrTuple: false,
})
}

function formatQueryParamsInternal({
value,
wrapStringInQuotes,
printNullAsKeyword,
isInArrayOrTuple,
}: FormatQueryParamsOptions & { isInArrayOrTuple: boolean }): string {
if (value === null || value === undefined) {
if (printNullAsKeyword) return 'NULL'
return '\\N'
Expand All @@ -16,7 +30,12 @@ export function formatQueryParams({
if (value === Number.NEGATIVE_INFINITY) return '-inf'

if (typeof value === 'number') return String(value)
if (typeof value === 'boolean') return value ? '1' : '0'
if (typeof value === 'boolean') {
if (isInArrayOrTuple) {
return value ? 'TRUE' : 'FALSE'
}
return value ? '1' : '0'
}
if (typeof value === 'string') {
let result = ''
for (let i = 0; i < value.length; i++) {
Expand Down Expand Up @@ -46,10 +65,11 @@ export function formatQueryParams({
if (Array.isArray(value)) {
return `[${value
.map((v) =>
formatQueryParams({
formatQueryParamsInternal({
value: v,
wrapStringInQuotes: true,
printNullAsKeyword: true,
isInArrayOrTuple: true,
}),
)
.join(',')}]`
Expand All @@ -70,10 +90,11 @@ export function formatQueryParams({
if (value instanceof TupleParam) {
return `(${value.values
.map((v) =>
formatQueryParams({
formatQueryParamsInternal({
value: v,
wrapStringInQuotes: true,
printNullAsKeyword: true,
isInArrayOrTuple: true,
}),
)
.join(',')})`
Expand All @@ -98,14 +119,16 @@ function formatObjectLikeParam(
const formatted: string[] = []
for (const [key, val] of entries) {
formatted.push(
`${formatQueryParams({
`${formatQueryParamsInternal({
value: key,
wrapStringInQuotes: true,
printNullAsKeyword: true,
})}:${formatQueryParams({
isInArrayOrTuple: true,
})}:${formatQueryParamsInternal({
value: val,
wrapStringInQuotes: true,
printNullAsKeyword: true,
isInArrayOrTuple: true,
})}`,
)
}
Expand Down
Loading