use super::interner::InternId;
use crate::coordinate::Coordinate;
use crate::event::{EventKind, HashChain};
use crate::store::{EncodedBytes, ExtensionKey};
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ClockKey {
pub(crate) wall_ms: u64,
pub(crate) clock: u32,
pub(crate) uuid: u128,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct IndexEntry {
pub(crate) event_id: u128,
pub(crate) correlation_id: u128,
pub(crate) causation_id: Option<u128>,
pub(crate) coord: Coordinate,
pub(crate) entity_id: InternId,
pub(crate) scope_id: InternId,
pub(crate) kind: EventKind,
pub(crate) wall_ms: u64,
pub(crate) clock: u32,
pub(crate) dag_lane: u32,
pub(crate) dag_depth: u32,
pub(crate) hash_chain: HashChain,
pub(crate) disk_pos: DiskPos,
pub(crate) global_sequence: u64,
pub(crate) receipt_extensions: BTreeMap<ExtensionKey, EncodedBytes>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiskPos {
pub(crate) segment_id: u64,
pub(crate) offset: u64,
pub(crate) length: u32,
}
impl DiskPos {
pub const fn new(segment_id: u64, offset: u64, length: u32) -> Self {
Self {
segment_id,
offset,
length,
}
}
pub const fn segment_id(self) -> u64 {
self.segment_id
}
pub const fn offset(self) -> u64 {
self.offset
}
pub const fn length(self) -> u32 {
self.length
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct QueryHit {
pub(crate) event_id: u128,
pub(crate) global_sequence: u64,
pub(crate) disk_pos: DiskPos,
pub(crate) kind: EventKind,
pub(crate) clock: u32,
pub(crate) dag_lane: u32,
}
impl QueryHit {
pub(crate) fn from_entry(entry: &IndexEntry) -> Self {
Self {
event_id: entry.event_id,
global_sequence: entry.global_sequence,
disk_pos: entry.disk_pos,
kind: entry.kind,
clock: entry.clock,
dag_lane: entry.dag_lane,
}
}
}
impl Ord for ClockKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.wall_ms
.cmp(&other.wall_ms)
.then(self.clock.cmp(&other.clock))
.then(self.uuid.cmp(&other.uuid))
}
}
impl PartialOrd for ClockKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl IndexEntry {
#[must_use]
pub const fn event_id(&self) -> crate::id::EventId {
crate::id::EventId::from_u128(self.event_id)
}
#[must_use]
pub const fn correlation_id(&self) -> u128 {
self.correlation_id
}
#[must_use]
pub const fn causation_id(&self) -> Option<u128> {
self.causation_id
}
#[must_use]
pub const fn coord(&self) -> &Coordinate {
&self.coord
}
#[must_use]
pub const fn event_kind(&self) -> EventKind {
self.kind
}
#[must_use]
pub const fn wall_ms(&self) -> u64 {
self.wall_ms
}
#[must_use]
pub const fn clock(&self) -> u32 {
self.clock
}
#[must_use]
pub const fn dag_lane(&self) -> u32 {
self.dag_lane
}
#[must_use]
pub const fn dag_depth(&self) -> u32 {
self.dag_depth
}
#[must_use]
pub const fn hash_chain(&self) -> &HashChain {
&self.hash_chain
}
#[must_use]
pub const fn disk_pos(&self) -> DiskPos {
self.disk_pos
}
#[must_use]
pub const fn global_sequence(&self) -> u64 {
self.global_sequence
}
#[must_use]
pub const fn receipt_extensions(&self) -> &BTreeMap<ExtensionKey, EncodedBytes> {
&self.receipt_extensions
}
pub fn is_correlated(&self) -> bool {
self.event_id != self.correlation_id
}
pub fn is_caused_by(&self, event_id: crate::id::EventId) -> bool {
use crate::id::EntityIdType;
self.causation_id == Some(event_id.as_u128())
}
pub fn is_root_cause(&self) -> bool {
self.causation_id.is_none()
}
}
#[cfg(test)]
mod entry_behavior_tests {
use super::super::interner::InternId;
use super::*;
fn make_entry(event_id: u128, correlation_id: u128, causation_id: Option<u128>) -> IndexEntry {
IndexEntry {
event_id,
correlation_id,
causation_id,
coord: Coordinate::new("entity:e", "scope:s").expect("coord"),
entity_id: InternId::sentinel(),
scope_id: InternId::sentinel(),
kind: EventKind::custom(0xA, 7),
wall_ms: 42,
clock: 3,
dag_lane: 1,
dag_depth: 2,
hash_chain: HashChain {
prev_hash: [1u8; 32],
event_hash: [2u8; 32],
},
disk_pos: DiskPos::new(11, 64, 128),
global_sequence: 9,
receipt_extensions: BTreeMap::new(),
}
}
#[test]
fn disk_pos_accessors_return_the_constructed_fields() {
let pos = DiskPos::new(7, 4096, 256);
assert_eq!(pos.segment_id(), 7, "segment_id accessor");
assert_eq!(pos.offset(), 4096, "offset accessor");
assert_eq!(pos.length(), 256, "length accessor");
}
#[test]
fn query_hit_from_entry_copies_the_correct_five_fields() {
let entry = make_entry(0xDEAD, 0xBEEF, Some(0xCAFE));
let hit = QueryHit::from_entry(&entry);
assert_eq!(hit.event_id, 0xDEAD, "event_id must come from entry");
assert_eq!(
hit.global_sequence, 9,
"global_sequence must come from entry"
);
assert_eq!(
hit.disk_pos,
DiskPos::new(11, 64, 128),
"disk_pos preserved"
);
assert_eq!(hit.kind, EventKind::custom(0xA, 7), "kind preserved");
assert_eq!(hit.clock, 3, "clock must come from entry, not wall_ms");
assert_ne!(u64::from(hit.clock), entry.wall_ms);
}
#[test]
fn is_correlated_is_true_only_when_correlation_differs_from_event_id() {
let root = make_entry(100, 100, None);
assert!(
!root.is_correlated(),
"a self-correlated root must NOT be reported as correlated"
);
let reaction = make_entry(101, 100, Some(100));
assert!(
reaction.is_correlated(),
"an event whose correlation differs from its id IS correlated"
);
}
#[test]
fn is_root_cause_polarity_matches_causation_presence() {
assert!(
make_entry(1, 1, None).is_root_cause(),
"no causation -> root cause"
);
assert!(
!make_entry(2, 1, Some(1)).is_root_cause(),
"present causation -> NOT root cause"
);
}
#[test]
fn is_caused_by_is_an_exact_match_not_a_has_any_cause_predicate() {
let entry = make_entry(2, 1, Some(0x1111));
assert!(
entry.is_caused_by(crate::id::EventId::from(0x1111u128)),
"exact causation id must match"
);
assert!(
!entry.is_caused_by(crate::id::EventId::from(0x2222u128)),
"a different id must NOT match (exactness)"
);
assert!(!make_entry(1, 1, None).is_caused_by(crate::id::EventId::from(0u128)));
}
#[test]
fn clock_key_tiebreaks_by_uuid_when_wall_and_clock_match() {
let lo = ClockKey {
wall_ms: 5,
clock: 5,
uuid: 1,
};
let hi = ClockKey {
wall_ms: 5,
clock: 5,
uuid: 2,
};
assert!(lo < hi, "uuid tiebreak must order lower uuid first");
let earlier_clock = ClockKey {
wall_ms: 5,
clock: 4,
uuid: 9999,
};
assert!(
earlier_clock < lo,
"a smaller clock must sort before a larger clock regardless of uuid"
);
}
}