Description
client.insert() builds its statement by concatenating the raw table value:
// packages/client-common/src/client.ts:745 (getInsertQuery)
return `INSERT INTO ${params.table.trim()}${columnsPart} FORMAT ${format}`;
The JSDoc for the parameter says only "Name of a table to insert into." (packages/client-common/src/client.ts:177), which reads as a raw identifier. But because the value is spliced into SQL verbatim, any legal ClickHouse table name that requires identifier quoting (my-table, user events, a name starting with a digit, …) produces a server-side SYNTAX_ERROR even though the table exists — INSERT INTO my-table parses as my minus table.
The workaround is to pre-quote (table: '`my-table`'), which works today precisely because the value is passed through untouched. So the parameter's real contract is "SQL fragment", not "name" — it just isn't documented as one. Either contract is defensible; the ask is to settle it:
- Raw name (what the doc implies): backtick-quote/escape internally, with pass-through for already-quoted input so the existing pre-quoting workaround keeps working (JDBC
Statement.enquoteIdentifier has the same contract). Note db.table is a common value for this parameter, so naive whole-string quoting would be a breaking change — a fix would need to split on the qualifier boundary.
- SQL fragment: keep the behavior and document that the caller must quote names that need it (and that the value is interpolated into SQL, i.e. it must never come from untrusted input).
Relationship to existing work
ClickHouse server version
26.8.1.2041 (local single node, HTTP). Verified against a running server.
Reproduction
Integration test (vitest, packages/client-node/__tests__/integration/), run with npm run test:integration -- run <file>:
import type { ClickHouseClient } from "@clickhouse/client-common";
import { describe, it, beforeEach, afterEach, expect } from "vitest";
import { createTestClient } from "@test/utils/client";
import { guid } from "@test/utils/guid";
describe("[Node.js] insert into a table whose name needs quoting", () => {
let client: ClickHouseClient;
let tableName: string;
beforeEach(async () => {
client = createTestClient();
tableName = `scratch-quoted-${guid()}`; // legal name, needs backquotes
await client.command({
query: `CREATE TABLE \`${tableName}\` (id UInt32) ENGINE MergeTree ORDER BY id`,
});
});
afterEach(async () => {
await client.close();
});
it("accepts the raw table name", async () => {
await client.insert({
table: tableName,
values: [{ id: 42 }],
format: "JSONEachRow",
});
const rs = await client.query({
query: `SELECT * FROM \`${tableName}\``,
format: "JSONEachRow",
});
expect(await rs.json()).toEqual([{ id: 42 }]);
});
it("accepts a pre-quoted table name (workaround)", async () => {
await client.insert({
table: `\`${tableName}\``,
values: [{ id: 43 }],
format: "JSONEachRow",
});
const rs = await client.query({
query: `SELECT * FROM \`${tableName}\``,
format: "JSONEachRow",
});
expect(await rs.json()).toEqual([{ id: 43 }]);
});
});
Expected: both tests pass (the table exists, and the parameter is documented as a name).
Actual: the pre-quoted test passes; the raw-name test fails —
Tests 1 failed | 1 passed (2)
FAIL ... > accepts the raw table name
Error: Syntax error: failed at position 20 (-) (line 1, col 20): -quoted-26a1fd0e... FORMAT JSONEachRow
{"id":42}
. Expected one of: token, Dot, OpeningRoundBracket, FROM INFILE, SETTINGS, VALUES, FORMAT, SELECT, WITH, FROM.
code: '62', type: 'SYNTAX_ERROR'
Suggested fix
getInsertQuery in packages/client-common/src/client.ts:730-746 — apply the same identifier-quoting helper introduced by #949 to params.table, splitting on the database.table boundary and passing through segments that are already backtick- or double-quoted; alternatively, document table as a SQL fragment in the InsertParams.table JSDoc (packages/client-common/src/client.ts:176-177) and in the insert docs.
Link
Relayed from ClickHouse/clickhouse-java#3089
Description
client.insert()builds its statement by concatenating the rawtablevalue:The JSDoc for the parameter says only "Name of a table to insert into." (
packages/client-common/src/client.ts:177), which reads as a raw identifier. But because the value is spliced into SQL verbatim, any legal ClickHouse table name that requires identifier quoting (my-table,user events, a name starting with a digit, …) produces a server-sideSYNTAX_ERROReven though the table exists —INSERT INTO my-tableparses asmyminustable.The workaround is to pre-quote (
table: '`my-table`'), which works today precisely because the value is passed through untouched. So the parameter's real contract is "SQL fragment", not "name" — it just isn't documented as one. Either contract is defensible; the ask is to settle it:Statement.enquoteIdentifierhas the same contract). Notedb.tableis a common value for this parameter, so naive whole-string quoting would be a breaking change — a fix would need to split on the qualifier boundary.Relationship to existing work
columnsparameter. PR fix: back-quote insert() column identifiers with special characters #949 backtick-quotes the column identifiers but deliberately leavesparams.table.trim()untouched, so thetablehalf described here survives that fix. Filing separately rather than commenting on [backfill: ClickHouse/clickhouse-js] insert columns parameter does not backtick-quote identifiers with spaces/special chars #945, since the resolution fortableis not the same (thedb.tablequalifier case makes blind quoting unsafe).getTableSchemafrom the upstream Java report has no counterpart in this client, so only the insert path applies here.ClickHouse server version
26.8.1.2041(local single node, HTTP). Verified against a running server.Reproduction
Integration test (vitest,
packages/client-node/__tests__/integration/), run withnpm run test:integration -- run <file>:Expected: both tests pass (the table exists, and the parameter is documented as a name).
Actual: the pre-quoted test passes; the raw-name test fails —
Suggested fix
getInsertQueryinpackages/client-common/src/client.ts:730-746— apply the same identifier-quoting helper introduced by #949 toparams.table, splitting on thedatabase.tableboundary and passing through segments that are already backtick- or double-quoted; alternatively, documenttableas a SQL fragment in theInsertParams.tableJSDoc (packages/client-common/src/client.ts:176-177) and in the insert docs.Link
Relayed from ClickHouse/clickhouse-java#3089