feat(sqlalchemy): Support span streaming #6132
8 issues
High
Breaking API change: render_span_tree signature change breaks existing callers - `tests/conftest.py:479`
The render_span_tree fixture signature changed from inner(event) (accepting a full transaction event dict) to inner(spans, root_span=None) (accepting a spans list). Existing callers in test_basic.py (lines 460, 599, 957, 1037, etc.) pass a full event dict like render_span_tree(transaction) which will now be incorrectly interpreted as a spans list, causing iteration over dict keys instead of spans and runtime errors.
Medium
Query source information lost when span streaming is enabled - `sentry_sdk/tracing_utils.py:164-174`
When span streaming is enabled, record_sql_queries yields a StreamedSpan. The span's _end() method (called via __exit__) captures and sends the span to Sentry immediately. However, add_query_source() is called AFTER the span context manager exits in the SQLAlchemy integration. This means any attributes set by add_query_source() (like code.line.number, code.file.path, code.function.name) will be lost because the span has already been transmitted.
Low
Debug print statement left in test fixture - `tests/conftest.py:487`
A print(span) statement was added for debugging purposes but should be removed before merging. Debug print statements in test fixtures can clutter test output and indicate incomplete cleanup before PR submission.
Missing SERVER_ADDRESS assertion in span streaming test branch - `tests/integrations/sqlalchemy/test_sqlalchemy.py:162`
In test_transactions, the span streaming branch (line 162) only asserts SPANDATA.SERVER_PORT not in span["attributes"], but the static branch (lines 203-204) also asserts SPANDATA.SERVER_ADDRESS not in span["data"]. This test coverage gap means the span streaming path doesn't verify that SERVER_ADDRESS is absent, potentially masking a behavioral difference between the two modes.
Also found at:
tests/integrations/sqlalchemy/test_sqlalchemy.py:293
Dead code: inner span_streaming else-branch never executes in if-block - `tests/integrations/sqlalchemy/test_sqlalchemy.py:931-933`
Inside the if span_streaming: block (line 901), the fake_record_sql_queries class contains an if span_streaming: check (lines 928-933). Since we're already inside the outer if span_streaming: block, span_streaming is always True, making lines 931-933 (else: self.span.start_timestamp = ...) dead code that will never execute.
Also found at:
tests/integrations/sqlalchemy/test_sqlalchemy.py:989-991
Debug print statement left in production test code - `tests/conftest.py:486`
Line 486 contains print(span) which appears to be a debug statement accidentally left in. This will output noise to stdout during every test that uses the render_span_tree fixture, potentially cluttering test output and making actual test failures harder to identify.
Missing SERVER_ADDRESS assertion in span_streaming test branch - `tests/integrations/sqlalchemy/test_sqlalchemy.py:162`
In test_transactions, the span_streaming branch at line 162 only asserts SPANDATA.SERVER_PORT not in span["attributes"], while the non-streaming branch at lines 203-204 asserts both SERVER_ADDRESS and SERVER_PORT. This test coverage gap means the span_streaming mode might emit SERVER_ADDRESS when it shouldn't (for sqlite:///:memory: which has no host), and this bug would go undetected.
Test asserts on wrong span - accesses spans[0] without filtering for SQLAlchemy spans - `tests/integrations/sqlalchemy/test_sqlalchemy.py:344-346`
In test_long_sql_query_preserved when span_streaming=True, the code accesses spans[0] assuming it's the SQL query span. However, capture_items("span") captures all spans including the parent "custom parent" span. Unlike test_transactions which filters for sqlalchemy_spans using sentry.origin attribute, this test directly accesses spans[0]. The test may pass by accident due to span ordering, but the assertion is on the wrong span.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 4 | 8m 20s | $3.41 |
| find-bugs | 4 | 6m 48s | $6.18 |
| skill-scanner | 0 | 7m 11s | $1.01 |
| security-review | 0 | 5m 33s | $1.19 |
Duration: 27m 53s · Tokens: 7.7M in / 80.6k out · Cost: $11.82 (+extraction: $0.01, +merge: $0.00, +fix_gate: $0.02, +dedup: $0.01)