diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py index cfa9a7e54e..efaf34cd9f 100644 --- a/sqlglot/dialects/clickhouse.py +++ b/sqlglot/dialects/clickhouse.py @@ -345,7 +345,7 @@ def safeconcat_sql(self, expression: exp.SafeConcat) -> str: "CONCAT", *[ exp.func("if", e.is_(exp.null()), e, exp.cast(e, "text")) - for e in expression.expressions + for e in t.cast(t.List[exp.Condition], expression.expressions) ], ) diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py index 7b09a37003..8f0552e71c 100644 --- a/sqlglot/expressions.py +++ b/sqlglot/expressions.py @@ -3221,15 +3221,15 @@ def output_name(self) -> str: return self.name -class Parameter(Expression): +class Parameter(Condition): arg_types = {"this": True, "wrapped": False} -class SessionParameter(Expression): +class SessionParameter(Condition): arg_types = {"this": True, "kind": False} -class Placeholder(Expression): +class Placeholder(Condition): arg_types = {"this": False, "kind": False} diff --git a/sqlglot/parser.py b/sqlglot/parser.py index d6888c772e..30c56cdced 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -3655,7 +3655,11 @@ def _parse_cast(self, strict: bool) -> exp.Expression: def _parse_concat(self) -> t.Optional[exp.Expression]: args = self._parse_csv(self._parse_conjunction) if self.CONCAT_NULL_OUTPUTS_STRING: - args = [exp.func("COALESCE", arg, exp.Literal.string("")) for arg in args] + args = [ + exp.func("COALESCE", exp.cast(arg, "text"), exp.Literal.string("")) + for arg in args + if arg + ] # Some dialects (e.g. Trino) don't allow a single-argument CONCAT call, so when # we find such a call we replace it with its argument. diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py index 7584c67483..b0df4df42b 100644 --- a/tests/dialects/test_clickhouse.py +++ b/tests/dialects/test_clickhouse.py @@ -53,7 +53,7 @@ def test_clickhouse(self): ) self.validate_all( - "CONCAT(CASE WHEN COALESCE(a, '') IS NULL THEN COALESCE(a, '') ELSE CAST(COALESCE(a, '') AS TEXT) END, CASE WHEN COALESCE(b, '') IS NULL THEN COALESCE(b, '') ELSE CAST(COALESCE(b, '') AS TEXT) END)", + "CONCAT(CASE WHEN COALESCE(CAST(a AS TEXT), '') IS NULL THEN COALESCE(CAST(a AS TEXT), '') ELSE CAST(COALESCE(CAST(a AS TEXT), '') AS TEXT) END, CASE WHEN COALESCE(CAST(b AS TEXT), '') IS NULL THEN COALESCE(CAST(b AS TEXT), '') ELSE CAST(COALESCE(CAST(b AS TEXT), '') AS TEXT) END)", read={"postgres": "CONCAT(a, b)"}, ) self.validate_all( diff --git a/tests/dialects/test_dialect.py b/tests/dialects/test_dialect.py index f801dc04f5..07ff4e4771 100644 --- a/tests/dialects/test_dialect.py +++ b/tests/dialects/test_dialect.py @@ -1192,7 +1192,7 @@ def test_operators(self): }, ) self.validate_all( - "COALESCE(a, '')", + "COALESCE(CAST(a AS TEXT), '')", read={ "drill": "CONCAT(a)", "duckdb": "CONCAT(a)", diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py index 4e57b363e1..c391052200 100644 --- a/tests/dialects/test_postgres.py +++ b/tests/dialects/test_postgres.py @@ -577,13 +577,19 @@ def test_bool_or(self): ) def test_string_concat(self): + self.validate_all( + "SELECT CONCAT('abcde', 2, NULL, 22)", + write={ + "postgres": "SELECT CONCAT(COALESCE(CAST('abcde' AS TEXT), ''), COALESCE(CAST(2 AS TEXT), ''), COALESCE(CAST(NULL AS TEXT), ''), COALESCE(CAST(22 AS TEXT), ''))", + }, + ) self.validate_all( "CONCAT(a, b)", write={ - "": "CONCAT(COALESCE(a, ''), COALESCE(b, ''))", - "duckdb": "CONCAT(COALESCE(a, ''), COALESCE(b, ''))", - "postgres": "CONCAT(COALESCE(a, ''), COALESCE(b, ''))", - "presto": "CONCAT(CAST(COALESCE(a, '') AS VARCHAR), CAST(COALESCE(b, '') AS VARCHAR))", + "": "CONCAT(COALESCE(CAST(a AS TEXT), ''), COALESCE(CAST(b AS TEXT), ''))", + "duckdb": "CONCAT(COALESCE(CAST(a AS TEXT), ''), COALESCE(CAST(b AS TEXT), ''))", + "postgres": "CONCAT(COALESCE(CAST(a AS TEXT), ''), COALESCE(CAST(b AS TEXT), ''))", + "presto": "CONCAT(CAST(COALESCE(CAST(a AS VARCHAR), '') AS VARCHAR), CAST(COALESCE(CAST(b AS VARCHAR), '') AS VARCHAR))", }, ) self.validate_all(