diff --git a/pandas/io/sql.py b/pandas/io/sql.py index 52adbd42c4479..03dab304b8976 100644 --- a/pandas/io/sql.py +++ b/pandas/io/sql.py @@ -98,7 +98,30 @@ # -- Helper functions +def _sa_text_if_string(stmt: Any) -> Any: + """ + Wrap plain SQL strings with sqlalchemy.text() if SQLAlchemy is available. + + Parameters + ---------- + stmt : Any + The SQL statement or object. + + Returns + ------- + Any + `sqlalchemy.sql.elements.TextClause` if wrapping occurred, + otherwise the original statement. + """ + try: + import sqlalchemy as sa # lazy import; keep SA optional + except ImportError: + return stmt + return sa.text(stmt) if isinstance(stmt, str) else stmt + + def _process_parse_dates_argument(parse_dates): + """Process parse_dates argument for read_sql functions""" # handle non-list entries for parse_dates gracefully if parse_dates is True or parse_dates is None or parse_dates is False: @@ -1853,7 +1876,8 @@ def read_query( read_sql """ - result = self.execute(sql, params) + stmt = _sa_text_if_string(sql) + result = self.execute(stmt, params) columns = result.keys() if chunksize is not None: @@ -1882,6 +1906,7 @@ def read_query( ) return frame + read_sql = read_query def prep_table( diff --git a/pandas/tests/io/sql/test_percent_patterns.py b/pandas/tests/io/sql/test_percent_patterns.py new file mode 100644 index 0000000000000..5352c2fbd2b9b --- /dev/null +++ b/pandas/tests/io/sql/test_percent_patterns.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +pytest.importorskip("sqlalchemy") + + +def test_modulo_operator(sql_con: Any) -> None: + query = "SELECT 10 % 3" + result = sql_con.execute(query) + assert result.scalar() == 1 + + +def test_like_pattern(sql_con: Any) -> None: + query = "SELECT 'abc' LIKE 'a%'" + result = sql_con.execute(query) + assert result.scalar() == 1 + + +def test_sqlalchemy_selectable(sql_con: Any) -> None: + from sqlalchemy import literal, select + + stmt = select(literal("hello")) + result = sql_con.execute(stmt) + assert result.scalar() == "hello"