Skip to content

Commit 79065b5

Browse files
authored
Refs django#20010 -- Unified DatabaseOperations.last_executed_query() on Oracle with other db backends.
Thanks Simon Charette for the review.
1 parent 6b4e57d commit 79065b5

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

django/db/backends/oracle/operations.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.db.backends.utils import strip_quotes, truncate_name
99
from django.db.utils import DatabaseError
1010
from django.utils import timezone
11-
from django.utils.encoding import force_bytes
11+
from django.utils.encoding import force_bytes, force_str
1212
from django.utils.functional import cached_property
1313

1414
from .base import Database
@@ -258,9 +258,16 @@ def last_executed_query(self, cursor, sql, params):
258258
# https://cx-oracle.readthedocs.io/en/latest/cursor.html#Cursor.statement
259259
# The DB API definition does not define this attribute.
260260
statement = cursor.statement
261-
# Unlike Psycopg's `query` and MySQLdb`'s `_executed`, CxOracle's
262-
# `statement` doesn't contain the query parameters. refs #20010.
263-
return super().last_executed_query(cursor, statement, params)
261+
# Unlike Psycopg's `query` and MySQLdb`'s `_executed`, cx_Oracle's
262+
# `statement` doesn't contain the query parameters. Substitute
263+
# parameters manually.
264+
if isinstance(params, (tuple, list)):
265+
for i, param in enumerate(params):
266+
statement = statement.replace(':arg%d' % i, force_str(param, errors='replace'))
267+
elif isinstance(params, dict):
268+
for key, param in params.items():
269+
statement = statement.replace(':%s' % key, force_str(param, errors='replace'))
270+
return statement
264271

265272
def last_insert_id(self, cursor, table_name, pk_name):
266273
sq_name = self._get_sequence_name(cursor, strip_quotes(table_name), pk_name)

tests/backends/tests.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_django_date_extract(self):
5151
@override_settings(DEBUG=True)
5252
class LastExecutedQueryTest(TestCase):
5353

54-
def test_last_executed_query(self):
54+
def test_last_executed_query_without_previous_query(self):
5555
"""
5656
last_executed_query should not raise an exception even if no previous
5757
query has been run.
@@ -73,6 +73,36 @@ def test_query_encoding(self):
7373
last_sql = cursor.db.ops.last_executed_query(cursor, sql, params)
7474
self.assertIsInstance(last_sql, str)
7575

76+
def test_last_executed_query(self):
77+
# last_executed_query() interpolate all parameters, in most cases it is
78+
# not equal to QuerySet.query.
79+
for qs in (
80+
Article.objects.filter(pk=1),
81+
Article.objects.filter(pk__in=(1, 2), reporter__pk=3),
82+
):
83+
sql, params = qs.query.sql_with_params()
84+
cursor = qs.query.get_compiler(DEFAULT_DB_ALIAS).execute_sql(CURSOR)
85+
self.assertEqual(
86+
cursor.db.ops.last_executed_query(cursor, sql, params),
87+
str(qs.query),
88+
)
89+
90+
@skipUnlessDBFeature('supports_paramstyle_pyformat')
91+
def test_last_executed_query_dict(self):
92+
square_opts = Square._meta
93+
sql = 'INSERT INTO %s (%s, %s) VALUES (%%(root)s, %%(square)s)' % (
94+
connection.introspection.identifier_converter(square_opts.db_table),
95+
connection.ops.quote_name(square_opts.get_field('root').column),
96+
connection.ops.quote_name(square_opts.get_field('square').column),
97+
)
98+
with connection.cursor() as cursor:
99+
params = {'root': 2, 'square': 4}
100+
cursor.execute(sql, params)
101+
self.assertEqual(
102+
cursor.db.ops.last_executed_query(cursor, sql, params),
103+
sql % params,
104+
)
105+
76106

77107
class ParameterHandlingTest(TestCase):
78108

0 commit comments

Comments
 (0)