From eb02c0a92611a00381d7286d4ffd9baa3b49fcb6 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Thu, 25 Jun 2026 00:49:32 +0200 Subject: [PATCH 1/2] fix(clickhouse): correctly transpile arrayMap and arrayFilter higher-order functions [CLAUDE] ClickHouse's higher-order array functions arrayMap(lambda, arr) and arrayFilter(lambda, arr) were being parsed as Anonymous expressions because the ClickHouse parser lacked entries for their uppercased forms (ARRAYMAP, ARRAYFILTER). As a result, transpiling to DuckDB produced invalid ARRAYMAP() and ARRAYFILTER() calls instead of LIST_TRANSFORM() and LIST_FILTER(). Additionally, the ClickHouse generator had no Transform entry, so DuckDB's LIST_TRANSFORM and Spark's TRANSFORM arrived in ClickHouse as the wrong name. Note: argument order in ClickHouse is reversed relative to the canonical exp.Transform/exp.ArrayFilter convention (lambda first, array second). --- sqlglot/generators/clickhouse.py | 1 + sqlglot/parsers/clickhouse.py | 8 ++++++++ tests/dialects/test_clickhouse.py | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/sqlglot/generators/clickhouse.py b/sqlglot/generators/clickhouse.py index d2b5381486..e95b375522 100644 --- a/sqlglot/generators/clickhouse.py +++ b/sqlglot/generators/clickhouse.py @@ -280,6 +280,7 @@ class ClickHouseGenerator(generator.Generator): exp.ArrayConcat: rename_func("arrayConcat"), exp.ArrayContains: rename_func("has"), exp.ArrayFilter: lambda self, e: self.func("arrayFilter", e.expression, e.this), + exp.Transform: lambda self, e: self.func("arrayMap", e.expression, e.this), exp.ArrayRemove: remove_from_array_using_filter, exp.ArrayReverse: rename_func("arrayReverse"), exp.ArraySlice: rename_func("arraySlice"), diff --git a/sqlglot/parsers/clickhouse.py b/sqlglot/parsers/clickhouse.py index 45bdf03af4..eb793e8e53 100644 --- a/sqlglot/parsers/clickhouse.py +++ b/sqlglot/parsers/clickhouse.py @@ -262,6 +262,14 @@ class ClickHouseParser(parser.Parser): "ARRAYMIN": exp.ArrayMin.from_arg_list, "ARRAYREVERSE": exp.ArrayReverse.from_arg_list, "ARRAYSLICE": exp.ArraySlice.from_arg_list, + # ClickHouse higher-order array functions: the lambda comes first, the array second. + # This is the opposite of exp.ArrayFilter(this=array, expression=lambda) convention. + "ARRAYFILTER": lambda args: exp.ArrayFilter( + this=seq_get(args, 1), expression=seq_get(args, 0) + ), + "ARRAYMAP": lambda args: exp.Transform( + this=seq_get(args, 1), expression=seq_get(args, 0) + ), "CURRENTDATABASE": exp.CurrentDatabase.from_arg_list, "CURRENTSCHEMAS": exp.CurrentSchemas.from_arg_list, "COUNTIF": _build_count_if, diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py index f81ff63904..d05738dac7 100644 --- a/tests/dialects/test_clickhouse.py +++ b/tests/dialects/test_clickhouse.py @@ -1768,6 +1768,28 @@ def test_functions(self): self.validate_identity( "SELECT TRANSFORM(foo, [1, 2], ['first', 'second'], 'default') FROM table" ) + self.validate_all( + "SELECT arrayMap(x -> x + 1, arr) FROM t", + read={ + "duckdb": "SELECT LIST_TRANSFORM(arr, x -> x + 1) FROM t", + "spark": "SELECT TRANSFORM(arr, x -> x + 1) FROM t", + }, + write={ + "clickhouse": "SELECT arrayMap(x -> x + 1, arr) FROM t", + "duckdb": "SELECT LIST_TRANSFORM(arr, x -> x + 1) FROM t", + "spark": "SELECT TRANSFORM(arr, x -> x + 1) FROM t", + }, + ) + self.validate_all( + "SELECT arrayFilter(x -> x > 0, arr) FROM t", + read={ + "duckdb": "SELECT LIST_FILTER(arr, x -> x > 0) FROM t", + }, + write={ + "clickhouse": "SELECT arrayFilter(x -> x > 0, arr) FROM t", + "duckdb": "SELECT LIST_FILTER(arr, x -> x > 0) FROM t", + }, + ) def test_array_offset(self): with self.assertLogs(helper_logger) as cm: From e7e93628abcd8f8fb16f5ea4b603fbfa5d29fcdf Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Thu, 25 Jun 2026 13:47:40 +0200 Subject: [PATCH 2/2] style: collapse ARRAYMAP lambda to single line [CLAUDE] ruff-format requires the multi-arg lambda to fit on one line. --- sqlglot/parsers/clickhouse.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sqlglot/parsers/clickhouse.py b/sqlglot/parsers/clickhouse.py index eb793e8e53..4e86252571 100644 --- a/sqlglot/parsers/clickhouse.py +++ b/sqlglot/parsers/clickhouse.py @@ -267,9 +267,7 @@ class ClickHouseParser(parser.Parser): "ARRAYFILTER": lambda args: exp.ArrayFilter( this=seq_get(args, 1), expression=seq_get(args, 0) ), - "ARRAYMAP": lambda args: exp.Transform( - this=seq_get(args, 1), expression=seq_get(args, 0) - ), + "ARRAYMAP": lambda args: exp.Transform(this=seq_get(args, 1), expression=seq_get(args, 0)), "CURRENTDATABASE": exp.CurrentDatabase.from_arg_list, "CURRENTSCHEMAS": exp.CurrentSchemas.from_arg_list, "COUNTIF": _build_count_if,