Skip to main content

kevy_index/
table_verify.rs

1//! Named verify-report types, shared by both faces.
2//!
3//! The first release shipped the embedded report as
4//! `(Vec<(Vec<u8>, [u64; 6])>, [u64; 2])` — six unnamed counters per
5//! index and two more for the spot check. The dogfood report's F10
6//! ("two counters side by side with different time semantics and
7//! nothing saying so") is the direct consequence: an anonymous array
8//! has nowhere to write what a number means. These structs are where
9//! that goes.
10
11/// Per-index verification counters, one row of `TABLE.VERIFY`.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct IndexVerify {
14    /// Compiled index name (`<table>.<column-or-orderpath>`).
15    pub name: Vec<u8>,
16    /// Entries currently held by the index.
17    pub entries: u64,
18    /// Approximate resident bytes of the index structure.
19    pub approx_bytes: u64,
20    /// Rows whose driving column is **present but fails to coerce** to
21    /// the declared type. Recomputed fresh on every call —
22    /// the 4.0 counter of this name was a lifetime tally that also
23    /// swallowed absent-column rows, which is how a healthy migration
24    /// once read as 30 152 live failures.
25    pub coerce_failures: u64,
26    /// Rows excluded because a string component exceeded the composite
27    /// bound (`MAX_STR_COMPONENT`). Fresh. The counter that turns a
28    /// silent two-row entries gap into a named number.
29    pub excluded: u64,
30    /// Rows whose driving column is absent — NULL semantics, excluded
31    /// by design and *not* a failure. Fresh; named so absence can never
32    /// again masquerade as coercion failure.
33    pub absent: u64,
34    /// Prefix rows this verify walked for the row→index direction.
35    pub rows: u64,
36    /// Rows that derive a value yet have **no entry** in the index —
37    /// the "writer forgot this path" class a drift walk structurally
38    /// cannot see, because it iterates entries and a missing entry is
39    /// not there to iterate. Fresh.
40    pub missing: u64,
41    /// Distinct sort keys held by more than one row. Non-zero means the
42    /// sort is not a total order — a paged reader can skip or repeat
43    /// rows at page boundaries. Add a bounded tie-break column.
44    pub duplicates: u64,
45    /// Entries whose held value disagrees with re-deriving from the row
46    /// right now. Recomputed fresh on every call.
47    pub drift: u64,
48    /// Entries the drift recheck examined this call.
49    pub checked: u64,
50}
51
52/// The whole `TABLE.VERIFY` answer.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct TableVerify {
55    /// One row per compiled index of the table.
56    pub per_index: Vec<IndexVerify>,
57    /// Rows the bounded column-type spot check sampled this call.
58    pub spot_rows: u64,
59    /// Of those, rows holding a value that contradicts the declared
60    /// column type.
61    pub spot_type_mismatches: u64,
62}
63
64/// What `table_ensure` found (the boot verb — see the embedded docs).
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum TableEnsure {
67    /// The table did not exist; it was declared and its indexes built.
68    Created,
69    /// An identical declaration already exists; nothing was touched.
70    Unchanged,
71}
72
73/// Name the first difference between an admitted spec and a proposed
74/// one — the message `table_ensure` refuses with. Specific enough to
75/// act on, short enough for a boot log.
76#[must_use]
77pub fn spec_diff(cur: &crate::TableSpec, new: &crate::TableSpec) -> String {
78    let part = if cur.prefix != new.prefix {
79        "PREFIX"
80    } else if cur.pk != new.pk {
81        "PK"
82    } else if cur.columns != new.columns {
83        "COLUMNS"
84    } else if cur.indexes != new.indexes {
85        "INDEXES"
86    } else if cur.orderpaths != new.orderpaths {
87        "ORDERPATHS"
88    } else {
89        "SPEC"
90    };
91    format!(
92        "ERR table '{}' exists with a different spec ({part} differ); \
93         TABLE.REPLACE rebuilds, TABLE.DROP + DECLARE is the manual form",
94        String::from_utf8_lossy(&cur.name)
95    )
96}