From 4426b1a72dc289643e2ae8c190b8dc4b3a39daf7 Mon Sep 17 00:00:00 2001
From: Michail Chatzis <michatzis@gmail.com>
Date: Fri, 2 Feb 2024 14:38:30 +0200
Subject: [PATCH] Fixed #35021 -- Fixed capturing queries when using
 client-side parameters binding with psycopg 3+.

---
 django/db/backends/postgresql/features.py   |  9 +++++++++
 django/db/backends/postgresql/operations.py | 11 ++++++++---
 django/db/backends/sqlite3/features.py      |  3 +++
 tests/backends/base/test_base.py            | 10 ++++++++++
 4 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index 809466fc7f..ef697e85b0 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -105,6 +105,15 @@ class DatabaseFeatures(BaseDatabaseFeatures):
                     },
                 }
             )
+        if self.uses_server_side_binding:
+            skips.update(
+                {
+                    "The actual query cannot be determined for server side bindings": {
+                        "backends.base.test_base.ExecuteWrapperTests."
+                        "test_wrapper_debug",
+                    }
+                },
+            )
         return skips
 
     @cached_property
diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
index 06981bc094..4e444d5f2b 100644
--- a/django/db/backends/postgresql/operations.py
+++ b/django/db/backends/postgresql/operations.py
@@ -296,9 +296,14 @@ class DatabaseOperations(BaseDatabaseOperations):
     if is_psycopg3:
 
         def last_executed_query(self, cursor, sql, params):
-            try:
-                return self.compose_sql(sql, params)
-            except errors.DataError:
+            if self.connection.features.uses_server_side_binding:
+                try:
+                    return self.compose_sql(sql, params)
+                except errors.DataError:
+                    return None
+            else:
+                if cursor._query and cursor._query.query is not None:
+                    return cursor._query.query.decode()
                 return None
 
     else:
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index 713d8bd38f..d95c6fb2d1 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -89,6 +89,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
                 "db_functions.math.test_round.RoundTests."
                 "test_integer_with_negative_precision",
             },
+            "The actual query cannot be determined on SQLite": {
+                "backends.base.test_base.ExecuteWrapperTests.test_wrapper_debug",
+            },
         }
         if self.connection.is_in_memory_db():
             skips.update(
diff --git a/tests/backends/base/test_base.py b/tests/backends/base/test_base.py
index 03356fbf10..4418d010ea 100644
--- a/tests/backends/base/test_base.py
+++ b/tests/backends/base/test_base.py
@@ -211,6 +211,16 @@ class ExecuteWrapperTests(TestCase):
         self.assertEqual(connection.execute_wrappers, [])
         self.assertEqual(connections["other"].execute_wrappers, [])
 
+    def test_wrapper_debug(self):
+        def wrap_with_comment(execute, sql, params, many, context):
+            return execute(f"/* My comment */ {sql}", params, many, context)
+
+        with CaptureQueriesContext(connection) as ctx:
+            with connection.execute_wrapper(wrap_with_comment):
+                list(Person.objects.all())
+        last_query = ctx.captured_queries[-1]["sql"]
+        self.assertTrue(last_query.startswith("/* My comment */"))
+
 
 class ConnectionHealthChecksTests(SimpleTestCase):
     databases = {"default"}