Skip to content

Commit a8c2000

Browse files
cdcadmanChuck Cadman
and
Chuck Cadman
authored
BUG: Make io.sql.execute raise TypeError on Engine or URI string. (#50177)
Co-authored-by: Chuck Cadman <[email protected]>
1 parent 4846169 commit a8c2000

File tree

3 files changed

+23
-26
lines changed

3 files changed

+23
-26
lines changed

doc/source/user_guide/io.rst

-15
Original file line numberDiff line numberDiff line change
@@ -5790,21 +5790,6 @@ Specifying this will return an iterator through chunks of the query result:
57905790
for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
57915791
print(chunk)
57925792
5793-
You can also run a plain query without creating a ``DataFrame`` with
5794-
:func:`~pandas.io.sql.execute`. This is useful for queries that don't return values,
5795-
such as INSERT. This is functionally equivalent to calling ``execute`` on the
5796-
SQLAlchemy engine or db connection object. Again, you must use the SQL syntax
5797-
variant appropriate for your database.
5798-
5799-
.. code-block:: python
5800-
5801-
from pandas.io import sql
5802-
5803-
sql.execute("SELECT * FROM table_name", engine)
5804-
sql.execute(
5805-
"INSERT INTO table_name VALUES(?, ?, ?)", engine, params=[("id", 1, 12.2, True)]
5806-
)
5807-
58085793
58095794
Engine connection examples
58105795
''''''''''''''''''''''''''

pandas/io/sql.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,7 @@ def execute(sql, con, params=None):
192192
----------
193193
sql : string
194194
SQL query to be executed.
195-
con : SQLAlchemy connectable(engine/connection) or sqlite3 connection
196-
Using SQLAlchemy makes it possible to use any DB supported by the
197-
library.
195+
con : SQLAlchemy connection or sqlite3 connection
198196
If a DBAPI2 object, only sqlite3 is supported.
199197
params : list or tuple, optional, default: None
200198
List of parameters to pass to execute method.
@@ -203,6 +201,10 @@ def execute(sql, con, params=None):
203201
-------
204202
Results Iterable
205203
"""
204+
sqlalchemy = import_optional_dependency("sqlalchemy", errors="ignore")
205+
206+
if sqlalchemy is not None and isinstance(con, (str, sqlalchemy.engine.Engine)):
207+
raise TypeError("pandas.io.sql.execute requires a connection") # GH50185
206208
with pandasSQL_builder(con, need_transaction=True) as pandas_sql:
207209
args = _convert_params(sql, params)
208210
return pandas_sql.execute(*args)

pandas/tests/io/test_sql.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,11 @@ def psql_insert_copy(table, conn, keys, data_iter):
663663
tm.assert_frame_equal(result, expected)
664664

665665

666+
def test_execute_typeerror(sqlite_iris_engine):
667+
with pytest.raises(TypeError, match="pandas.io.sql.execute requires a connection"):
668+
sql.execute("select * from iris", sqlite_iris_engine)
669+
670+
666671
class MixInBase:
667672
def teardown_method(self):
668673
# if setup fails, there may not be a connection to close.
@@ -956,7 +961,8 @@ def test_roundtrip_chunksize(self, test_frame1):
956961

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

@@ -2795,7 +2801,8 @@ def format_query(sql, *args):
27952801

27962802
def tquery(query, con=None):
27972803
"""Replace removed sql.tquery function"""
2798-
res = sql.execute(query, con=con).fetchall()
2804+
with sql.pandasSQL_builder(con) as pandas_sql:
2805+
res = pandas_sql.execute(query).fetchall()
27992806
return None if res is None else list(res)
28002807

28012808

@@ -2860,7 +2867,8 @@ def test_execute(self, sqlite_buildin):
28602867
ins = "INSERT INTO test VALUES (?, ?, ?, ?)"
28612868

28622869
row = frame.iloc[0]
2863-
sql.execute(ins, sqlite_buildin, params=tuple(row))
2870+
with sql.pandasSQL_builder(sqlite_buildin) as pandas_sql:
2871+
pandas_sql.execute(ins, tuple(row))
28642872
sqlite_buildin.commit()
28652873

28662874
result = sql.read_sql("select * from test", sqlite_buildin)
@@ -2895,11 +2903,12 @@ def test_execute_fail(self, sqlite_buildin):
28952903
cur = sqlite_buildin.cursor()
28962904
cur.execute(create_sql)
28972905

2898-
sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)', sqlite_buildin)
2899-
sql.execute('INSERT INTO test VALUES("foo", "baz", 2.567)', sqlite_buildin)
2906+
with sql.pandasSQL_builder(sqlite_buildin) as pandas_sql:
2907+
pandas_sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)')
2908+
pandas_sql.execute('INSERT INTO test VALUES("foo", "baz", 2.567)')
29002909

2901-
with pytest.raises(sql.DatabaseError, match="Execution failed on sql"):
2902-
sql.execute('INSERT INTO test VALUES("foo", "bar", 7)', sqlite_buildin)
2910+
with pytest.raises(sql.DatabaseError, match="Execution failed on sql"):
2911+
pandas_sql.execute('INSERT INTO test VALUES("foo", "bar", 7)')
29032912

29042913
def test_execute_closed_connection(self):
29052914
create_sql = """
@@ -2915,7 +2924,8 @@ def test_execute_closed_connection(self):
29152924
cur = conn.cursor()
29162925
cur.execute(create_sql)
29172926

2918-
sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)', conn)
2927+
with sql.pandasSQL_builder(conn) as pandas_sql:
2928+
pandas_sql.execute('INSERT INTO test VALUES("foo", "bar", 1.234)')
29192929

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

0 commit comments

Comments
 (0)