Skip to content

Commit 7de670a

Browse files
committed
wip
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 2d7ea6f commit 7de670a

5 files changed

Lines changed: 183 additions & 38 deletions

File tree

encodings/parquet-variant/src/vtable.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ mod tests {
309309
use vortex_array::Canonical;
310310
use vortex_array::EqMode;
311311
use vortex_array::IntoArray;
312-
use vortex_array::VTable;
313312
use vortex_array::VortexSessionExecute;
314313
use vortex_array::arrays::PrimitiveArray;
315314
use vortex_array::arrays::VarBinViewArray;
@@ -400,11 +399,7 @@ mod tests {
400399

401400
#[fixture]
402401
fn write_strategy() -> Arc<dyn LayoutStrategy> {
403-
let mut allowed = vortex_file::ALLOWED_ENCODINGS.clone();
404-
allowed.insert(ParquetVariant.id());
405-
vortex_file::WriteStrategyBuilder::default()
406-
.with_allow_encodings(allowed)
407-
.build()
402+
vortex_file::WriteStrategyBuilder::default().build()
408403
}
409404

410405
#[test]

vortex-edition/src/session.rs

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4-
//! The [`EditionSession`] session variable: the per-session registry of editions and
5-
//! edition inclusions.
4+
//! Session variables for registered and enabled editions.
65
76
use std::any::Any;
87
use std::collections::BTreeMap;
@@ -41,6 +40,28 @@ struct Inner {
4140
inclusions: BTreeMap<Id, EditionInclusion>,
4241
}
4342

43+
/// The editions enabled for writing in a session.
44+
///
45+
/// At most one edition is enabled per family. Enabling a newer or older edition from the
46+
/// same family replaces the previous selection. This is separate from [`EditionSession`]:
47+
/// registration describes what a session knows how to reason about, while enabling is the
48+
/// explicit writer policy.
49+
#[derive(Clone, Debug, Default)]
50+
pub struct EnabledEditions {
51+
inner: Arc<RwLock<BTreeMap<&'static str, EditionId>>>,
52+
}
53+
54+
impl EnabledEditions {
55+
/// Return the enabled editions, sorted by family.
56+
pub fn editions(&self) -> Vec<EditionId> {
57+
self.inner.read().values().copied().collect()
58+
}
59+
60+
fn enable(&self, edition: EditionId) {
61+
self.inner.write().insert(edition.family, edition);
62+
}
63+
}
64+
4465
impl EditionSession {
4566
/// Create a session variable with no declarations.
4667
pub fn empty() -> Self {
@@ -190,12 +211,71 @@ impl SessionVar for EditionSession {
190211
}
191212
}
192213

214+
impl SessionVar for EnabledEditions {
215+
fn as_any(&self) -> &dyn Any {
216+
self
217+
}
218+
219+
fn as_any_mut(&mut self) -> &mut dyn Any {
220+
self
221+
}
222+
}
223+
193224
/// Session data for Vortex editions.
194225
pub trait EditionSessionExt: SessionExt {
195226
/// Returns the edition registry.
196227
fn editions(&self) -> SessionGuard<'_, EditionSession> {
197228
self.get::<EditionSession>()
198229
}
230+
231+
/// Returns the editions enabled for writing.
232+
///
233+
/// Accessing this method installs the enabled-editions session variable if it is absent,
234+
/// opting the session into edition-gated writing with an initially empty selection.
235+
fn enabled_editions(&self) -> SessionGuard<'_, EnabledEditions> {
236+
self.get::<EnabledEditions>()
237+
}
238+
239+
/// Register an edition declaration with this session.
240+
fn register_edition(&self, declaration: &EditionDeclaration) -> Result<(), EditionError> {
241+
self.editions().declare(declaration)
242+
}
243+
244+
/// Enable a registered edition for writing.
245+
///
246+
/// Enabling an edition replaces the enabled edition from the same family. An edition
247+
/// must be registered first so a typo or unavailable third-party declaration cannot
248+
/// silently produce an empty writable set.
249+
fn enable_edition(&self, edition: EditionId) -> Result<(), EditionError> {
250+
if self.editions().find(&edition).is_none() {
251+
return Err(EditionError::new(format!(
252+
"cannot enable unregistered edition {edition}"
253+
)));
254+
}
255+
self.enabled_editions().enable(edition);
256+
Ok(())
257+
}
258+
259+
/// Resolve the encodings in all enabled editions.
260+
///
261+
/// Returns `None` when the enabled-editions variable is absent, which is the explicit
262+
/// opt-out used by lower-level sessions. Once the variable is present, an empty enabled
263+
/// set resolves to `Some(Vec::new())` and therefore permits no edition encodings.
264+
fn enabled_encoding_ids(&self) -> Vec<Id> {
265+
let Some(enabled) = self.get_opt::<EnabledEditions>() else {
266+
vec![]
267+
};
268+
let editions = self.editions();
269+
let mut ids: Vec<Id> = enabled
270+
.editions()
271+
.iter()
272+
.flat_map(|edition| editions.encodings_in(edition))
273+
.map(|inclusion| inclusion.encoding_id)
274+
.collect();
275+
ids.sort_unstable();
276+
ids.dedup();
277+
ids
278+
}
199279
}
200280

201281
impl<S: SessionExt> EditionSessionExt for S {}

vortex-file/src/writer.rs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@ use futures::pin_mut;
1717
use futures::select;
1818
use itertools::Itertools;
1919
use vortex_array::ArrayContext;
20+
use vortex_array::ArrayId;
2021
use vortex_array::ArrayRef;
2122
use vortex_array::dtype::DType;
2223
use vortex_array::dtype::FieldPath;
2324
use vortex_array::expr::stats::Stat;
2425
use vortex_array::iter::ArrayIterator;
2526
use vortex_array::iter::ArrayIteratorExt;
27+
use vortex_array::session::ArrayRegistry;
2628
use vortex_array::session::ArraySessionExt;
2729
use vortex_array::stats::PRUNING_STATS;
2830
use vortex_array::stream::ArrayStream;
2931
use vortex_array::stream::ArrayStreamAdapter;
3032
use vortex_array::stream::ArrayStreamExt;
3133
use vortex_array::stream::SendableArrayStream;
3234
use vortex_buffer::ByteBuffer;
35+
use vortex_edition::EditionSessionExt;
3336
use vortex_error::VortexError;
3437
use vortex_error::VortexExpect;
3538
use vortex_error::VortexResult;
@@ -49,7 +52,6 @@ use vortex_session::SessionExt;
4952
use vortex_session::VortexSession;
5053
use vortex_session::registry::ReadContext;
5154

52-
use crate::ALLOWED_ENCODINGS;
5355
use crate::Footer;
5456
use crate::MAGIC_BYTES;
5557
use crate::WriteStrategyBuilder;
@@ -158,14 +160,14 @@ impl VortexWriteOptions {
158160
mut write: W,
159161
stream: SendableArrayStream,
160162
) -> VortexResult<WriteSummary> {
161-
// NOTE(os): Setup an array context that already has all known encodings pre-populated.
162-
// This is preferred for now over having an empty context here, because only the
163-
// serialised array order is deterministic. The serialisation of arrays are done
164-
// parallel and with an empty context they can register their encodings to the context
165-
// in different order, changing the written bytes from run to run.
166-
let ctx = ArrayContext::new(ALLOWED_ENCODINGS.iter().cloned().sorted().collect())
167-
// Configure a registry just to ensure only known encodings are interned.
168-
.with_registry(self.session.arrays().registry().clone());
163+
// Pre-populate the array context with the registered encodings selected by the enabled
164+
// editions. This keeps the serialized ordering deterministic without advertising every
165+
// encoding the session happens to know how to read.
166+
let array_registry = self.session.arrays().registry().clone();
167+
let ctx = ArrayContext::new(self.session.enabled_encoding_ids())
168+
// The registry supplies serialization implementations; the layout strategy applies
169+
// the enabled-edition write policy before arrays reach serialization.
170+
.with_registry(array_registry);
169171
let dtype = stream.dtype().clone();
170172

171173
let (mut ptr, eof) = SequenceId::root().split();
@@ -532,3 +534,41 @@ impl WriteSummary {
532534
.collect())
533535
}
534536
}
537+
538+
#[cfg(test)]
539+
mod tests {
540+
use vortex_array::VTable;
541+
use vortex_array::array_session;
542+
use vortex_array::arrays::Bool;
543+
use vortex_array::arrays::Primitive;
544+
use vortex_array::session::ArraySessionExt;
545+
use vortex_edition::Edition;
546+
use vortex_edition::EditionDeclaration;
547+
use vortex_edition::EditionId;
548+
use vortex_edition::EditionSession;
549+
use vortex_edition::EditionSessionExt;
550+
551+
use super::initial_array_ids;
552+
553+
#[test]
554+
fn initial_array_ids_are_registered_and_enabled() -> Result<(), vortex_edition::EditionError> {
555+
const EDITION: EditionId = EditionId::new("test", 2026, 7, 0);
556+
static DECLARATION: EditionDeclaration = EditionDeclaration {
557+
edition: Edition {
558+
id: EDITION,
559+
min_vortex_version: None,
560+
},
561+
added: &[&"vortex.primitive", &"test.not_registered"],
562+
};
563+
564+
let session = array_session().with::<EditionSession>();
565+
session.register_edition(&DECLARATION)?;
566+
session.enable_edition(EDITION)?;
567+
568+
let registry = session.arrays().registry().clone();
569+
let ids = initial_array_ids(&session, &registry);
570+
assert_eq!(ids, [Primitive.id()]);
571+
assert!(!ids.contains(&Bool.id()));
572+
Ok(())
573+
}
574+
}

