Files
sqlparser/parser/named_tuple_test.go
zeekay 4d341a6a1f read back the named tuple ClickHouse prints
A tuple column is declared either way — Tuple(String, Array(String)) or
Tuple(label String, path Array(String)) — and SHOW CREATE TABLE returns whichever
the column carries. Only the unnamed form parsed, so a schema the server printed
itself could not be read back: the parse stopped at the first element's TYPE with
"expected the last token kind is: )" under `label String`.

The named form is a column list, which parseNestedType already reads for Nested,
so this routes to it on one token of lookahead rather than teaching a second
grammar: inside the parens a NAME is followed by its type (an identifier), while
an unnamed element is followed by a comma, the closing paren, or its own
parameters.

Measured on the estate: event.log and event.span each carry a named tuple, and
every logs query against them answered 500 "failed to get logs keys" because the
metadata step reads the table back through this parser.
2026-08-13 19:02:18 -07:00

35 lines
1.4 KiB
Go

package parser
import "testing"
// TestNamedTupleColumn reads back a column type ClickHouse itself printed.
//
// SHOW CREATE TABLE returns a tuple in whichever form the column was declared, and
// the named form is a column list, not a list of types. Reading only the unnamed
// form stopped the parse at the first element's TYPE — "expected the last token
// kind is: )" pointing at `label String` — which made an entire real table
// unreadable to anything that parses its schema back.
func TestNamedTupleColumn(t *testing.T) {
const ddl = "CREATE TABLE event.log (`el` Tuple(label String, role LowCardinality(String), path Array(String))) ENGINE = MergeTree ORDER BY tuple()"
stmts, err := NewParser(ddl).ParseStmts()
if err != nil {
t.Fatalf("named tuple did not parse: %v", err)
}
ct, ok := stmts[0].(*CreateTable)
if !ok {
t.Fatalf("statement is %T, want *CreateTable", stmts[0])
}
if n := len(ct.TableSchema.Columns); n != 1 {
t.Fatalf("columns = %d, want 1", n)
}
}
// TestUnnamedTupleColumnStillParses is the other half: the lookahead must not
// send a bare list of types down the column-list grammar.
func TestUnnamedTupleColumnStillParses(t *testing.T) {
const ddl = "CREATE TABLE t (`x` Tuple(String, Array(String), LowCardinality(String))) ENGINE = MergeTree ORDER BY tuple()"
if _, err := NewParser(ddl).ParseStmts(); err != nil {
t.Fatalf("unnamed tuple regressed: %v", err)
}
}