Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

#### Bug Fixes

- Fixed a Snowflake platform compatibility issue (SNOW-3259059) where `concat(lit('"'), ...)` could lose the leading quote through some `EXCEPT` / chained set-operation plans by lowering that literal to `CHR(34)` in generated SQL.

#### Improvements

- Restored the following query improvements that were reverted in 1.47.0 due to bugs:
Expand Down
23 changes: 22 additions & 1 deletion src/snowflake/snowpark/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3712,6 +3712,22 @@ def collation(e: ColumnOrName, _emit_ast: bool = True) -> Column:
return _call_function("collation", c, _emit_ast=_emit_ast)


def _rewrite_concat_arg_if_double_quote_string_literal(
c: Column, *, _emit_ast: bool
) -> Column:
# SNOW-3259059: Some Snowflake releases (10.7.1–10.9.2 per JIRA) dropped CONCAT's leading
# lit('"') through EXCEPT / chained set-op plans. CHR(34) is the same VARCHAR character in SQL
# without that specific single-character literal form.
expr = c._expression
if (
isinstance(expr, Literal)
and isinstance(expr.datatype, StringType)
and expr.value == '"'
):
return _call_function("chr", lit(34, _emit_ast=_emit_ast), _emit_ast=_emit_ast)
return c


@publicapi
def concat(*cols: ColumnOrName, _emit_ast: bool = True) -> Column:
"""Concatenates one or more strings, or concatenates one or more binary values. If any of the values is null, the result is also null.
Expand All @@ -3726,7 +3742,12 @@ def concat(*cols: ColumnOrName, _emit_ast: bool = True) -> Column:
--------------------------
<BLANKLINE>
"""
columns = [_to_col_if_str(c, "concat") for c in cols]
columns = [
_rewrite_concat_arg_if_double_quote_string_literal(
_to_col_if_str(c, "concat"), _emit_ast=_emit_ast
)
for c in cols
]
return _call_function("concat", *columns, _emit_ast=_emit_ast)


Expand Down
26 changes: 26 additions & 0 deletions tests/integ/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,32 @@ def test_concat(session, col_a, col_b, col_c):
assert res[0][0] == "123"


@pytest.mark.skipif(
"config.getoption('local_testing_mode', default=False)",
reason="Local testing replaces plan SQL with MOCK_TEST_FAKE_QUERY(); CHR(34) assertion needs real SQL.",
)
def test_concat_rewrites_lit_double_quote_to_chr_sql(session):
df = session.create_dataframe([["fn"]], schema=["a"])
out = df.select(concat(lit('"'), col("a")).alias("q"))
sql = out.queries["queries"][0].upper()
assert "CHR(34)" in sql
assert "CONCAT(" in sql
rows = out.collect()
assert len(rows) == 1 and rows[0][0] == '"fn'


@pytest.mark.skipif(
"config.getoption('local_testing_mode', default=False)",
reason="Local testing replaces plan SQL with MOCK_TEST_FAKE_QUERY(); literal SQL assertions need real SQL.",
)
def test_concat_preserves_other_string_literals_sql(session):
df = session.create_dataframe([["x"]], schema=["a"])
out = df.select(concat(lit("##"), col("a")).alias("q"))
sql = out.queries["queries"][0]
assert "##" in sql
assert "CHR(34)" not in sql.upper()


@pytest.mark.parametrize(
"col_a, col_b, col_c", [("a", "b", "c"), (col("a"), col("b"), col("c"))]
)
Expand Down
Loading