Skip to content

Commit 6d5182c

Browse files
ajitpratap0Ajit Pratap Singh
andauthored
fix(parser): support ILIKE and PIVOT/UNPIVOT in Snowflake dialect (#484)
ILIKE was rejected with a "PostgreSQL-specific" error in every non-Postgres dialect, but Snowflake and ClickHouse natively support it. Allow it in those dialects and update the rejection message for the remaining ones. PIVOT/UNPIVOT was implemented in #477 and gated to SQL Server / Oracle only. Snowflake also supports the same syntax — extend the gate. Part of #483 (Snowflake parser gaps from QA sweep). Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local>
1 parent c50661f commit 6d5182c

File tree

3 files changed

+94
-11
lines changed

3 files changed

+94
-11
lines changed

pkg/sql/parser/expressions_operators.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,21 @@ func (p *Parser) parseComparisonExpression() (ast.Expression, error) {
9292
// Check for LIKE/ILIKE operator
9393
if p.isType(models.TokenTypeLike) || strings.EqualFold(p.currentToken.Token.Value, "ILIKE") {
9494
operator := p.currentToken.Token.Value
95-
// Reject ILIKE in non-PostgreSQL dialects - it is a PostgreSQL extension.
96-
if strings.EqualFold(operator, "ILIKE") &&
97-
p.dialect != "" &&
98-
p.dialect != string(keywords.DialectPostgreSQL) {
99-
return nil, fmt.Errorf(
100-
"ILIKE is a PostgreSQL-specific operator and is not supported in %s; "+
101-
"use LIKE or LOWER() for case-insensitive matching", p.dialect,
102-
)
95+
// ILIKE is supported by PostgreSQL, Snowflake, and ClickHouse natively.
96+
// Reject in other dialects (e.g. MySQL, SQL Server, SQLite, Oracle) where
97+
// it is not a recognized operator.
98+
if strings.EqualFold(operator, "ILIKE") && p.dialect != "" {
99+
switch p.dialect {
100+
case string(keywords.DialectPostgreSQL),
101+
string(keywords.DialectSnowflake),
102+
string(keywords.DialectClickHouse):
103+
// supported
104+
default:
105+
return nil, fmt.Errorf(
106+
"ILIKE is not supported in %s; "+
107+
"use LIKE or LOWER() for case-insensitive matching", p.dialect,
108+
)
109+
}
103110
}
104111
p.advance() // Consume LIKE/ILIKE
105112

pkg/sql/parser/pivot.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ func (p *Parser) parsePivotAlias(ref *ast.TableReference) {
6565
}
6666

6767
// pivotDialectAllowed reports whether PIVOT/UNPIVOT is a recognized clause
68-
// for the parser's current dialect. PIVOT/UNPIVOT are SQL Server / Oracle
69-
// extensions; in other dialects the words must remain valid identifiers.
68+
// for the parser's current dialect. PIVOT/UNPIVOT are SQL Server, Oracle,
69+
// and Snowflake extensions; in other dialects the words must remain valid
70+
// identifiers.
7071
func (p *Parser) pivotDialectAllowed() bool {
7172
return p.dialect == string(keywords.DialectSQLServer) ||
72-
p.dialect == string(keywords.DialectOracle)
73+
p.dialect == string(keywords.DialectOracle) ||
74+
p.dialect == string(keywords.DialectSnowflake)
7375
}
7476

7577
// isPivotKeyword returns true if the current token is the contextual PIVOT
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
5+
package parser_test
6+
7+
import (
8+
"strings"
9+
"testing"
10+
11+
"github.com/ajitpratap0/GoSQLX/pkg/gosqlx"
12+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
13+
)
14+
15+
// TestSnowflakeILIKE verifies that ILIKE is accepted in the Snowflake dialect.
16+
// Snowflake natively supports ILIKE; the parser previously rejected it with a
17+
// "PostgreSQL-specific" error. Regression for #483.
18+
func TestSnowflakeILIKE(t *testing.T) {
19+
queries := []string{
20+
`SELECT * FROM users WHERE name ILIKE 'alice%'`,
21+
`SELECT * FROM users WHERE name NOT ILIKE 'alice%'`,
22+
}
23+
for _, q := range queries {
24+
t.Run(q, func(t *testing.T) {
25+
if _, err := gosqlx.ParseWithDialect(q, keywords.DialectSnowflake); err != nil {
26+
t.Fatalf("ParseWithDialect(Snowflake) failed: %v", err)
27+
}
28+
})
29+
}
30+
}
31+
32+
// TestClickHouseILIKE verifies ILIKE is accepted in the ClickHouse dialect.
33+
func TestClickHouseILIKE(t *testing.T) {
34+
q := `SELECT * FROM events WHERE message ILIKE '%error%'`
35+
if _, err := gosqlx.ParseWithDialect(q, keywords.DialectClickHouse); err != nil {
36+
t.Fatalf("ParseWithDialect(ClickHouse) failed: %v", err)
37+
}
38+
}
39+
40+
// TestMySQLILIKERejected verifies ILIKE is still rejected in dialects that
41+
// do not natively support it.
42+
func TestMySQLILIKERejected(t *testing.T) {
43+
q := `SELECT * FROM users WHERE name ILIKE 'alice%'`
44+
_, err := gosqlx.ParseWithDialect(q, keywords.DialectMySQL)
45+
if err == nil {
46+
t.Fatal("expected MySQL ILIKE to be rejected")
47+
}
48+
if !strings.Contains(err.Error(), "ILIKE is not supported") {
49+
t.Fatalf("unexpected error message: %v", err)
50+
}
51+
}
52+
53+
// TestSnowflakePivot verifies PIVOT is parsed in the Snowflake dialect, where
54+
// it was previously gated to SQL Server / Oracle only. Regression for #483.
55+
func TestSnowflakePivot(t *testing.T) {
56+
q := `SELECT *
57+
FROM monthly_sales
58+
PIVOT (SUM(amount) FOR month IN ('JAN', 'FEB', 'MAR'))
59+
AS p`
60+
if _, err := gosqlx.ParseWithDialect(q, keywords.DialectSnowflake); err != nil {
61+
t.Fatalf("Snowflake PIVOT parse failed: %v", err)
62+
}
63+
}
64+
65+
// TestSnowflakeUnpivot verifies UNPIVOT is parsed in the Snowflake dialect.
66+
func TestSnowflakeUnpivot(t *testing.T) {
67+
q := `SELECT *
68+
FROM monthly_sales
69+
UNPIVOT (amount FOR month IN (jan, feb, mar))
70+
AS u`
71+
if _, err := gosqlx.ParseWithDialect(q, keywords.DialectSnowflake); err != nil {
72+
t.Fatalf("Snowflake UNPIVOT parse failed: %v", err)
73+
}
74+
}

0 commit comments

Comments
 (0)