vortex-jni/src/writer.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use jni::sys::jlong;
3232
use jni::sys::jobject;
3333
use object_store::ObjectStore;
3434
use object_store::path::Path as ObjectStorePath;
35+
use vortex::array::ArrayId;
3536
use vortex::array::ArrayRef;
3637
use vortex::array::VTable;
3738
use vortex::array::scalar::PValue;
@@ -42,6 +43,7 @@ use vortex::array::stream::ArrayStreamAdapter;
4243
use vortex::dtype::DType;
4344
use vortex::dtype::Field as DTypeField;
4445
use vortex::dtype::FieldPath;
46+
use vortex::editions::EditionSessionExt;
4547
use vortex::error::VortexError;
4648
use vortex::error::VortexResult;
4749
use vortex::error::vortex_err;
@@ -60,6 +62,7 @@ use vortex::io::runtime::Task;
6062
use vortex::io::session::RuntimeSessionExt;
6163
use vortex::session::VortexSession;
6264
use vortex::utils::aliases::hash_map::HashMap;
65+
use vortex::utils::aliases::hash_set::HashSet;
6366
use vortex_arrow::ArrowSessionExt;
6467
use vortex_parquet_variant::ParquetVariant;
6568

@@ -108,7 +111,11 @@ fn write_options_for_schema(
108111
return session.write_options();
109112
}
110113

