///|
pub(all) enum TemporalJoinMode {
BothAssigned
LeftAssigned
AllStates
}
///|
/// A pair of independent ledger witnesses over one common valid-time span.
/// None is an unclaimed gap; Some(tombstone) remains explicit withdrawal evidence.
pub struct JoinedSegment {
valid : Span
left : Revision?
right : Revision?
}
///|
pub fn JoinedSegment::valid(self : JoinedSegment) -> Span {
self.valid
}
///|
pub fn JoinedSegment::left(self : JoinedSegment) -> Revision? {
self.left
}
///|
pub fn JoinedSegment::right(self : JoinedSegment) -> Revision? {
self.right
}
///|
/// Join two independently versioned facts using each side's knowledge cutoff.
/// No arithmetic on time endpoints, no automatic business-value comparison.
pub fn Ledger::join_timeline(
self : Ledger,
other : Ledger,
left_key : String,
right_key : String,
bounds : Span,
left_known : Int64,
right_known : Int64,
mode~ : TemporalJoinMode = BothAssigned,
max_segments~ : Int32 = 4096,
) -> Array[JoinedSegment] raise TemporalError {
if max_segments <= 0 {
raise TooManySegments(maximum=max_segments)
}
if left_key.is_empty() || right_key.is_empty() {
raise EmptyKey
}
let left = self.timeline(left_key, bounds, left_known, max_segments~)
let right = other.timeline(right_key, bounds, right_known, max_segments~)
let result : Array[JoinedSegment] = []
let mut a = 0
let mut b = 0
while a < left.length() && b < right.length() {
let emit_row = match mode {
BothAssigned => left[a].value() is Some(_) && right[b].value() is Some(_)
LeftAssigned => left[a].value() is Some(_)
AllStates => true
}
if emit_row && left[a].valid.intersection(right[b].valid) is Some(valid) {
if result.length() >= max_segments {
raise TooManySegments(maximum=max_segments)
}
result.push({ valid, left: left[a].source, right: right[b].source, })
}
if end_before(left[a].valid.end, right[b].valid.end) {
a += 1
} else if end_before(right[b].valid.end, left[a].valid.end) {
b += 1
} else {
a += 1
b += 1
}
}
result
}