From 249ecc437f79c08b087d0daa0ec8b41f9b25a238 Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Tue, 5 Jul 2022 05:53:49 +0200
Subject: [PATCH] Fixed #33815 -- Fixed last_executed_query() on Oracle when
 parameter names overlap.

---
 django/db/backends/oracle/operations.py |  9 +++++----
 tests/backends/tests.py                 | 17 +++++++++++++++++
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py
index 672a02d664..b044adadda 100644
--- a/django/db/backends/oracle/operations.py
+++ b/django/db/backends/oracle/operations.py
@@ -309,14 +309,15 @@ END;
         # `statement` doesn't contain the query parameters. Substitute
         # parameters manually.
         if isinstance(params, (tuple, list)):
-            for i, param in enumerate(params):
+            for i, param in enumerate(reversed(params), start=1):
+                param_num = len(params) - i
                 statement = statement.replace(
-                    ":arg%d" % i, force_str(param, errors="replace")
+                    ":arg%d" % param_num, force_str(param, errors="replace")
                 )
         elif isinstance(params, dict):
-            for key, param in params.items():
+            for key in sorted(params, key=len, reverse=True):
                 statement = statement.replace(
-                    ":%s" % key, force_str(param, errors="replace")
+                    ":%s" % key, force_str(params[key], errors="replace")
                 )
         return statement
 
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index 9303089b51..c6980058ca 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -101,6 +101,7 @@ class LastExecutedQueryTest(TestCase):
                 pk=1,
                 reporter__pk=9,
             ).exclude(reporter__pk__in=[2, 1]),
+            Article.objects.filter(pk__in=list(range(20, 31))),
         ):
             sql, params = qs.query.sql_with_params()
             with qs.query.get_compiler(DEFAULT_DB_ALIAS).execute_sql(CURSOR) as cursor:
@@ -125,6 +126,22 @@ class LastExecutedQueryTest(TestCase):
                 sql % params,
             )
 
+    @skipUnlessDBFeature("supports_paramstyle_pyformat")
+    def test_last_executed_query_dict_overlap_keys(self):
+        square_opts = Square._meta
+        sql = "INSERT INTO %s (%s, %s) VALUES (%%(root)s, %%(root2)s)" % (
+            connection.introspection.identifier_converter(square_opts.db_table),
+            connection.ops.quote_name(square_opts.get_field("root").column),
+            connection.ops.quote_name(square_opts.get_field("square").column),
+        )
+        with connection.cursor() as cursor:
+            params = {"root": 2, "root2": 4}
+            cursor.execute(sql, params)
+            self.assertEqual(
+                cursor.db.ops.last_executed_query(cursor, sql, params),
+                sql % params,
+            )
+
 
 class ParameterHandlingTest(TestCase):
     def test_bad_parameter_count(self):