111-
let mut allowed = vortex::file::ALLOWED_ENCODINGS.clone();
114+
let mut allowed: HashSet<ArrayId> = session
115+
.enabled_encoding_ids()
116+
.unwrap_or_default()
117+
.into_iter()
118+
.collect();
112119
allowed.insert(ParquetVariant.id());
113120

114121
let strategy = WriteStrategyBuilder::default().with_allow_encodings(allowed);

vortex/src/editions/mod.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,27 @@
33

44
//! The Vortex edition declarations.
55
//!
6-
//! [`vortex_edition`] provides the types, the [`crate::editions::EditionSession`] session
7-
//! variable, and the test harness; the actual declarations live here, one module per edition
8-
//! (`editions::<family>::<date>`), and are seeded into the default session by
9-
//! [`crate::editions::register_default_editions`].
6+
//! [`vortex_edition`] provides the types, session variables, and test harness. The actual
7+
//! first-party declarations live here, one module per edition. The default session first
8+
//! registers them with [`register_default_editions`] and then selects its write policy with
9+
//! [`enable_default_editions`].
1010
//!
11-
//! Each edition module declares the edition together with the encodings that join the
12-
//! family at it; members of earlier editions are inherited and never restated. Correctness
13-
//! is enforced by unit tests: every edition module calls
14-
//! [`vortex_edition::test_harness::validate_edition`] once from its `#[cfg(test)]` module,
15-
//! and the computed set of a frozen edition is pinned by a golden test, so any change to
16-
//! these declarations that alters a frozen set fails CI. New encodings are staged into the
17-
//! newest draft edition.
18-
//!
19-
//! Note this is currently unused but a future PR will make this public and gate the writer behind
20-
//! editions.
11+
//! The default file writer resolves the session's enabled editions at write time. The
12+
//! facade enables the newest frozen `core` edition, [`CORE_2026_07_0`], and additionally
13+
//! enables the latest unstable edition when the `unstable_encodings` feature is selected.
2114
2215
pub mod core;
16+
#[cfg(test)]
17+
mod tests;
2318
pub mod unstable;
2419

