Skip to content

Commit c0f8bf5

Browse files
mergify[bot]majorgreysbrettlangdon
authored
fix(django): handle composed queries in postgres (#2463) (#2513)
* fix(django): handle composed queries in postgres * fix patching * nit * black * ducktype Co-authored-by: Brett Langdon <[email protected]> (cherry picked from commit af326b6) Co-authored-by: Tahir H. Butt <[email protected]> Co-authored-by: Brett Langdon <[email protected]>
1 parent 8f1bc78 commit c0f8bf5

File tree

5 files changed

+98
-1
lines changed

5 files changed

+98
-1
lines changed

ddtrace/contrib/django/patch.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
from ddtrace.constants import SPAN_MEASURED_KEY
2020
from ddtrace.contrib import dbapi
2121
from ddtrace.contrib import func_name
22+
23+
24+
try:
25+
from psycopg2._psycopg import cursor as psycopg_cursor_cls
26+
27+
from ddtrace.contrib.psycopg.patch import Psycopg2TracedCursor
28+
except ImportError:
29+
psycopg_cursor_cls = None
30+
Psycopg2TracedCursor = None
31+
2232
from ddtrace.ext import SpanTypes
2333
from ddtrace.ext import http
2434
from ddtrace.ext import sql as sqlx
@@ -82,7 +92,15 @@ def cursor(django, pin, func, instance, args, kwargs):
8292
"django.db.alias": alias,
8393
}
8494
pin = Pin(service, tags=tags, tracer=pin.tracer, app=prefix)
85-
return dbapi.TracedCursor(func(*args, **kwargs), pin, config.django)
95+
cursor = func(*args, **kwargs)
96+
traced_cursor_cls = dbapi.TracedCursor
97+
if (
98+
Psycopg2TracedCursor is not None
99+
and hasattr(cursor, "cursor")
100+
and isinstance(cursor.cursor, psycopg_cursor_cls)
101+
):
102+
traced_cursor_cls = Psycopg2TracedCursor
103+
return traced_cursor_cls(cursor, pin, config.django)
86104

87105
if not isinstance(conn.cursor, wrapt.ObjectProxy):
88106
conn.cursor = wrapt.FunctionWrapper(conn.cursor, trace_utils.with_traced_module(cursor)(django))

docs/spelling_wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ CGroup
2828
cgroups
2929
cherrypy
3030
compat
31+
composable
3132
config
3233
coroutine
3334
coroutines
@@ -104,6 +105,7 @@ respawn
104105
runnable
105106
runtime
106107
sanic
108+
sql
107109
sqlalchemy
108110
sqlite
109111
starlette
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
django: fix a bug when postgres query is composable sql object.

tests/contrib/django/test_django_snapshots.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,38 @@ def test_safe_string_encoding(client):
6868
)
6969
def test_404_exceptions(client):
7070
assert client.get("/404-view/").status_code == 404
71+
72+
73+
@pytest.fixture()
74+
def psycopg2_patched(transactional_db):
75+
from django.db import connections
76+
77+
from ddtrace.contrib.psycopg.patch import patch
78+
from ddtrace.contrib.psycopg.patch import unpatch
79+
80+
patch()
81+
82+
# # force recreate connection to ensure psycopg2 patching has occurred
83+
del connections["postgres"]
84+
connections["postgres"].close()
85+
connections["postgres"].connect()
86+
87+
yield
88+
89+
unpatch()
90+
91+
92+
@snapshot(ignores=["meta.out.host"])
93+
@pytest.mark.django_db
94+
def test_psycopg_query_default(client, psycopg2_patched):
95+
"""Execute a psycopg2 query on a Django database wrapper"""
96+
from django.db import connections
97+
from psycopg2.sql import SQL
98+
99+
query = SQL("""select 'one' as x""")
100+
conn = connections["postgres"]
101+
with conn.cursor() as cur:
102+
cur.execute(query)
103+
rows = cur.fetchall()
104+
assert len(rows) == 1, rows
105+
assert rows[0][0] == "one"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[[{"name" "postgres.query"
2+
"service" "postgresdb"
3+
"resource" "select 'one' as x"
4+
"type" "sql"
5+
"error" 0
6+
"span_id" 0
7+
"trace_id" 0
8+
"parent_id" nil
9+
"start" 1623177658188007300
10+
"duration" 1828500
11+
"meta" {"runtime-id" "98e7426d5d6d44d1a5d76d1322d69cac"
12+
"django.db.vendor" "postgresql"
13+
"django.db.alias" "postgres"}
14+
"metrics" {"_dd.agent_psr" 1.0
15+
"_dd.measured" 1
16+
"_sampling_priority_v1" 1
17+
"system.pid" 236
18+
"db.rowcount" 1
19+
"sql.rows" 1
20+
"_dd.tracer_kr" 1.0}}
21+
{"name" "postgres.query"
22+
"service" "postgres"
23+
"resource" "select 'one' as x"
24+
"type" "sql"
25+
"error" 0
26+
"span_id" 1
27+
"trace_id" 0
28+
"parent_id" 0
29+
"start" 1623177658188405100
30+
"duration" 1265000
31+
"meta" {"out.host" "127.0.0.1"
32+
"db.name" "test_postgres"
33+
"db.user" "postgres"
34+
"db.application" "None"}
35+
"metrics" {"_dd.measured" 1
36+
"out.port" 5432
37+
"db.rowcount" 1
38+
"sql.rows" 1}}]]

0 commit comments

Comments
 (0)