Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,14 @@ def _row_to_event(
actions = None
if actions_val is not None:
try:
if isinstance(actions_val, bytes):
# The source rows are read with raw SQL, so SQLAlchemy has no column
# type to coerce with and whatever the driver produced for the binary
# column arrives here untouched. psycopg2 produces a memoryview rather
# than bytes, so match every bytes-like form instead of one driver's.
if isinstance(actions_val, (bytes, bytearray, memoryview)):
actions = _restricted_pickle_loads(
actions_val, allow_unsafe_unpickling=allow_unsafe_unpickling
bytes(actions_val),
allow_unsafe_unpickling=allow_unsafe_unpickling,
)
else: # for spanner - it might return object directly
actions = actions_val
Expand Down
26 changes: 26 additions & 0 deletions tests/unittests/sessions/migration/test_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,32 @@ def test_migrate_from_sqlalchemy_pickle_ignores_non_object_json_fields():
assert event.content is None


@pytest.mark.parametrize("as_binary", [bytes, bytearray, memoryview])
def test_migrate_from_sqlalchemy_pickle_reads_every_binary_column_type(
as_binary,
):
"""Pickled actions must survive whichever binary type the driver returns.

Events are read with raw SQL, so SQLAlchemy has no column type to coerce
with and the driver's own representation reaches the migration: psycopg2
returns a memoryview rather than bytes. Treating that as "some other
backend handed us an object" replaced the actions with an empty one while
the migration still reported success.
"""
actions = EventActions(state_delta={"skey": 4}, escalate=True)

event = mfsp._row_to_event({
"id": "event-binary-actions",
"invocation_id": "invoke1",
"author": "user",
"timestamp": datetime(2026, 1, 1, tzinfo=timezone.utc),
"actions": as_binary(pickle.dumps(actions)),
})

assert event.actions.state_delta == {"skey": 4}
assert event.actions.escalate is True


def test_migrate_from_sqlalchemy_pickle_blocks_unsafe_actions_pickle(
tmp_path, monkeypatch
):
Expand Down