25-
use vortex_edition::EditionDeclaration;
20+
pub use vortex_edition::Edition;
21+
pub use vortex_edition::EditionDeclaration;
22+
pub use vortex_edition::EditionId;
23+
pub use vortex_edition::EditionInclusion;
2624
pub use vortex_edition::EditionSession;
27-
use vortex_edition::EditionSessionExt;
25+
pub use vortex_edition::EditionSessionExt;
26+
pub use vortex_edition::EnabledEditions;
2827
use vortex_error::VortexExpect;
2928
use vortex_error::vortex_err;
3029
use vortex_session::VortexSession;
@@ -38,7 +37,14 @@ pub use self::unstable::UNSTABLE_2026_02_0;
3837
pub use self::unstable::UNSTABLE_2026_04_0;
3938
pub use self::unstable::UNSTABLE_2026_06_0;
4039

41-
/// The Vortex editions, each declared together with the encodings it adds.
40+
/// The `core` edition enabled for writing by the default Vortex session.
41+
pub const DEFAULT_CORE_EDITION: EditionId = CORE_2026_07_0;
42+
43+
/// The `unstable` edition enabled for writing by the default Vortex session when the
44+
/// `unstable_encodings` feature is selected.
45+
pub const DEFAULT_UNSTABLE_EDITION: EditionId = UNSTABLE_2026_06_0;
46+
47+
/// The first-party Vortex edition declarations.
4248
pub static EDITION_DECLARATIONS: &[&EditionDeclaration] = &[
4349
&core::v2025_05::DECLARATION,
4450
&core::v2025_06::DECLARATION,
@@ -52,11 +58,28 @@ pub static EDITION_DECLARATIONS: &[&EditionDeclaration] = &[
5258

5359
/// Register the Vortex edition declarations with the session's [`EditionSession`].
5460
pub fn register_default_editions(session: &VortexSession) {
55-
let editions = session.editions();
5661
for declaration in EDITION_DECLARATIONS {
57-
editions
58-
.declare(declaration)
62+
session
63+
.register_edition(declaration)
5964
.map_err(|e| vortex_err!("{e}"))
6065
.vortex_expect("edition declarations are valid");
6166
}
6267
}
68+
69+
/// Enable the default Vortex editions for writing.
70+
///
71+
/// This selects the newest frozen `core` edition and, when configured, the newest unstable
72+
/// edition. All declarations must have been registered first with
73+
/// [`register_default_editions`].
74+
pub fn enable_default_editions(session: &VortexSession) {
75+
session
76+
.enable_edition(DEFAULT_CORE_EDITION)
77+
.map_err(|e| vortex_err!("{e}"))
78+
.vortex_expect("default core edition is registered");
79+
80+
#[cfg(feature = "unstable_encodings")]
81+
session
82+
.enable_edition(DEFAULT_UNSTABLE_EDITION)
83+
.map_err(|e| vortex_err!("{e}"))
84+
.vortex_expect("default unstable edition is registered");
85+
}

0 commit comments

Comments
 (0)