Skip to content

BUG: Make io.sql.execute raise TypeError on Engine or URI string. #50177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 15, 2022
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
15 changes: 0 additions & 15 deletions doc/source/user_guide/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5785,21 +5785,6 @@ Specifying this will return an iterator through chunks of the query result:
for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
print(chunk)

You can also run a plain query without creating a ``DataFrame`` with
:func:`~pandas.io.sql.execute`. This is useful for queries that don't return values,
such as INSERT. This is functionally equivalent to calling ``execute`` on the
SQLAlchemy engine or db connection object. Again, you must use the SQL syntax
variant appropriate for your database.

.. code-block:: python

from pandas.io import sql

sql.execute("SELECT * FROM table_name", engine)
sql.execute(
"INSERT INTO table_name VALUES(?, ?, ?)", engine, params=[("id", 1, 12.2, True)]
)


Engine connection examples
''''''''''''''''''''''''''
Expand Down
8 changes: 5 additions & 3 deletions pandas/io/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,7 @@ def execute(sql, con, params=None):
----------
sql : string
SQL query to be executed.
con : SQLAlchemy connectable(engine/connection) or sqlite3 connection
Using SQLAlchemy makes it possible to use any DB supported by the
library.
con : SQLAlchemy connection or sqlite3 connection
If a DBAPI2 object, only sqlite3 is supported.
params : list or tuple, optional, default: None
List of parameters to pass to execute method.
Expand All @@ -180,6 +178,10 @@ def execute(sql, con, params=None):
-------
Results Iterable
"""
sqlalchemy = import_optional_dependency("sqlalchemy", errors="ignore")

if sqlalchemy is not None and isinstance(con, (str, sqlalchemy.engine.Engine)):
raise TypeError("pandas.io.sql.execute requires a connection") # GH50185
with pandasSQL_builder(con, need_transaction=True) as pandas_sql:
args = _convert_params(sql, params)
return pandas_sql.execute(*args)
Expand Down
26 changes: 18 additions & 8 deletions pandas/tests/io/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,11 @@ def psql_insert_copy(table, conn, keys, data_iter):
tm.assert_frame_equal(result, expected)


def test_execute_typeerror(sqlite_iris_engine):
with pytest.raises(TypeError, match="pandas.io.sql.execute requires a connection"):
sql.execute("select * from iris", sqlite_iris_engine)


class MixInBase:
def teardown_method(self):
# if setup fails, there may not be a connection to close.
Expand Down Expand Up @@ -952,7 +957,8 @@ def test_roundtrip_chunksize(self, test_frame1):

def test_execute_sql(self):
# drop_sql = "DROP TABLE IF EXISTS test" # should already be done
iris_results = sql.execute("SELECT * FROM iris", con=self.conn)
with sql.pandasSQL_builder(self.conn) as pandas_sql:
iris_results = pandas_sql.execute("SELECT * FROM iris")
row = iris_results.fetchone()
tm.equalContents(row, [5.1, 3.5, 1.4, 0.2, "Iris-setosa"])

Expand Down Expand Up @@ -2692,7 +2698,8 @@ def format_query(sql, *args):

def tquery(query, con=None):
"""Replace removed sql.tquery function"""
res = sql.execute(query, con=con).fetchall()
with sql.pandasSQL_builder(con) as pandas_sql:
res = pandas_sql.execute(query).fetchall()
return None if res is None else list(res)


Expand Down Expand Up @@ -2757,7 +2764,8 @@ def test_execute(self, sqlite_buildin):
ins = "INSERT INTO test VALUES (?, ?, ?, ?)"

row = frame.iloc[0]
sql.execute(ins, sqlite_buildin, params=tuple(row))
with sql.pandasSQL_builder(sqlite_buildin) as pandas_sql:
pandas_sql.execute(ins, tuple(row))
sqlite_buildin.commit()

result = sql.read_sql("select * from test", sqlite_buildin)
Expand Down Expand Up @@ -2792,11 +2800,12 @@ def test_execute_fail(self, sqlite_buildin):
cur = sqlite_buildin.cursor()
cur.execute(create_sql)

sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)', sqlite_buildin)
sql.execute('INSERT INTO test VALUES("foo", "baz", 2.567)', sqlite_buildin)
with sql.pandasSQL_builder(sqlite_buildin) as pandas_sql:
pandas_sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)')
pandas_sql.execute('INSERT INTO test VALUES("foo", "baz", 2.567)')

with pytest.raises(sql.DatabaseError, match="Execution failed on sql"):
sql.execute('INSERT INTO test VALUES("foo", "bar", 7)', sqlite_buildin)
with pytest.raises(sql.DatabaseError, match="Execution failed on sql"):
pandas_sql.execute('INSERT INTO test VALUES("foo", "bar", 7)')

def test_execute_closed_connection(self):
create_sql = """
Expand All @@ -2812,7 +2821,8 @@ def test_execute_closed_connection(self):
cur = conn.cursor()
cur.execute(create_sql)

sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)', conn)
with sql.pandasSQL_builder(conn) as pandas_sql:
pandas_sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)')

msg = "Cannot operate on a closed database."
with pytest.raises(sqlite3.ProgrammingError, match=msg):
Expand Down