Skip to content
Merged
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
21 changes: 8 additions & 13 deletions src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2354,13 +2354,13 @@ impl From<TxidSnapshotOrPgSnapshot> for JsonValue {
}

/// A wire format that's just an opaque byte blob with no meaningful text
/// representation — encoded the same way this plugin's existing `BYTEA`
/// arm does (see `extract_simple_kind`'s `Type::BYTEA` arm): the full
/// base64 payload with a hardcoded `application/octet-stream` MIME type,
/// no truncation. Matches the builtin's `binary_wrapper!` macro
/// (`extract/advanced_types.rs`), used for internal planner-statistics
/// types too rarely queried directly to be worth a real text
/// representation.
/// representation — encoded via the same truncated-preview `encode_blob`
/// this plugin's `Type::BYTEA` arm uses (see `crate::utils::blob`).
/// Matches the builtin's `binary_wrapper!` macro (`extract/advanced_types.rs`),
/// which calls the builtin's own `encode_blob` for the same reason: these
/// are internal planner-statistics types too rarely queried directly to be
/// worth a real text representation, but a large one should still be capped
/// like any other BYTEA read.
macro_rules! binary_blob_wrapper {
($name:ident, $pg_type:ident) => {
pub(crate) struct $name(Vec<u8>);
Expand All @@ -2380,12 +2380,7 @@ macro_rules! binary_blob_wrapper {

impl From<$name> for JsonValue {
fn from(v: $name) -> Self {
let b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &v.0);
JsonValue::String(format!(
"BLOB:{}:application/octet-stream:{}",
v.0.len(),
b64
))
JsonValue::String(crate::utils::blob::encode_blob(&v.0))
}
}
};
Expand Down
33 changes: 33 additions & 0 deletions src/extract_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,39 @@ fn internal_statistics_blob_types_decode_and_accept_correctly() {
));
}

#[test]
fn internal_statistics_blob_types_truncate_a_large_buffer_to_the_preview_cap() {
// #106: binary_blob_wrapper! had its own untruncated copy of the BLOB:
// encoding, independently drifted from the Type::BYTEA arm #87 fixed.
// A large value for any of these internal-stats types must also be
// capped at MAX_BLOB_PREVIEW_SIZE, with the header still reporting the
// true size -- same contract as an ordinary BYTEA column.
let large_buf = vec![0x37u8; 20 * 1024]; // 20 KB, over the 10 KB cap

let v = PgNdistinct::from_sql(&Type::PG_NDISTINCT, &large_buf).unwrap();
let serde_json::Value::String(wire) = serde_json::Value::from(v) else {
panic!("expected a string");
};
let header_prefix = format!("BLOB:{}:", large_buf.len());
assert!(
wire.starts_with(&header_prefix),
"header must report the true size (20480), not the truncated preview size: {wire}"
);
let mime_and_b64 = &wire[header_prefix.len()..];
let (_, b64_payload) = mime_and_b64.split_once(':').unwrap();
let decoded =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64_payload).unwrap();
assert_eq!(
decoded.len(),
crate::utils::blob::MAX_BLOB_PREVIEW_SIZE,
"base64 payload must be truncated to the preview cap, not the full 20480 bytes"
);
assert_eq!(
decoded,
large_buf[..crate::utils::blob::MAX_BLOB_PREVIEW_SIZE]
);
}

#[test]
fn tsvector_array_decodes_each_element() {
let ty = array_type(Type::TS_VECTOR);
Expand Down
Loading