Skip to content

Commit f9ba830

Browse files
committed
restore statement substitution to before_execute()
Fixed issue where the ability of the :meth:`_engine.ConnectionEvents.before_execute` method to alter the SQL statement object passed, returning the new object to be invoked, was inadvertently removed. This behavior has been restored. The refactor in a193971 removed this feature for some reason and there were no tests in place to detect it. I don't see any indication this was planned. Fixes: sqlalchemy#6913 Change-Id: Ia77ca08aa91ab9403f19a8eb61e2a0e41aad138a
1 parent 5c4a74f commit f9ba830

4 files changed

Lines changed: 77 additions & 1 deletion

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. change::
2+
:tags: bug, engine, regression
3+
:tickets: 6913
4+
5+
Fixed issue where the ability of the
6+
:meth:`_engine.ConnectionEvents.before_execute` method to alter the SQL
7+
statement object passed, returning the new object to be invoked, was
8+
inadvertently removed. This behavior has been restored.
9+

lib/sqlalchemy/engine/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,7 @@ def _execute_default(
12871287

12881288
if self._has_events or self.engine._has_events:
12891289
(
1290+
default,
12901291
distilled_params,
12911292
event_multiparams,
12921293
event_params,
@@ -1335,6 +1336,7 @@ def _execute_ddl(self, ddl, multiparams, params, execution_options):
13351336

13361337
if self._has_events or self.engine._has_events:
13371338
(
1339+
ddl,
13381340
distilled_params,
13391341
event_multiparams,
13401342
event_params,
@@ -1399,7 +1401,7 @@ def _invoke_before_exec_event(
13991401
else:
14001402
distilled_params = []
14011403

1402-
return distilled_params, event_multiparams, event_params
1404+
return elem, distilled_params, event_multiparams, event_params
14031405

14041406
def _execute_clauseelement(
14051407
self, elem, multiparams, params, execution_options
@@ -1415,6 +1417,7 @@ def _execute_clauseelement(
14151417
has_events = self._has_events or self.engine._has_events
14161418
if has_events:
14171419
(
1420+
elem,
14181421
distilled_params,
14191422
event_multiparams,
14201423
event_params,
@@ -1492,6 +1495,7 @@ def _execute_compiled(
14921495

14931496
if self._has_events or self.engine._has_events:
14941497
(
1498+
compiled,
14951499
distilled_params,
14961500
event_multiparams,
14971501
event_params,
@@ -1536,6 +1540,7 @@ def _exec_driver_sql(
15361540
if not future:
15371541
if self._has_events or self.engine._has_events:
15381542
(
1543+
statement,
15391544
distilled_params,
15401545
event_multiparams,
15411546
event_params,

test/engine/test_deprecations.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,17 @@ def after_execute(
16871687
)
16881688
eq_(result.all(), [("15",)])
16891689

1690+
@testing.only_on("sqlite")
1691+
def test_modify_statement_string(self, connection):
1692+
@event.listens_for(connection, "before_execute", retval=True)
1693+
def _modify(
1694+
conn, clauseelement, multiparams, params, execution_options
1695+
):
1696+
return clauseelement.replace("hi", "there"), multiparams, params
1697+
1698+
with _string_deprecation_expect():
1699+
eq_(connection.scalar("select 'hi'"), "there")
1700+
16901701
def test_retval_flag(self):
16911702
canary = []
16921703

test/engine/test_execute.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from sqlalchemy.pool import QueuePool
3232
from sqlalchemy.sql import column
3333
from sqlalchemy.sql import literal
34+
from sqlalchemy.sql.elements import literal_column
3435
from sqlalchemy.testing import assert_raises
3536
from sqlalchemy.testing import assert_raises_message
3637
from sqlalchemy.testing import config
@@ -1771,6 +1772,56 @@ def before_execute(
17711772
with e1.connect() as conn:
17721773
conn.execute(select(literal("1")))
17731774

1775+
@testing.only_on("sqlite")
1776+
def test_dont_modify_statement_driversql(self, connection):
1777+
m1 = mock.Mock()
1778+
1779+
@event.listens_for(connection, "before_execute", retval=True)
1780+
def _modify(
1781+
conn, clauseelement, multiparams, params, execution_options
1782+
):
1783+
m1.run_event()
1784+
return clauseelement.replace("hi", "there"), multiparams, params
1785+
1786+
# the event does not take effect for the "driver SQL" option
1787+
eq_(connection.exec_driver_sql("select 'hi'").scalar(), "hi")
1788+
1789+
# event is not called at all
1790+
eq_(m1.mock_calls, [])
1791+
1792+
@testing.combinations((True,), (False,), argnames="future")
1793+
@testing.only_on("sqlite")
1794+
def test_modify_statement_internal_driversql(self, connection, future):
1795+
m1 = mock.Mock()
1796+
1797+
@event.listens_for(connection, "before_execute", retval=True)
1798+
def _modify(
1799+
conn, clauseelement, multiparams, params, execution_options
1800+
):
1801+
m1.run_event()
1802+
return clauseelement.replace("hi", "there"), multiparams, params
1803+
1804+
eq_(
1805+
connection._exec_driver_sql(
1806+
"select 'hi'", [], {}, {}, future=future
1807+
).scalar(),
1808+
"hi" if future else "there",
1809+
)
1810+
1811+
if future:
1812+
eq_(m1.mock_calls, [])
1813+
else:
1814+
eq_(m1.mock_calls, [call.run_event()])
1815+
1816+
def test_modify_statement_clauseelement(self, connection):
1817+
@event.listens_for(connection, "before_execute", retval=True)
1818+
def _modify(
1819+
conn, clauseelement, multiparams, params, execution_options
1820+
):
1821+
return select(literal_column("'there'")), multiparams, params
1822+
1823+
eq_(connection.scalar(select(literal_column("'hi'"))), "there")
1824+
17741825
def test_argument_format_execute(self, testing_engine):
17751826
def before_execute(
17761827
conn, clauseelement, multiparams, params, execution_options

0 commit comments

Comments
 (0)