Skip to content

Materialize BLOBs in SQLAlchemy fetch path (#58)#90

Open
fdcastel wants to merge 1 commit intomainfrom
fix/issue-58-blobreader-streaming
Open

Materialize BLOBs in SQLAlchemy fetch path (#58)#90
fdcastel wants to merge 1 commit intomainfrom
fix/issue-58-blobreader-streaming

Conversation

@fdcastel
Copy link
Copy Markdown
Owner

@fdcastel fdcastel commented May 7, 2026

Summary

Fixes #58. Large BLOBs (default >64 KiB) were unreadable from SQLAlchemy because firebird-driver returns BlobReader objects past stream_blob_threshold, and SQLAlchemy's fetch lifecycle is incompatible with them:

  • SQLAlchemy fully consumes a cursor and then closes it before its result processors run. firebird-driver closes any associated BlobReader objects when the cursor closes, so any subsequent .read() fails.
  • Even with an open cursor, LargeBinary.result_processor calls bytes(value), which iterates a binary BlobReader line-by-line and raises InterfaceError: Can't read line from binary BLOB.

The dialect now sets cursor.stream_blob_threshold = sys.maxsize in do_execute, do_executemany, and do_execute_no_params, forcing the driver to materialize every BLOB as bytes / str. The user-level driver_config.stream_blob_threshold is left untouched, so applications that need streaming can still go through the raw DB-API cursor (as recommended by the SQLAlchemy maintainer in sqlalchemy/sqlalchemy#10549).

Test plan

  • New test_issue_58_large_blob covers a 200 KiB BLOB SUB_TYPE 0 and SUB_TYPE 1 round trip; verified that it fails on main (Can't read line from binary BLOB and BlobReader != str) and passes with the fix.
  • Full pytest --dburi $FIREBIRD_DBURI run (Firebird 5.0.4 / Python 3.14): 939 passed, 529 skipped, 0 failed.
  • ruff check and ruff format --check clean.

firebird-driver returns BlobReader objects for BLOBs larger than
stream_blob_threshold (default 64 KiB), but SQLAlchemy fully
consumes a cursor and closes it before its result processors run --
which closes the BlobReader objects too, leaving result rows
unreadable. Even on a still-open cursor, SQLAlchemy's LargeBinary
result processor calls bytes(value), which iterates a binary
BlobReader and crashes with "Can't read line from binary BLOB".

Override stream_blob_threshold to sys.maxsize on every cursor used
by the dialect so BLOBs are always returned as materialized
bytes/str. driver_config.stream_blob_threshold is left untouched;
applications that need streaming can still use the raw dbapi
cursor directly, as advised by the SQLAlchemy maintainer in
sqlalchemy/sqlalchemy#10549.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SqlAlchemy handling of firebird stream blobs (a.k.a. BlobReader objects)

1 participant