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}