Skip to content

add comment

47f5a6b
Select commit
Loading
Failed to load commit list.
Merged

feat(sqlalchemy): Support span streaming #6132

add comment
47f5a6b
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden completed Apr 28, 2026 in 2m 27s

5 issues

Medium

Streaming branch drops db.params/db.paramstyle/db.executemany/db.cursor span data - `sentry_sdk/tracing_utils.py:213-220`

In the has_span_streaming_enabled branch, the data dict is built (lines 198-207) and used for the breadcrumb, but is never attached to the span — only sentry.origin and sentry.op are passed as attributes. The non-streaming branch attaches all of data via span.set_data(k, v). As a result, when span streaming is enabled, SQL spans will lose db.params, db.paramstyle, db.executemany, and db.cursor attributes, which is a behavioral/backwards-compatibility regression in telemetry data.

Streaming SQL span drops db.params, db.paramstyle, db.executemany, and db.cursor attributes - `sentry_sdk/tracing_utils.py:213-220`

In the streaming branch, the data dict (db.params, db.paramstyle, db.executemany, db.cursor) is built but never attached to the span — only sentry.origin and sentry.op are set as attributes. The legacy branch (else) correctly applies them via span.set_data(k, v). As a consequence, when trace_lifecycle == 'stream' is enabled, SQL spans lose all parameter/cursor/executemany diagnostic information that users rely on for debugging, while the same instrumentation works on the non-streaming path. This is a behavioral regression that silently degrades observability on the streaming code path.

Low

Type annotation for `span` in `_handle_error` excludes `StreamedSpan` but code branches on it - `sentry_sdk/integrations/sqlalchemy.py:107-113`

On line 107, span is annotated as Optional[Span], yet the new branch on line 110 calls isinstance(span, StreamedSpan). Because _before_cursor_execute now stores either a Span or StreamedSpan in context._sentry_sql_span, the annotation is no longer accurate and is inconsistent with _after_cursor_execute (line 86), which correctly types the variable as Optional[Union[Span, StreamedSpan]]. This is only a type-hint inaccuracy — runtime behavior is correct — but it can mislead readers and static type checkers.

`` fallback is unreachable because _format_sql never returns None - `sentry_sdk/tracing_utils.py:214`

The streaming branch uses name="<unknown SQL query>" if query is None else query, but query is reassigned just above via query = _format_sql(cursor, query). Per _format_sql (line 498), the return is real_sql or to_string(sql), which always returns a string (never None). The fallback name therefore can never trigger, so spans generated from a None original description will be named "None" (from to_string(None)) instead of <unknown SQL query>, defeating the stated PR goal.

`` fallback is unreachable as written but masks empty-string queries - `sentry_sdk/tracing_utils.py:214`

query = _format_sql(cursor, query) returns real_sql or to_string(sql) (tracing_utils.py:513). to_string typically does not return None for non-None input, and even if sql is None, to_string(None) would yield the string 'None' rather than None. Therefore the "<unknown SQL query>" if query is None else query guard is essentially dead code for the documented inputs. More importantly, an empty string query ("") — a plausible degenerate input — would still be used as the span name instead of the fallback because the check is is None rather than a truthiness check, leading to spans named with an empty string.

4 skills analyzed
Skill Findings Duration Cost
code-review 3 1m 42s $2.43
find-bugs 2 1m 4s $2.98
skill-scanner 0 2m $1.96
security-review 0 2m 15s $1.97

Duration: 7m 2s · Tokens: 1.8M in / 12.2k out · Cost: $9.37 (+merge: $0.00, +dedup: $0.03)