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
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ fn main() {
let f_other = t2.seal();
let foreign = table.map(&f_other, 0);
let foreign_ok = matches!(foreign, Err(MapRead::Stale(FrameRefusal::ForeignFrame)));
let future = table.map_at(RouteVersion(table.current_version().0 + 7), 0);
let future = table.diagnose_at(RouteVersion(table.current_version().0 + 7), 0);
let future_ok = matches!(future, Err(MapRead::FutureVersion { .. }));
let now = table.seal();
let ambiguous = y_sh.map(|g| table.locate(&now, &[10, 11], g));
Expand Down
60 changes: 54 additions & 6 deletions crates/lance-graph-dt64/src/dr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ pub struct RouteVersion(pub u64);
/// The one counter behind [`RouteVersion`]. A [`RouteLog`], a [`FrameStore`]
/// and a [`MapTable`] built from clones of the same clock advance ONE
/// sequence: a version then names a single point of the DR log, and a frame's
/// version answers `RouteLog::routed_at` as well as `MapTable::map_at`.
/// version answers `RouteLog::routed_at` as well as `MapTable::diagnose_at`.
///
/// `clone` SHARES the counter (that is how two structures are joined);
/// [`VersionClock::fork`] copies its present value into an independent one.
Expand Down Expand Up @@ -797,6 +797,40 @@ impl<'t> TileMap<'t> {
}
}

/// A read-only view of a tile's map at a bare version number. It exposes the
/// same reads as [`TileMap`] but is a distinct type with no conversion into
/// one, so it cannot be passed to an executor (`MidMap`). Only
/// [`MapTable::map`], which checks the frame's issuer, returns an executable
/// map.
#[derive(Clone, Copy, Debug)]
pub struct MapDiagnostic<'t>(TileMap<'t>);

impl MapDiagnostic<'_> {
pub fn width(&self) -> Width {
self.0.width()
}

pub fn len(&self) -> usize {
self.0.len()
}

pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

pub fn local(&self, global: u64) -> Result<u8, MapRefusal> {
self.0.local(global)
}

pub fn global(&self, local: u8) -> Option<u64> {
self.0.global(local)
}

pub fn digest(&self) -> u64 {
self.0.digest()
}
}

/// The versioned global → local table (D-DT64-7 O5): every tile's history of
/// [`LocalMap`]s under one [`FrameStore`]. Maps change only through
/// [`MapTable::advance`], which appends: an extension is a length at a
Expand Down Expand Up @@ -922,18 +956,32 @@ impl MapTable {
self.view(version, tile)
}

/// The map of `tile` as of a numeric `version` (for replay where only the
/// number survives). A version past the end of the sequence is
/// `FutureVersion`.
pub fn map_at(&self, version: RouteVersion, tile: TileId) -> Result<TileMap<'_>, MapRead> {
/// A read-only diagnostic of `tile` as of a numeric `version`. A bare
/// number carries no issuer, so a frame of another table at an equal
/// version would be answered with THIS table's layout; the result is
/// therefore a [`MapDiagnostic`], not an executable [`TileMap`]. Execute
/// only through [`MapTable::map`]. A version past the end of the sequence
/// is `FutureVersion`.
///
/// ```compile_fail
/// use lance_graph_dt64::dr::{MapTable, RouteVersion, TileMap};
/// fn executes(_: TileMap<'_>) {}
/// let t = MapTable::new(1, 1);
/// executes(t.diagnose_at(RouteVersion(0), 1).unwrap());
/// ```
pub fn diagnose_at(
&self,
version: RouteVersion,
tile: TileId,
) -> Result<MapDiagnostic<'_>, MapRead> {
let current = self.frames.clock.current();
if version > current {
return Err(MapRead::FutureVersion {
asked: version,
current,
});
}
self.view(version, tile)
self.view(version, tile).map(MapDiagnostic)
}

fn view(&self, version: RouteVersion, tile: TileId) -> Result<TileMap<'_>, MapRead> {
Expand Down
37 changes: 31 additions & 6 deletions crates/lance-graph-dt64/tests/dr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,8 @@ mod vmap {
assert_ne!(v1[0].1, v2[0].1);
assert_ne!(v1[2].1, v2[2].1);
// The numeric read agrees with the frame read.
assert_eq!(t.map_at(f1.version(), 1).unwrap().local(10), Ok(0));
assert_eq!(t.map_at(f2.version(), 1).unwrap().local(10), Ok(2));
assert_eq!(t.diagnose_at(f1.version(), 1).unwrap().local(10), Ok(0));
assert_eq!(t.diagnose_at(f2.version(), 1).unwrap().local(10), Ok(2));
}

/// F2 extension is a length at a version. Fails (disable: drop the
Expand Down Expand Up @@ -555,23 +555,48 @@ mod vmap {

/// Q2: a version past the end is a typed refusal, not a clamp.
#[test]
fn map_at_refuses_a_version_past_the_end() {
fn diagnose_at_refuses_a_version_past_the_end() {
let mut t = MapTable::new(1, 1);
t.advance(1, 1, vec![(1, Some(map(&[10])))]);
let cur = t.current_version();
assert!(t.map_at(cur, 1).is_ok());
assert!(t.diagnose_at(cur, 1).is_ok());
let future = RouteVersion(cur.0 + 1);
assert_eq!(
t.map_at(future, 1).unwrap_err(),
t.diagnose_at(future, 1).unwrap_err(),
MapRead::FutureVersion {
asked: future,
current: cur
}
);
assert_eq!(
t.map_at(RouteVersion(0), 1).unwrap_err(),
t.diagnose_at(RouteVersion(0), 1).unwrap_err(),
MapRead::NoSuchTile(1),
"before the tile existed"
);
}

/// P9 (E1): a foreign frame's version, read numerically, must not yield an
/// executable map. `map(frame)` refuses the frame; `diagnose_at` can still
/// show the number's layout in THIS table, but only as a `MapDiagnostic`,
/// which is not a `TileMap` and so cannot reach `MidMap` executors.
#[test]
fn p9_equal_version_foreign_frame_is_not_executable() {
let mut a = MapTable::new(1, 1);
let mut b = MapTable::new(1, 1);
a.advance(1, 1, vec![(1, Some(map(&[10, 20])))]);
b.advance(1, 1, vec![(1, Some(map(&[20, 10])))]);
let fb = b.seal();
assert_eq!(fb.version(), a.seal().version(), "equal versions");
// The executable read refuses the foreign frame.
assert_eq!(
a.map(&fb, 1).unwrap_err(),
MapRead::Stale(FrameRefusal::ForeignFrame)
);
// The issuer answers differently from A's layout at that number:
// the numeric read in A is a different layout than B's frame means.
assert_eq!(b.map(&fb, 1).unwrap().local(10), Ok(1));
let d = a.diagnose_at(fb.version(), 1).unwrap();
assert_eq!(d.local(10), Ok(0), "A's layout, not the frame's");
assert_ne!(d.digest(), b.map(&fb, 1).unwrap().digest());
}
}
Loading