diff --git a/vortex-file/src/writer.rs b/vortex-file/src/writer.rs index 6c0d01dfa05..874d08be306 100644 --- a/vortex-file/src/writer.rs +++ b/vortex-file/src/writer.rs @@ -391,16 +391,25 @@ fn new_array_context(session: &VortexSession, enforce_editions: bool) -> ArrayCo // serialised array order is deterministic. The serialisation of arrays are done // parallel and with an empty context they can register their encodings to the context // in different order, changing the written bytes from run to run. - // - // The seeded IDs are also what the writer may emit: callers read them back with - // `ArrayContext::to_ids` to restrict compression to the same set. + + // The registry keys are exactly the serialized IDs that can be produced by the plugins + // registered in the session. Intersected with the serialized IDs the enabled editions permit, + // this is the set of IDs that can be written. Only seed the array context with these IDs, and + // only allow compressor schemes that produce arrays with serialized IDs within this set. + let registered: HashSet = session + .arrays() + .registry() + .read(|registry| registry.keys().copied().collect()); let serialized_ids: Vec = if enforce_editions { - session.enabled_component_ids(ComponentKind::Array) - } else { + // Must filter by the set of serialized IDs supported by registered plugins. Otherwise, + // we could enable an ID that will never be written. session - .arrays() - .registry() - .read(|registry| registry.keys().copied().collect()) + .enabled_component_ids(ComponentKind::Array) + .into_iter() + .filter(|id| registered.contains(id)) + .collect() + } else { + registered.into_iter().collect() }; let array_ctx = ArrayContext::new(serialized_ids.iter().copied().sorted().collect()); if enforce_editions { @@ -764,6 +773,7 @@ mod tests { use vortex_edition::EditionMember; use vortex_edition::EditionSession; use vortex_edition::EditionSessionExt; + use vortex_session::registry::CachedId; use super::*; @@ -788,6 +798,33 @@ mod tests { Ok(()) } + /// An edition may enable an encoding whose plugin is not registered on the session. Nothing + /// could serialize it, so it is neither seeded into the table nor offered to the compressor. + #[test] + fn array_context_skips_enabled_but_unregistered_encodings() -> VortexResult<()> { + const EDITION: EditionId = EditionId::new("test", 2026, 9, 0); + static DECLARATION: EditionDeclaration = EditionDeclaration { + edition: Edition { + id: EDITION, + min_library_version: None, + }, + added: &[ + EditionMember::array(&"vortex.primitive"), + EditionMember::array(&"vortex.alp"), + ], + }; + + let session = array_session().with::(); + session.register_edition(&DECLARATION)?; + session.enable_edition(EDITION)?; + + let ctx = new_array_context(&session, true); + assert_eq!(ctx.to_ids(), [Primitive.id()]); + static ALP: CachedId = CachedId::new("vortex.alp"); + assert!(ctx.intern(&ALP).is_none()); + Ok(()) + } + #[test] fn disabling_editions_allows_all_registered_array_ids() { let session = array_session();