Accept memoryview parameters in the Rust value mapping

Synapse binds some binary values (e.g. slices of event data) as a `memoryview`
rather than `bytes`/`bytearray`. The shim's `from_py` handled the latter two but
not `memoryview`, so those parameters raised `TypeError: unsupported parameter
type for postgres: memoryview` — e.g. persisting an event over federation
(seen via `test_third_party_rules.test_on_new_event`).

Accept a `memoryview` as a BYTEA parameter, copying its bytes out with
`tobytes()` (the buffer protocol isn't available under the limited ABI the crate
builds against).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d
This commit is contained in:
Erik Johnston
2026-07-06 10:14:31 +00:00
co-authored by Claude Opus 4.8
parent 62f113f0a3
commit 57883548ce
+18 -1
View File
@@ -47,7 +47,9 @@ use postgres_protocol::types::{
int4_to_sql, int8_from_sql, int8_to_sql, text_to_sql, ArrayDimension,
};
use pyo3::exceptions::{PyTypeError, PyValueError};
use pyo3::types::{PyBool, PyByteArray, PyBytes, PyFloat, PyInt, PyList, PyString, PyTuple};
use pyo3::types::{
PyBool, PyByteArray, PyBytes, PyFloat, PyInt, PyList, PyMemoryView, PyString, PyTuple,
};
use pyo3::{prelude::*, BoundObject};
use tokio_postgres::types::{to_sql_checked, FromSql, IsNull, Kind, ToSql, Type, WrongType};
@@ -107,6 +109,14 @@ impl PgValue {
if let Ok(b) = obj.cast::<PyByteArray>() {
return Ok(PgValue::Bytea(b.to_vec().into()));
}
// A `memoryview` (most often over binary event data) is also bound as
// BYTEA. The buffer protocol isn't available under the limited ABI, so
// copy its bytes out via `tobytes()`.
if let Ok(mv) = obj.cast::<PyMemoryView>() {
let raw = mv.call_method0("tobytes")?;
let bytes = raw.cast::<PyBytes>()?;
return Ok(PgValue::Bytea(bytes.as_bytes().into()));
}
// A list is bound as a Postgres array (for `= ANY($1)` / `!= ALL($1)`).
// Each element is classified recursively.
if let Ok(list) = obj.cast::<PyList>() {
@@ -623,6 +633,13 @@ mod tests {
PgValue::Bytea(b) => assert_eq!(&*b, b"\x00\xff"),
other => panic!("expected Bytea, got {other:?}"),
}
// A `memoryview` over bytes binds as BYTEA via the buffer protocol.
let memoryview = py.eval(c"memoryview(b'\\x00\\xff')", None, None).unwrap();
match PgValue::from_py(&memoryview).unwrap() {
PgValue::Bytea(b) => assert_eq!(&*b, b"\x00\xff"),
other => panic!("expected Bytea, got {other:?}"),
}
});
}