﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
35021	Debug query capturing on psycopg3 disregards execute wrappers.	Ran Benita	Michail Chatzis	"== Problem

I use [https://docs.djangoproject.com/en/4.2/topics/db/instrumentation/#connection-execute-wrapper connection.execute_wrapper] as a way to add comments to certain queries (see code at the end for reference). And I test the wrapper using `assertNumQueries` (see code at end for reference). Once I switched from psycopg2 to psycopg3, this test started failing: the unmodified query is captured, instead of the query modified by the execute wrapper. Note however that the modified query is what is actually sent to the DB, so this is only a debug issue.

I expect it's the same for `django.db.backends` debug logs but I haven't verified this.

== Analysis

I've traced the issue to the following code in `postgresql.DatabaseOperations.last_executed_query` https://github.com/django/django/blob/66d58e77de3196404e0820a6fef0a6144186015a/django/db/backends/postgresql/operations.py#L296-L311:

{{{
#!python

    if is_psycopg3:

        def last_executed_query(self, cursor, sql, params):
            try:
                return self.compose_sql(sql, params)
            except errors.DataError:
                return None

    else:

        def last_executed_query(self, cursor, sql, params):
            # https://www.psycopg.org/docs/cursor.html#cursor.query
            # The query attribute is a Psycopg extension to the DB API 2.0.
            if cursor.query is not None:
                return cursor.query.decode()
            return None
}}}

psycopg2 uses `cursor.query` which ends up being the modified query. psycopg3 uses whatever's passed in which ends up being the unmodified query.

It seems like psycopg3 has an equivalent in [https://www.psycopg.org/psycopg3/docs/api/cursors.html#psycopg.Cursor._query cursor._query]. It is documented in the API reference but with a warning ""You shouldn’t consider it part of the public interface of the object: it might change without warnings. [...] If you would like to build reliable features using this object, please get in touch so we can try and design an useful interface for it."". So if this is the desired solution, will need to work with psycopg to expose a stable interface.

== Reproduction code

Example execute wrapper:

{{{
#!python
def db_comment_wrapper(comment: str) -> AbstractContextManager[None]:
    def handler(execute, sql, params, many, context):
        clean_comment = escape(comment)
        return execute(f'/* {clean_comment} */ {sql}', params, many, context)
    return db_connection.execute_wrapper(handler)
}}}

Test:

{{{
#!python
with self.assertNumQueries(1) as captured:
    with db_comment_wrapper('This is a comment'):
        list(ContentType.objects.all())
sql = captured[0]['sql']
assert sql.startswith('/* This is a comment */')
}}}"	Cleanup/optimization	closed	Database layer (models, ORM)	5.0	Normal	fixed		Florian Apolloner Daniele Varrazzo	Ready for checkin	1	0	0	0	0	0
