Skip to content

Commit 807c319

Browse files
mlennon-lumereuntitaker
authored andcommitted
Use cursor.mogrify when available (getsentry#203)
* Use cursor.mogrify when available instead of trying to reimplement sql formatting * fix: Port changes from getsentry#202 * fix: Formatting * adds test for psycopg2 methods * use old style django postgres backend * have travis install test database as well for older versions of django * fix: Avoid bytestrings, refactor postgres setup * fix: Formatting
1 parent 37d12da commit 807c319

File tree

5 files changed

+106
-32
lines changed

5 files changed

+106
-32
lines changed

.travis.yml

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ python:
77
- "3.5"
88
- "3.6"
99

10+
env:
11+
- SENTRY_PYTHON_TEST_POSTGRES_USER=postgres
12+
- SENTRY_PYTHON_TEST_POSTGRES_NAME=travis_ci_test
13+
1014
cache:
1115
pip: true
1216
cargo: true
@@ -48,6 +52,13 @@ matrix:
4852
- source $HOME/osx-py/bin/activate
4953
- export TRAVIS_PYTHON_VERSION=2.7
5054

55+
before_script:
56+
- psql -c 'create database travis_ci_test;' -U postgres
57+
- psql -c 'create database test_travis_ci_test;' -U postgres
58+
59+
services:
60+
- postgresql
61+
5162
install:
5263
- pip install tox
5364
- pip install codecov

sentry_sdk/integrations/django/__init__.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ def format_sql(sql, params):
312312
# convert sql with named parameters to sql with unnamed parameters
313313
conv = _FormatConverter(params)
314314
if params:
315-
sql = sql_to_string(sql)
316315
sql = sql % conv
317316
params = conv.params
318317
else:
@@ -327,20 +326,27 @@ def format_sql(sql, params):
327326
return sql, rv
328327

329328

330-
def record_sql(sql, params):
331-
# type: (Any, Any) -> None
329+
def record_sql(sql, params, cursor=None):
330+
# type: (Any, Any, Any) -> None
332331
hub = Hub.current
333332
if hub.get_integration(DjangoIntegration) is None:
334333
return
335-
real_sql, real_params = format_sql(sql, params)
336334

337-
if real_params:
338-
try:
339-
real_sql = format_and_strip(real_sql, real_params)
340-
except Exception:
341-
pass
335+
with capture_internal_exceptions():
336+
if cursor and hasattr(cursor, "mogrify"): # psycopg2
337+
real_sql = cursor.mogrify(sql, params)
338+
with capture_internal_exceptions():
339+
if isinstance(real_sql, bytes):
340+
real_sql = real_sql.decode(cursor.connection.encoding)
341+
else:
342+
real_sql, real_params = format_sql(sql, params)
342343

343-
hub.add_breadcrumb(message=real_sql, category="query")
344+
if real_params:
345+
try:
346+
real_sql = format_and_strip(real_sql, real_params)
347+
except Exception:
348+
pass
349+
hub.add_breadcrumb(message=real_sql, category="query")
344350

345351

346352
def install_sql_hook():
@@ -358,21 +364,21 @@ def install_sql_hook():
358364
# This won't work on Django versions < 1.6
359365
return
360366

361-
def record_many_sql(sql, param_list):
367+
def record_many_sql(sql, param_list, cursor):
362368
for params in param_list:
363-
record_sql(sql, params)
369+
record_sql(sql, params, cursor)
364370

365371
def execute(self, sql, params=None):
366372
try:
367373
return real_execute(self, sql, params)
368374
finally:
369-
record_sql(sql, params)
375+
record_sql(sql, params, self.cursor)
370376

371377
def executemany(self, sql, param_list):
372378
try:
373379
return real_executemany(self, sql, param_list)
374380
finally:
375-
record_many_sql(sql, param_list)
381+
record_many_sql(sql, param_list, self.cursor)
376382

377383
CursorWrapper.execute = execute
378384
CursorWrapper.executemany = executemany

tests/integrations/django/myapp/settings.py

+9
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ def process_response(self, request, response):
9494

9595
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
9696

97+
try:
98+
DATABASES["postgres"] = {
99+
"ENGINE": "django.db.backends.postgresql_psycopg2",
100+
"NAME": os.environ["SENTRY_PYTHON_TEST_POSTGRES_NAME"],
101+
"USER": os.environ["SENTRY_PYTHON_TEST_POSTGRES_USER"],
102+
}
103+
except KeyError:
104+
pass
105+
97106

98107
# Password validation
99108
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

tests/integrations/django/test_basic.py

+64-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from werkzeug.test import Client
44
from django.contrib.auth.models import User
55
from django.core.management import execute_from_command_line
6-
from django.db.utils import OperationalError
6+
from django.db.utils import OperationalError, ProgrammingError, DataError
77

88

99
try:
@@ -186,49 +186,95 @@ def test_sql_queries(sentry_init, capture_events):
186186
@pytest.mark.django_db
187187
def test_sql_dict_query_params(sentry_init, capture_events):
188188
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
189-
from django.db import connection
190189

191-
sql = connection.cursor()
190+
from django.db import connections
191+
192+
if "postgres" not in connections:
193+
pytest.skip("postgres tests disabled")
194+
195+
sql = connections["postgres"].cursor()
192196

193197
events = capture_events()
194-
with pytest.raises(OperationalError):
195-
# This really only works with postgres. sqlite will crash with syntax error
198+
with pytest.raises(ProgrammingError):
196199
sql.execute(
197200
"""SELECT count(*) FROM people_person WHERE foo = %(my_foo)s""",
198201
{"my_foo": 10},
199202
)
200203

201204
capture_message("HI")
202-
203205
event, = events
204206

205207
crumb, = event["breadcrumbs"]
206208
assert crumb["message"] == ("SELECT count(*) FROM people_person WHERE foo = 10")
207209

208210

211+
@pytest.mark.parametrize(
212+
"query",
213+
[
214+
lambda sql: sql.SQL("SELECT %(my_param)s FROM {mytable}").format(
215+
mytable=sql.Identifier("foobar")
216+
),
217+
lambda sql: sql.SQL('SELECT %(my_param)s FROM "foobar"'),
218+
],
219+
)
209220
@pytest.mark.django_db
210-
def test_sql_psycopg2_string_composition(sentry_init, capture_events):
221+
def test_sql_psycopg2_string_composition(sentry_init, capture_events, query):
211222
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
212-
from django.db import connection
223+
from django.db import connections
213224

214-
psycopg2 = pytest.importorskip("psycopg2")
215-
psycopg2_sql = psycopg2.sql
225+
if "postgres" not in connections:
226+
pytest.skip("postgres tests disabled")
216227

217-
sql = connection.cursor()
228+
import psycopg2
229+
230+
sql = connections["postgres"].cursor()
218231

219232
events = capture_events()
220-
with pytest.raises(TypeError):
221-
# crashes because we use sqlite
222-
sql.execute(
223-
psycopg2_sql.SQL("SELECT %(my_param)s FROM people_person"), {"my_param": 10}
224-
)
233+
with pytest.raises(ProgrammingError):
234+
sql.execute(query(psycopg2.sql), {"my_param": 10})
225235

226236
capture_message("HI")
227237

228238
event, = events
229-
230239
crumb, = event["breadcrumbs"]
231-
assert crumb["message"] == ("SELECT 10 FROM people_person")
240+
assert crumb["message"] == ('SELECT 10 FROM "foobar"')
241+
242+
243+
@pytest.mark.django_db
244+
def test_sql_psycopg2_placeholders(sentry_init, capture_events):
245+
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
246+
from django.db import connections
247+
248+
if "postgres" not in connections:
249+
pytest.skip("postgres tests disabled")
250+
251+
import psycopg2
252+
253+
sql = connections["postgres"].cursor()
254+
255+
events = capture_events()
256+
with pytest.raises(DataError):
257+
names = ["foo", "bar"]
258+
identifiers = [psycopg2.sql.Identifier(name) for name in names]
259+
placeholders = [
260+
psycopg2.sql.Placeholder(var) for var in ["first_var", "second_var"]
261+
]
262+
sql.execute("create table my_test_table (foo text, bar date)")
263+
264+
query = psycopg2.sql.SQL("insert into my_test_table ({}) values ({})").format(
265+
psycopg2.sql.SQL(", ").join(identifiers),
266+
psycopg2.sql.SQL(", ").join(placeholders),
267+
)
268+
sql.execute(query, {"first_var": "fizz", "second_var": "not a date"})
269+
270+
capture_message("HI")
271+
272+
event, = events
273+
crumb1, crumb2 = event["breadcrumbs"]
274+
assert crumb1["message"] == ("create table my_test_table (foo text, bar date)")
275+
assert crumb2["message"] == (
276+
"""insert into my_test_table ("foo", "bar") values ('fizz', 'not a date')"""
277+
)
232278

233279

234280
@pytest.mark.django_db

tox.ini

+2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ passenv =
126126
SENTRY_PYTHON_TEST_AWS_ACCESS_KEY_ID
127127
SENTRY_PYTHON_TEST_AWS_SECRET_ACCESS_KEY
128128
SENTRY_PYTHON_TEST_AWS_IAM_ROLE
129+
SENTRY_PYTHON_TEST_POSTGRES_USER
130+
SENTRY_PYTHON_TEST_POSTGRES_NAME
129131
usedevelop = True
130132
extras =
131133
flask: flask

0 commit comments

Comments
 (0)