use crate::Value;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionOrigin {
Live,
Commit(crate::CommitId),
CarvedResidue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewState {
PresentInFinalView,
ValueChangedLater,
AbsentInFinalView,
CarvedResidue,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq)]
pub struct RowVersion {
pub rowid: Option<i64>,
pub values: Vec<Value>,
pub origin: VersionOrigin,
pub commit_seq: Option<u32>,
pub view_state: ViewState,
pub is_deleted: bool,
pub is_guessed: bool,
pub rowid_reused: bool,
pub attribution_uncertain: bool,
pub reinserted_after_gap: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableHistory {
pub table: String,
pub columns: Vec<String>,
pub without_rowid: bool,
pub versions: Vec<RowVersion>,
pub without_rowid_rows: Vec<Vec<Value>>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq)]
pub struct RowView {
pub commit_seq: Option<u32>,
pub is_final: bool,
pub checksum_valid: bool,
pub schema_known: bool,
pub origin: VersionOrigin,
pub rows: BTreeMap<i64, Vec<Value>>,
}
#[must_use]
pub fn build_rowid_versions(rowid: i64, views: &[RowView]) -> Vec<RowVersion> {
struct Run<'a> {
values: &'a [Value],
earliest_seq: Option<u32>,
origin: VersionOrigin,
uncertain: bool,
gap_before: bool,
reappeared_after_gap: bool,
}
let mut runs: Vec<Run> = Vec::new();
let mut seen_present = false;
let mut pending_gap = false;
for view in views {
match view.rows.get(&rowid) {
None => {
if seen_present {
pending_gap = true;
}
}
Some(values) => {
let uncertain = !view.checksum_valid || !view.schema_known;
let extends = matches!(runs.last(), Some(r) if r.values == values.as_slice());
if extends && !pending_gap {
} else if extends && pending_gap {
if let Some(r) = runs.last_mut() {
r.reappeared_after_gap = true;
}
} else {
runs.push(Run {
values,
earliest_seq: view.commit_seq,
origin: view.origin.clone(),
uncertain,
gap_before: pending_gap,
reappeared_after_gap: false,
});
}
seen_present = true;
pending_gap = false;
}
}
}
if runs.is_empty() {
return Vec::new();
}
let final_values: Option<&[Value]> = views
.iter()
.rev()
.find(|v| v.is_final)
.and_then(|v| v.rows.get(&rowid))
.map(Vec::as_slice);
let present_in_final = final_values.is_some();
let rowid_reused = runs.iter().skip(1).any(|r| r.gap_before);
let last_idx = runs.len() - 1;
let next_gap: Vec<bool> = (0..runs.len())
.map(|i| runs.get(i + 1).is_some_and(|r| r.gap_before))
.collect();
runs.iter()
.enumerate()
.map(|(i, run)| {
let is_last = i == last_idx;
let is_final_value = is_last && present_in_final && final_values == Some(run.values);
let deleted_here = next_gap[i] || (is_last && !present_in_final);
let view_state = if is_final_value {
ViewState::PresentInFinalView
} else if deleted_here {
ViewState::AbsentInFinalView
} else {
ViewState::ValueChangedLater
};
let is_deleted = matches!(view_state, ViewState::AbsentInFinalView);
RowVersion {
rowid: Some(rowid),
values: run.values.to_vec(),
origin: run.origin.clone(),
commit_seq: run.earliest_seq,
view_state,
is_deleted,
is_guessed: false,
rowid_reused,
attribution_uncertain: run.uncertain,
reinserted_after_gap: run.reappeared_after_gap,
}
})
.collect()
}
#[must_use]
pub fn table_history(
table: String,
columns: Vec<String>,
without_rowid: bool,
views: &[RowView],
) -> TableHistory {
if without_rowid {
return TableHistory {
table,
columns,
without_rowid: true,
versions: Vec::new(),
without_rowid_rows: Vec::new(),
};
}
let mut rowids: std::collections::BTreeSet<i64> = std::collections::BTreeSet::new();
for v in views {
rowids.extend(v.rows.keys().copied());
}
let mut versions: Vec<RowVersion> = Vec::new();
for rowid in rowids {
versions.extend(build_rowid_versions(rowid, views));
}
sort_versions(&mut versions);
TableHistory {
table,
columns,
without_rowid: false,
versions,
without_rowid_rows: Vec::new(),
}
}
pub fn sort_table_versions(versions: &mut [RowVersion]) {
sort_versions(versions);
}
fn sort_versions(versions: &mut [RowVersion]) {
versions.sort_by(|a, b| {
let ra = a.rowid;
let rb = b.rowid;
let rowid_ord = match (ra, rb) {
(Some(x), Some(y)) => x.cmp(&y),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
};
rowid_ord.then_with(|| {
match (a.commit_seq, b.commit_seq) {
(Some(x), Some(y)) => x.cmp(&y),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
})
});
}