Skip to content

Commit 9251b26

Browse files
committed
Merge branch 'master' into antonpirker/django-db-improvements
2 parents 9d46f9b + 3ef02a1 commit 9251b26

File tree

2 files changed

+65
-13
lines changed

2 files changed

+65
-13
lines changed

sentry_sdk/integrations/clickhouse_driver.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
# without introducing a hard dependency on `typing_extensions`
1212
# from: https://stackoverflow.com/a/71944042/300572
1313
if TYPE_CHECKING:
14-
from typing import ParamSpec, Callable
14+
from collections.abc import Iterator
15+
from typing import Any, ParamSpec, Callable
1516
else:
1617
# Fake ParamSpec
1718
class ParamSpec:
@@ -49,9 +50,7 @@ def setup_once() -> None:
4950
)
5051

5152
# If the query contains parameters then the send_data function is used to send those parameters to clickhouse
52-
clickhouse_driver.client.Client.send_data = _wrap_send_data(
53-
clickhouse_driver.client.Client.send_data
54-
)
53+
_wrap_send_data()
5554

5655
# Every query ends either with the Client's `receive_end_of_query` (no result expected)
5756
# or its `receive_result` (result expected)
@@ -128,23 +127,44 @@ def _inner_end(*args: P.args, **kwargs: P.kwargs) -> T:
128127
return _inner_end
129128

130129

131-
def _wrap_send_data(f: Callable[P, T]) -> Callable[P, T]:
132-
def _inner_send_data(*args: P.args, **kwargs: P.kwargs) -> T:
133-
instance = args[0] # type: clickhouse_driver.client.Client
134-
data = args[2]
135-
span = getattr(instance.connection, "_sentry_span", None)
130+
def _wrap_send_data() -> None:
131+
original_send_data = clickhouse_driver.client.Client.send_data
132+
133+
def _inner_send_data( # type: ignore[no-untyped-def] # clickhouse-driver does not type send_data
134+
self, sample_block, data, types_check=False, columnar=False, *args, **kwargs
135+
):
136+
span = getattr(self.connection, "_sentry_span", None)
136137

137138
if span is not None:
138-
_set_db_data(span, instance.connection)
139+
_set_db_data(span, self.connection)
139140

140141
if should_send_default_pii():
141142
db_params = span._data.get("db.params", [])
142-
db_params.extend(data)
143+
144+
if isinstance(data, (list, tuple)):
145+
db_params.extend(data)
146+
147+
else: # data is a generic iterator
148+
orig_data = data
149+
150+
# Wrap the generator to add items to db.params as they are yielded.
151+
# This allows us to send the params to Sentry without needing to allocate
152+
# memory for the entire generator at once.
153+
def wrapped_generator() -> "Iterator[Any]":
154+
for item in orig_data:
155+
db_params.append(item)
156+
yield item
157+
158+
# Replace the original iterator with the wrapped one.
159+
data = wrapped_generator()
160+
143161
span.set_data("db.params", db_params)
144162

145-
return f(*args, **kwargs)
163+
return original_send_data(
164+
self, sample_block, data, types_check, columnar, *args, **kwargs
165+
)
146166

147-
return _inner_send_data
167+
clickhouse_driver.client.Client.send_data = _inner_send_data
148168

149169

150170
def _set_db_data(

tests/integrations/clickhouse_driver/test_clickhouse_driver.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,38 @@ def test_clickhouse_client_spans(
342342
assert event["spans"] == expected_spans
343343

344344

345+
def test_clickhouse_spans_with_generator(sentry_init, capture_events):
346+
sentry_init(
347+
integrations=[ClickhouseDriverIntegration()],
348+
send_default_pii=True,
349+
traces_sample_rate=1.0,
350+
)
351+
events = capture_events()
352+
353+
# Use a generator to test that the integration obtains values from the generator,
354+
# without consuming the generator.
355+
values = ({"x": i} for i in range(3))
356+
357+
with start_transaction(name="test_clickhouse_transaction"):
358+
client = Client("localhost")
359+
client.execute("DROP TABLE IF EXISTS test")
360+
client.execute("CREATE TABLE test (x Int32) ENGINE = Memory")
361+
client.execute("INSERT INTO test (x) VALUES", values)
362+
res = client.execute("SELECT x FROM test")
363+
364+
# Verify that the integration did not consume the generator
365+
assert res == [(0,), (1,), (2,)]
366+
367+
(event,) = events
368+
spans = event["spans"]
369+
370+
[span] = [
371+
span for span in spans if span["description"] == "INSERT INTO test (x) VALUES"
372+
]
373+
374+
assert span["data"]["db.params"] == [{"x": 0}, {"x": 1}, {"x": 2}]
375+
376+
345377
def test_clickhouse_client_spans_with_pii(
346378
sentry_init, capture_events, capture_envelopes
347379
) -> None:

0 commit comments

Comments
 (0)