ClickHouse / ClickHouse/clickhouse-js

Scalar {p:Date} / {p:Date32} query params serialize JS Date as a Unix timestamp, rejected by server

Open Beginner friendly
#955 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
331
Forks
74
PR merge metrics
No merged PRs in 30d

Description

## Description

When a JavaScript `Date` object is bound to a **scalar** `Date` or `Date32` server-side query parameter (`{name:Date}` / `{name:Date32}`), the client serializes it as a bare Unix-seconds timestamp. ClickHouse's parameter parser accepts a numeric timestamp for `DateTime`/`DateTime64`, but **rejects it for** `Date`**/**`Date32`, which expect a `YYYY-MM-DD` date string. The query fails with `BAD_QUERY_PARAMETER` (code 457).

The offending path is in `packages/client-common/src/data_formatter/format_query_params.ts`. For a top-level `Date` value (`isInArrayOrTuple === false`), it returns a Unix timestamp:

```ts
if (value instanceof Date) {
if (isInArrayOrTuple) {
// container elements are correctly emitted as quoted 'YYYY-MM-DD'
return `'${value.toISOString().slice(0, 10)}'`;
}
// scalar path: Unix timestamp -- OK for DateTime/DateTime64, WRONG for Date/Date32
const unixTimestamp = Math.floor(value.getTime() / 1000).toString().padStart(10, "0");
...
}
```

The `Array(Date)` container case was already fixed in [#947]() (elements emitted as quoted `'YYYY-MM-DD'`), but the scalar `Date`/`Date32` case still emits a Unix timestamp.

This is the clickhouse-js analog of ClickHouse/clickhouse-go#1927 (case 1). Cases 1b/1c (Array(Bool), Array(Date)), 2a/2b (string escaping / TSV newline) and 3 (timezone) from that report do **not** reproduce here — I verified them against the server and they are already handled correctly (arrays use `TRUE`/`FALSE` and quoted dates; strings are escaped for the escaped-TSV format; timestamps are timezone-agnostic Unix seconds).

## ClickHouse server version

Verified against `26.6.1.1193` (local, reachable over HTTP).

## Reproduction

Client-level test (Node client, against `http://localhost:8123`):

```ts
import { createClient } from "@clickhouse/client";

const client = createClient({ url: "http://localhost:8123" });
const d = new Date(Date.UTC(2026, 4, 15)); // 2026-05-15T00:00:00Z

async function run(label: string, query: string, params: Record) {
try {
const rs = await client.query({ query, query_params: params, format: "JSONEachRow" });
console.log(`${label}\n OK -> ${JSON.stringify(await rs.json())}`);
} catch (e: any) {
console.log(`${label}\n ERROR: ${e.message?.split("\n")[0]}`);
}
}

await run("{p:Date} scalar", "SELECT {p:Date} AS v", { p: d });
await run("{p:Date32} scalar", "SELECT {p:Date32} AS v", { p: d });
await run("{p:DateTime} scalar (control)", "SELECT {p:DateTime} AS v", { p: d });
await client.close();
```

**Expected:** all three succeed (a value valid for the declared type should round-trip).

**Actual:** the two `Date`/`Date32` calls fail; only `DateTime` succeeds.

```
{p:Date} scalar
ERROR: code: 457, ... Value 1778457600 cannot be parsed as Date ... only 8 of 10 bytes was parsed: 17784576
{p:Date32} scalar
ERROR: code: 457, ... Value 1778457600 cannot be parsed as Date32 ...
{p:DateTime} scalar (control)
OK -> [{"v":"..."}]
```

### Direct verification against the server

The HTTP transport forwards `formatQueryParams({ value })` verbatim as `param_` (`packages/client-common/src/utils/url.ts`). Sending the exact value the formatter emits for the `Date` object above (`1778457600`) reproduces the failure without the client:

```
$ curl -s --get 'http://localhost:8123/?query=SELECT%20{p:Date}' --data-urlencode 'param_p=1778457600'
Code: 457. DB::Exception: Value 1778457600 cannot be parsed as Date for query parameter 'p'
because it isn't parsed completely: only 8 of 10 bytes was parsed: 17784576. (BAD_QUERY_PARAMETER)

$ curl -s --get 'http://localhost:8123/?query=SELECT%20{p:Date}' --data-urlencode 'param_p=2026-05-15'
2026-05-15 # a quoted/plain date string is what Date expects
```

## Suggested fix

In `format_query_params.ts`, the scalar `Date` branch cannot know the declared parameter type, so it must emit a representation accepted by every temporal type. A quoted `'YYYY-MM-DD HH:MM:SS[.fff]'` string (or, matching the already-fixed container path, distinguishing Date vs DateTime) is accepted by `Date`, `Date32`, `DateTime` and `DateTime64` alike, whereas a bare Unix timestamp is only accepted by the DateTime family. Aligning the scalar path with the container fix (which emits a quoted date string) would resolve `Date`/`Date32` while preserving DateTime behavior.

## Link

Relayed from ClickHouse/clickhouse-go#1927 (case 1). Related prior fix for the container case: [#947]().

Contributor guide

Open the contributing guide

Research direction

Start in packages/client-common/src/data_formatter/format_query_params.ts and trace the scalar Date branch, then reproduce the Date, Date32, and DateTime cases from the issue against the client or server. Done means scalar Date and Date32 parameters are accepted while the existing DateTime behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.