1+ import inspect
2+ import os
3+ import sys
4+
15from collections .abc import Iterable
26from typing import Tuple , Union
37
48import django
59
10+ from django .conf import settings
611from django .core .exceptions import SuspiciousOperation
712from django .db .models import Expression , Model , Q
813from django .db .models .fields .related import RelatedField
9- from django .db .models .sql .compiler import SQLInsertCompiler , SQLUpdateCompiler
14+ from django .db .models .sql .compiler import (
15+ SQLAggregateCompiler ,
16+ SQLCompiler ,
17+ SQLDeleteCompiler ,
18+ SQLInsertCompiler ,
19+ SQLUpdateCompiler ,
20+ )
1021from django .db .utils import ProgrammingError
1122
1223from .expressions import HStoreValue
1324from .types import ConflictAction
1425
1526
27+ def append_caller_to_sql (sql ):
28+ """Append the caller to SQL queries.
29+
30+ Adds the calling file and function as an SQL comment to each query.
31+ Examples:
32+ INSERT INTO "tests_47ee19d1" ("id", "title")
33+ VALUES (1, 'Test')
34+ RETURNING "tests_47ee19d1"."id"
35+ /* 998020 test_append_caller_to_sql_crud .../django-postgres-extra/tests/test_append_caller_to_sql.py 55 */
36+
37+ SELECT "tests_47ee19d1"."id", "tests_47ee19d1"."title"
38+ FROM "tests_47ee19d1"
39+ WHERE "tests_47ee19d1"."id" = 1
40+ LIMIT 1
41+ /* 998020 test_append_caller_to_sql_crud .../django-postgres-extra/tests/test_append_caller_to_sql.py 69 */
42+
43+ UPDATE "tests_47ee19d1"
44+ SET "title" = 'success'
45+ WHERE "tests_47ee19d1"."id" = 1
46+ /* 998020 test_append_caller_to_sql_crud .../django-postgres-extra/tests/test_append_caller_to_sql.py 64 */
47+
48+ DELETE FROM "tests_47ee19d1"
49+ WHERE "tests_47ee19d1"."id" IN (1)
50+ /* 998020 test_append_caller_to_sql_crud .../django-postgres-extra/tests/test_append_caller_to_sql.py 74 */
51+
52+ Slow and blocking queries could be easily tracked down to their originator
53+ within the source code using the "pg_stat_activity" table.
54+
55+ Enable "PSQLEXTRA_ANNOTATE_SQL" within the database settings to enable this feature.
56+ """
57+
58+ if not getattr (settings , "PSQLEXTRA_ANNOTATE_SQL" , None ):
59+ return sql
60+
61+ try :
62+ # Search for the first non-Django caller
63+ stack = inspect .stack ()
64+ for stack_frame in stack [1 :]:
65+ frame_filename = stack_frame [1 ]
66+ frame_line = stack_frame [2 ]
67+ frame_function = stack_frame [3 ]
68+ if "/django/" in frame_filename or "/psqlextra/" in frame_filename :
69+ continue
70+
71+ return f"{ sql } /* { os .getpid ()} { frame_function } { frame_filename } { frame_line } */"
72+
73+ # Django internal commands (like migrations) end up here
74+ return f"{ sql } /* { os .getpid ()} { sys .argv [0 ]} */"
75+ except Exception :
76+ # Don't break anything because this convinence function runs into an unexpected situation
77+ return sql
78+
79+
80+ class PostgresCompiler (SQLCompiler ):
81+ def as_sql (self ):
82+ sql , params = super ().as_sql ()
83+ return append_caller_to_sql (sql ), params
84+
85+
86+ class PostgresDeleteCompiler (SQLDeleteCompiler ):
87+ def as_sql (self ):
88+ sql , params = super ().as_sql ()
89+ return append_caller_to_sql (sql ), params
90+
91+
92+ class PostgresAggregateCompiler (SQLAggregateCompiler ):
93+ def as_sql (self ):
94+ sql , params = super ().as_sql ()
95+ return append_caller_to_sql (sql ), params
96+
97+
1698class PostgresUpdateCompiler (SQLUpdateCompiler ):
1799 """Compiler for SQL UPDATE statements that allows us to use expressions
18100 inside HStore values.
@@ -24,7 +106,8 @@ class PostgresUpdateCompiler(SQLUpdateCompiler):
24106
25107 def as_sql (self ):
26108 self ._prepare_query_values ()
27- return super ().as_sql ()
109+ sql , params = super ().as_sql ()
110+ return append_caller_to_sql (sql ), params
28111
29112 def _prepare_query_values (self ):
30113 """Extra prep on query values by converting dictionaries into.
@@ -72,15 +155,27 @@ def _does_dict_contain_expression(data: dict) -> bool:
72155class PostgresInsertCompiler (SQLInsertCompiler ):
73156 """Compiler for SQL INSERT statements."""
74157
75- def __init__ (self , * args , ** kwargs ):
76- """Initializes a new instance of :see:PostgresInsertCompiler."""
158+ def as_sql (self , return_id = False ):
159+ """Builds the SQL INSERT statement."""
160+ queries = [
161+ (append_caller_to_sql (sql ), params )
162+ for sql , params in super ().as_sql ()
163+ ]
164+
165+ return queries
77166
167+
168+ class PostgresInsertOnConflictCompiler (SQLInsertCompiler ):
169+ """Compiler for SQL INSERT statements."""
170+
171+ def __init__ (self , * args , ** kwargs ):
172+ """Initializes a new instance of
173+ :see:PostgresInsertOnConflictCompiler."""
78174 super ().__init__ (* args , ** kwargs )
79175 self .qn = self .connection .ops .quote_name
80176
81177 def as_sql (self , return_id = False ):
82178 """Builds the SQL INSERT statement."""
83-
84179 queries = [
85180 self ._rewrite_insert (sql , params , return_id )
86181 for sql , params in super ().as_sql ()
@@ -132,10 +227,12 @@ def _rewrite_insert(self, sql, params, return_id=False):
132227 self .qn (self .query .model ._meta .pk .attname ) if return_id else "*"
133228 )
134229
135- return self ._rewrite_insert_on_conflict (
230+ ( sql , params ) = self ._rewrite_insert_on_conflict (
136231 sql , params , self .query .conflict_action .value , returning
137232 )
138233
234+ return append_caller_to_sql (sql ), params
235+
139236 def _rewrite_insert_on_conflict (
140237 self , sql , params , conflict_action : ConflictAction , returning
141238 ):
0 commit comments