diff --git a/pyathena/formatter.py b/pyathena/formatter.py index 5759642e..89564ea4 100644 --- a/pyathena/formatter.py +++ b/pyathena/formatter.py @@ -150,11 +150,21 @@ def wrap_unload( return operation, location -def _escape_presto(val: str) -> str: +def _escape_trino(val: str) -> str: escaped = val.replace("'", "''") return f"'{escaped}'" +def _escape_presto(val: str) -> str: + # Backward-compatible alias. Athena's engine is Trino (engine v3; formerly + # Presto in engine v1/v2), and Trino and Presto escape string literals + # identically -- a single quote is doubled and a backslash is not an escape + # character. `_escape_trino` is the canonical name; `_escape_presto` is kept + # so external callers that import it (e.g. dbt-athena) keep working. + # Deprecated; candidate for removal in a future major release. + return _escape_trino(val) + + _LEADING_COMMENT_PATTERN = re.compile( r"^(?:\s*(?:/\*.*?\*/|--[^\n]*(?:\n|$)))+", re.DOTALL, @@ -203,7 +213,7 @@ def _get_escaper(operation: str) -> Callable[[str], str]: """Select the escaper matching the engine that will parse the statement.""" if _HIVE_STATEMENT_PATTERN.match(_strip_leading_comments(operation)): return _escape_hive - return _escape_presto + return _escape_trino def _escape_hive(val: str) -> str: diff --git a/tests/pyathena/test_formatter.py b/tests/pyathena/test_formatter.py index 7af39e4c..87da041d 100644 --- a/tests/pyathena/test_formatter.py +++ b/tests/pyathena/test_formatter.py @@ -5,6 +5,7 @@ import pytest from pyathena.error import ProgrammingError +from pyathena.formatter import _escape_presto, _escape_trino # A value whose single quote must not be allowed to terminate the string # literal. Trino and Athena do not treat a backslash as an escape character @@ -553,3 +554,16 @@ def test_table_name_starting_with_as_is_still_hive_ddl(self, formatter): assert "\\'" in formatter.format( "CREATE EXTERNAL TABLE as_of (c string) LOCATION %(v)s", {"v": HOSTILE} ) + + +def test_escape_trino_doubles_single_quotes(): + assert _escape_trino("a'b") == "'a''b'" + # A backslash is not an escape character in Trino/Presto; it stays literal. + assert _escape_trino("a\\'b") == "'a\\''b'" + + +def test_escape_presto_is_backward_compatible_alias(): + # _escape_presto is a deprecated alias that must keep producing output + # identical to _escape_trino for external callers (e.g. dbt-athena). + value = "a' OR 1=1 --" + assert _escape_presto(value) == _escape_trino(value) == "'a'' OR 1=1 --'"