Description
extractInsertQueryComponents (batch.go:25, regex normalizeInsertQueryMatch at batch.go:9) captures the insert target as ([^(]+) — everything between INSERT INTO and the first (. Two valid ClickHouse INSERT forms are therefore misparsed:
- The optional
TABLE keyword — INSERT INTO TABLE tbl (col1, col2) is valid SQL. The parser returns tableName = "TABLE tbl".
- A table-function target —
INSERT INTO FUNCTION null('id UInt32') / INSERT INTO TABLE FUNCTION ... are valid. The parser returns tableName = "FUNCTION null" / "TABLE FUNCTION null" (the argument list is dropped, since it lands in the optional column-list group).
The returned tableName is discarded on the native protocol (conn_batch.go:23 uses _), so native PrepareBatch still works — the server resolves the target and returns the block header. On the HTTP protocol it is interpolated straight into a DESCRIBE statement at conn_http_batch.go:17:
describeTableQuery := fmt.Sprintf("DESCRIBE TABLE %s", tableName)
which yields DESCRIBE TABLE TABLE tbl (a syntax error) and DESCRIBE TABLE FUNCTION null (target loses its arguments), so PrepareBatch fails for statements the server accepts. The only workaround is to pre-supply columns through the context (opt.columnNamesAndTypes).
Note that the other halves of the upstream report do not apply here: multi-line INSERTs parse correctly (Go's \s and negated character classes cross newlines), and double-quoted column names are already normalized by strings.Trim(..., "\"") at batch.go:45.
ClickHouse server version
Code analysis + unit test only; not verified against a running server (no ClickHouse instance was reachable in the investigation environment). The DESCRIBE TABLE TABLE tbl / DESCRIBE TABLE FUNCTION null failure on the HTTP path is inferred from the fmt.Sprintf above, not observed end to end. Upstream reports these INSERT forms accepted by ClickHouse 26.5.1.882.
Reproduction
batch_parse_repro_test.go (package clickhouse):
package clickhouse
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestInsertTargetParsing(t *testing.T) {
cases := []struct {
query string
expectedTableName string
expectedColumns []string
}{
{
query: "INSERT INTO TABLE table_name (col1, col2) VALUES (1, 2)",
expectedTableName: "table_name",
expectedColumns: []string{"col1", "col2"},
},
{
query: "INSERT INTO TABLE table_name VALUES (1, 2)",
expectedTableName: "table_name",
expectedColumns: []string{},
},
{
query: "INSERT INTO FUNCTION null('id UInt32') VALUES (1)",
expectedTableName: "null('id UInt32')",
expectedColumns: []string{},
},
}
for _, tc := range cases {
t.Run(tc.query, func(t *testing.T) {
_, tableName, columns, err := extractNormalizedInsertQueryAndColumns(tc.query)
assert.NoError(t, err)
fmt.Printf("query=%q -> tableName=%q columns=%v (DESCRIBE would be: %q)\n",
tc.query, tableName, columns, fmt.Sprintf("DESCRIBE TABLE %s", tableName))
assert.Equal(t, tc.expectedTableName, tableName)
assert.Equal(t, tc.expectedColumns, columns)
})
}
}
go test -run TestInsertTargetParsing -v . — all three subtests fail:
query="INSERT INTO TABLE table_name (col1, col2) VALUES (1, 2)" -> tableName="TABLE table_name" columns=[col1 col2] (DESCRIBE would be: "DESCRIBE TABLE TABLE table_name")
expected: "table_name"
actual : "TABLE table_name"
query="INSERT INTO TABLE table_name VALUES (1, 2)" -> tableName="TABLE table_name" columns=[] (DESCRIBE would be: "DESCRIBE TABLE TABLE table_name")
expected: "table_name"
actual : "TABLE table_name"
query="INSERT INTO FUNCTION null('id UInt32') VALUES (1)" -> tableName="FUNCTION null" columns=[] (DESCRIBE would be: "DESCRIBE TABLE FUNCTION null")
expected: "null('id UInt32')"
actual : "FUNCTION null"
Expected: the target name resolves to table_name in the first two cases (with the column list intact), and the table-function expression is either carried through whole (DESCRIBE TABLE null('id UInt32') is valid for table functions) or rejected with an actionable error rather than a mangled DESCRIBE.
Suggested fix
In normalizeInsertQueryMatch (batch.go:9), consume the optional TABLE keyword after INSERT INTO instead of letting it fall into the table-name group, and stop the table-name group at whitespace/( rather than at ( alone so a stray keyword cannot be absorbed. Table-function targets ([TABLE] FUNCTION <fn>(...)) need to be recognized as such: either preserve the full expression for the HTTP DESCRIBE (conn_http_batch.go:17), or return an explicit error explaining that HTTP batches into table functions require columnNamesAndTypes in the context.
Link
Relayed from ClickHouse/clickhouse-connect#932
Description
extractInsertQueryComponents(batch.go:25, regexnormalizeInsertQueryMatchatbatch.go:9) captures the insert target as([^(]+)— everything betweenINSERT INTOand the first(. Two valid ClickHouse INSERT forms are therefore misparsed:TABLEkeyword —INSERT INTO TABLE tbl (col1, col2)is valid SQL. The parser returnstableName = "TABLE tbl".INSERT INTO FUNCTION null('id UInt32')/INSERT INTO TABLE FUNCTION ...are valid. The parser returnstableName = "FUNCTION null"/"TABLE FUNCTION null"(the argument list is dropped, since it lands in the optional column-list group).The returned
tableNameis discarded on the native protocol (conn_batch.go:23uses_), so nativePrepareBatchstill works — the server resolves the target and returns the block header. On the HTTP protocol it is interpolated straight into aDESCRIBEstatement atconn_http_batch.go:17:which yields
DESCRIBE TABLE TABLE tbl(a syntax error) andDESCRIBE TABLE FUNCTION null(target loses its arguments), soPrepareBatchfails for statements the server accepts. The only workaround is to pre-supply columns through the context (opt.columnNamesAndTypes).Note that the other halves of the upstream report do not apply here: multi-line INSERTs parse correctly (Go's
\sand negated character classes cross newlines), and double-quoted column names are already normalized bystrings.Trim(..., "\"")atbatch.go:45.ClickHouse server version
Code analysis + unit test only; not verified against a running server (no ClickHouse instance was reachable in the investigation environment). The
DESCRIBE TABLE TABLE tbl/DESCRIBE TABLE FUNCTION nullfailure on the HTTP path is inferred from thefmt.Sprintfabove, not observed end to end. Upstream reports these INSERT forms accepted by ClickHouse 26.5.1.882.Reproduction
batch_parse_repro_test.go(packageclickhouse):go test -run TestInsertTargetParsing -v .— all three subtests fail:Expected: the target name resolves to
table_namein the first two cases (with the column list intact), and the table-function expression is either carried through whole (DESCRIBE TABLE null('id UInt32')is valid for table functions) or rejected with an actionable error rather than a mangledDESCRIBE.Suggested fix
In
normalizeInsertQueryMatch(batch.go:9), consume the optionalTABLEkeyword afterINSERT INTOinstead of letting it fall into the table-name group, and stop the table-name group at whitespace/(rather than at(alone so a stray keyword cannot be absorbed. Table-function targets ([TABLE] FUNCTION <fn>(...)) need to be recognized as such: either preserve the full expression for the HTTPDESCRIBE(conn_http_batch.go:17), or return an explicit error explaining that HTTP batches into table functions requirecolumnNamesAndTypesin the context.Link
Relayed from ClickHouse/clickhouse-connect#932