diff --git a/pandas/io/sql.py b/pandas/io/sql.py index 18129257af1c9..d7a4b0d8f68a0 100644 --- a/pandas/io/sql.py +++ b/pandas/io/sql.py @@ -94,6 +94,15 @@ # -- Helper functions +def _sa_text_if_string(stmt): + """Wrap plain SQL strings in sqlalchemy.text().""" + try: + import sqlalchemy as sa + except Exception: + 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 @@ -1848,7 +1857,9 @@ def read_query( read_sql_table : Read SQL database table into a DataFrame. read_sql + """ + sql = _sa_text_if_string(sql) result = self.execute(sql, params) columns = result.keys() 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..ce233f4c64360 --- /dev/null +++ b/pandas/tests/io/sql/test_percent_patterns.py @@ -0,0 +1,45 @@ +# pandas/tests/io/sql/test_percent_patterns.py +import os + +import pytest + +sa = pytest.importorskip("sqlalchemy") + +PG = os.environ.get("PANDAS_TEST_POSTGRES_URI") +URL = PG or "sqlite+pysqlite:///:memory:" + + +def _eng(): + return sa.create_engine(URL) + + +def test_text_modulo(): + import pandas as pd + + with _eng().connect() as c: + df = pd.read_sql(sa.text("SELECT 5 % 2 AS r"), c) + assert df.iloc[0, 0] == 1 + + +def test_like_single_percent(): + import pandas as pd + + with _eng().connect() as c: + df = pd.read_sql( + sa.text("SELECT 'John' AS fullname WHERE 'John' LIKE 'John%'"), + c, + ) + assert len(df) == 1 + + +def test_sqlalchemy_expr_percent_operator(): + from sqlalchemy import ( + literal, + select, + ) + + import pandas as pd + + with _eng().connect() as c: + df = pd.read_sql(select((literal(7) % literal(3)).label("r")), c) + assert df.iloc[0, 0] == 1