From d55bb2029878d1bdeddab9ce3025fefbd05860d5 Mon Sep 17 00:00:00 2001 From: basili4-1982 <1531789+basili4-1982@users.noreply.github.com> Date: Sat, 22 Aug 2026 02:24:58 +0300 Subject: [PATCH] feat(parser): ClickHouse array/tuple element access via dot (d.1) Allow a numeric component after a period in ClickHouse qualified identifiers (alias.1, tuple.2) for array/tuple element access. Restricted to the ClickHouse dialect so Postgres qualified columns stay unchanged. Regression: TestClickHouseArrayElementAccess. --- pkg/sql/parser/clickhouse_array_dot_test.go | 41 +++++++++++++++++++++ pkg/sql/parser/expressions_literal.go | 3 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 pkg/sql/parser/clickhouse_array_dot_test.go diff --git a/pkg/sql/parser/clickhouse_array_dot_test.go b/pkg/sql/parser/clickhouse_array_dot_test.go new file mode 100644 index 00000000..430f09e6 --- /dev/null +++ b/pkg/sql/parser/clickhouse_array_dot_test.go @@ -0,0 +1,41 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser_test + +import ( + "testing" + + "github.com/ajitpratap0/GoSQLX/pkg/gosqlx" + "github.com/ajitpratap0/GoSQLX/pkg/sql/keywords" +) + +// Regression: ClickHouse array/tuple element access via dot-notation (d.1, t.2) +// must parse. Previously a numeric component after '.' raised E2004. +func TestClickHouseArrayElementAccess(t *testing.T) { + tests := []string{ + `SELECT d.1 FROM t ARRAY JOIN arr AS d`, + `SELECT a.x FROM t ARRAY JOIN a`, + `SELECT arr[1] FROM t`, + } + for _, sql := range tests { + tree, err := gosqlx.ParseWithDialect(sql, keywords.DialectClickHouse) + if err != nil { + t.Fatalf("parse %q failed: %v", sql, err) + } + if got := tree.SQL(); got != sql { + t.Errorf("SQL() = %q, want %q", got, sql) + } + } +} diff --git a/pkg/sql/parser/expressions_literal.go b/pkg/sql/parser/expressions_literal.go index 04516108..938f6005 100644 --- a/pkg/sql/parser/expressions_literal.go +++ b/pkg/sql/parser/expressions_literal.go @@ -194,10 +194,11 @@ func (p *Parser) parsePrimaryExpression() (ast.Expression, error) { Pos: identPos, } p.advance() - } else if p.isIdentifier() || p.isNonReservedKeyword() { + } else if p.isIdentifier() || p.isNonReservedKeyword() || (p.dialect == string(keywords.DialectClickHouse) && p.isNumericLiteral()) { // Handle table.column (qualified identifier). // isNonReservedKeyword covers reserved words valid as column // names after a dot, e.g. table.KEY, schema.INDEX, alias.VIEW. + // ClickHouse also allows numeric element access: d.1, tuple.2. ident = &ast.Identifier{ Table: ident.Name, Name: p.currentToken.Token.Value,