diff --git a/rust/src/database/postgres/value.rs b/rust/src/database/postgres/value.rs index e03f20c319..178e2dbec6 100644 --- a/rust/src/database/postgres/value.rs +++ b/rust/src/database/postgres/value.rs @@ -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::() { 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::() { + let raw = mv.call_method0("tobytes")?; + let bytes = raw.cast::()?; + 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::() { @@ -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:?}"), + } }); }