fix: prevent hang when store-configured query returns zero rows#59
Conversation
When a query configured with Store returns an empty result set, the server correctly responds with state=succeeded and result_uri=None. Previously, the driver misinterpreted this as a non-store query and called __request_results(), which returned null results that were silently dropped — leaving the cursor's queue.get() blocking forever. Two fixes applied: 1. Primary fix: In the SUCCEEDED+STATE_UPDATED path, check query.store before falling through to __request_results(). When store is configured but result_uri is falsy, immediately complete with an empty ExecutionResult. 2. Defensive fix: In the execution_result handler, when results is falsy or not a dict, deliver an empty ExecutionResult instead of silently returning. This ensures the cursor is always unblocked. Also adds comprehensive tests covering empty store results, normal store results, non-store queries, null execution results, and empty dict execution results.
[Arch] Architectural Review — ✅ APPROVEDI reviewed this PR against the architecture doc ( Layer 1 (Primary Fix) ✅ — The Layer 2 (Defensive Fix) ✅ — The null/empty results handler in the Option C (No server changes) ✅ — Correctly client-only fix as designed. The fix relies on local state ( Component boundaries ✅ — No new abstractions, no new dependencies, no changes to the Future extensibility ✅ — The fix is additive (12 lines of production code) and doesn't constrain any future protocol changes. The defensive layer protects against future edge cases beyond the immediate bug. Test coverage ✅ — 5 well-structured tests covering all three paths (empty store, normal store, non-store fallback) plus defensive cases (null results, empty dict results). Tests operate at the right level of abstraction by injecting messages into No architectural concerns. This is ready to merge from an architecture perspective (pending pre-commit formatting fix). |
[Design] Spec Compliance Review✅ Primary Fix —
|
james-willis
left a comment
There was a problem hiding this comment.
[Design] Spec compliance review complete. Implementation matches the design spec precisely. One CI fix needed: remove unused imports in tests/test_empty_store_results.py (see PR comment for details). Will approve once that's addressed.
Summary
cursor.get_store_result()(andfetchall()) hangs indefinitely when a store-configured query returns zero rowsstate=succeededwithresult_uri=Nonefor empty store results, but the driver misinterpreted this as a non-store query and fell into a code path that silently dropped the response — leavingcursor.__queue.get()blocking foreverDesign Docs
docs/architecture/fix-empty-store-results-hang.mddocs/design/fix-empty-store-results-hang.mdChanges
connection.py,__listen()): Addedif query.store is not None:check after theif result_uri:block in theSUCCEEDED+STATE_UPDATEDpath. When store is configured butresult_uriis falsy (empty result set), immediately marks query as COMPLETED and delivers an emptyExecutionResult()instead of calling__request_results()connection.py,__listen()): In theexecution_resulthandler, whenresultsis falsy or not a dict, now marks query as COMPLETED and delivers an emptyExecutionResult()instead of silently returning — ensures the cursor is always unblockedtests/test_empty_store_results.py): 5 tests covering empty store results, normal store results, non-store query fallback, null execution results, and empty dict execution results0.25.0→0.25.1Testing
pytest tests/ -v— all 19 tests pass (14 existing + 5 new)unittest.mockto mock the WebSocket and test__listen()behavior by injecting messages and verifying the handler receives the correctExecutionResultDeviations from Design
None