quantydb_sqlite/survey.rs
1//! Which stored value belongs to which declared column, and what a column
2//! actually holds.
3//!
4//! These two things live together because the second cannot be done without
5//! the first. Counting what is in a column means knowing which value in the
6//! record is that column, and the record does not line up with the column
7//! list. Three rules pull them apart, and each was checked against a file
8//! written by SQLite rather than recalled:
9//!
10//! A virtual generated column occupies no slot in the record, while a
11//! stored one does. In `(a, v virtual, s stored)` the record holds two
12//! values, `a` and `s`. Zipping the declared columns against the stored
13//! values puts `s`'s value into `v`, and then every column after it, and
14//! nothing about the result looks wrong.
15//!
16//! A column that is an alias for the rowid is stored as NULL, and its value
17//! is the cell's rowid. Reading it as stored gives a column of nothing.
18//!
19//! A record may end early. `alter table add column` does not rewrite the
20//! existing rows, so a table with four columns can hold rows with two
21//! values in them, and the rest take the default from the declaration. A
22//! reader that treats a short record as corruption refuses ordinary files.
23
24use crate::affinity::{Affinity, StorageClass};
25use crate::ddl::TableDef;
26use crate::error::Result;
27use crate::record::SqliteValue;
28use crate::schema::SchemaObject;
29use crate::source::Source;
30use crate::tree::Row;
31use crate::Reader;
32
33/// Where a declared column's value comes from.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35enum Slot {
36 /// Position in the record.
37 Stored(usize),
38 /// Position in the record, which holds NULL; the value is the rowid.
39 RowidAlias(usize),
40 /// Computed on demand by SQLite and absent from the file.
41 Virtual,
42}
43
44/// A column's value in one row, once the record has been lined up with the
45/// declaration.
46#[derive(Debug, Clone, PartialEq)]
47pub enum Cell<'a> {
48 /// The value as stored.
49 Value(&'a SqliteValue),
50 /// The row's rowid, standing in for a column that aliases it.
51 Rowid(i64),
52 /// The record ended before this column, so its default applies.
53 Missing,
54 /// A virtual generated column: the file holds nothing for it.
55 Virtual,
56}
57
58/// Lines a table's records up with its declared columns.
59///
60/// Built once per table and used for every row, because the layout is a
61/// property of the declaration rather than of any particular row.
62pub struct RowLayout {
63 slots: Vec<Slot>,
64}
65
66impl RowLayout {
67 pub fn new(def: &TableDef) -> RowLayout {
68 if def.without_rowid {
69 return RowLayout::keyed(def);
70 }
71 let alias = def.rowid_alias().map(|c| c.name.clone());
72 let mut slots = Vec::with_capacity(def.columns.len());
73 let mut position = 0usize;
74 for column in &def.columns {
75 if column.generated.is_virtual() {
76 slots.push(Slot::Virtual);
77 continue;
78 }
79 let is_alias = alias
80 .as_deref()
81 .is_some_and(|a| a.eq_ignore_ascii_case(&column.name));
82 slots.push(if is_alias {
83 Slot::RowidAlias(position)
84 } else {
85 Slot::Stored(position)
86 });
87 position += 1;
88 }
89 RowLayout { slots }
90 }
91
92 /// The layout of a without rowid table, where the record is permuted.
93 ///
94 /// Such a table is stored as an index b-tree keyed by its primary key,
95 /// and the entry holds the key columns first, in key order, then the
96 /// remaining columns in declared order. Nothing in the bytes says which
97 /// arrangement is in force, so this is read off the declaration and is
98 /// the whole reason the create statement has to be parsed before such a
99 /// table can be read at all.
100 ///
101 /// A key column named in the primary key but missing from the column
102 /// list cannot happen in a database sqlite accepted, and if it does the
103 /// column simply takes the next position, which keeps the mapping
104 /// total rather than panicking on a file we did not write.
105 fn keyed(def: &TableDef) -> RowLayout {
106 let mut order: Vec<usize> = Vec::with_capacity(def.columns.len());
107 for key in &def.primary_key {
108 if let Some(index) = def.column_index(&key.name) {
109 if !order.contains(&index) {
110 order.push(index);
111 }
112 }
113 }
114 for index in 0..def.columns.len() {
115 if !order.contains(&index) {
116 order.push(index);
117 }
118 }
119
120 // walk the record order, handing out positions, then put the slots
121 // back into declared order for the caller
122 let mut slots = vec![Slot::Virtual; def.columns.len()];
123 let mut position = 0usize;
124 for index in order {
125 if def.columns[index].generated.is_virtual() {
126 continue;
127 }
128 slots[index] = Slot::Stored(position);
129 position += 1;
130 }
131 RowLayout { slots }
132 }
133
134 /// How many columns the declaration has, virtual ones included.
135 pub fn declared_columns(&self) -> usize {
136 self.slots.len()
137 }
138
139 /// How many values a complete record of this table holds.
140 pub fn stored_columns(&self) -> usize {
141 self.slots
142 .iter()
143 .filter(|s| !matches!(s, Slot::Virtual))
144 .count()
145 }
146
147 /// The value of declared column `index` in `row`.
148 ///
149 /// Takes the unified row, so the same layout serves both kinds of
150 /// table: one call site, one set of rules, and no way for the two paths
151 /// to drift apart.
152 pub fn cell<'a>(&self, row: &'a Row, index: usize) -> Cell<'a> {
153 match self.slots.get(index) {
154 None => Cell::Missing,
155 Some(Slot::Virtual) => Cell::Virtual,
156 Some(Slot::RowidAlias(at)) => match row.rowid {
157 // the slot exists and holds NULL; the value is the rowid.
158 // if a file ever stored something else there, the rowid is
159 // still the authority, because that is what sqlite indexes
160 // and what other tables reference.
161 Some(rowid) => Cell::Rowid(rowid),
162 // a row without a rowid cannot have a column aliasing one,
163 // so this is a file disagreeing with itself. report what is
164 // actually stored rather than inventing a number.
165 None => match row.values.get(*at) {
166 Some(value) => Cell::Value(value),
167 None => Cell::Missing,
168 },
169 },
170 Some(Slot::Stored(at)) => match row.values.get(*at) {
171 Some(value) => Cell::Value(value),
172 None => Cell::Missing,
173 },
174 }
175 }
176}
177
178/// What one column turned out to hold.
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct ColumnSurvey {
181 pub name: String,
182 pub declared_type: Option<String>,
183 pub affinity: Affinity,
184 /// Rows per logical storage class, affinity already applied.
185 pub nulls: u64,
186 pub integers: u64,
187 pub reals: u64,
188 pub texts: u64,
189 pub blobs: u64,
190 /// Rows whose record ended before this column, which take its default.
191 pub missing: u64,
192 /// The largest integer seen, by magnitude. A column that mixes integers
193 /// and reals can only become a float if every integer in it survives
194 /// the trip, and past 2^53 they stop doing that.
195 pub largest_integer: u64,
196 /// Bytes in the longest text or blob seen. A key or an index has a
197 /// length limit, so a caller deciding what to make of this column needs
198 /// to know its worst case before it starts writing rather than after.
199 pub longest_value: usize,
200 pub is_rowid_alias: bool,
201 pub is_virtual: bool,
202}
203
204impl ColumnSurvey {
205 /// The classes actually present, ignoring nulls, which are a question
206 /// about nullability rather than about type.
207 pub fn classes(&self) -> Vec<StorageClass> {
208 let mut present = Vec::new();
209 for (count, class) in [
210 (self.integers, StorageClass::Integer),
211 (self.reals, StorageClass::Real),
212 (self.texts, StorageClass::Text),
213 (self.blobs, StorageClass::Blob),
214 ] {
215 if count > 0 {
216 present.push(class);
217 }
218 }
219 present
220 }
221
222 pub fn has_nulls(&self) -> bool {
223 self.nulls > 0 || self.missing > 0
224 }
225}
226
227/// What a whole table turned out to hold.
228#[derive(Debug, Clone, PartialEq)]
229pub struct TableSurvey {
230 pub name: String,
231 pub rows: u64,
232 pub columns: Vec<ColumnSurvey>,
233 pub primary_key: Vec<String>,
234 pub without_rowid: bool,
235 /// The largest rowid in the table, which matters when the rowid has to
236 /// become a key column of its own.
237 pub largest_rowid: i64,
238}
239
240impl<S: Source> Reader<S> {
241 /// Read every row of a table and report what each column holds.
242 ///
243 /// This is the first of the import's two passes: it writes nothing and
244 /// its whole purpose is to let the second pass know the shape of what
245 /// it is about to write, and to let a caller see every problem at once
246 /// rather than the first one after ten minutes of work.
247 pub fn survey_table(&self, object: &SchemaObject) -> Result<TableSurvey> {
248 let def = object.table_def()?;
249 let layout = RowLayout::new(&def);
250
251 let mut columns: Vec<ColumnSurvey> = def
252 .columns
253 .iter()
254 .map(|column| ColumnSurvey {
255 name: column.name.clone(),
256 declared_type: column.declared_type.clone(),
257 affinity: Affinity::of(column.declared_type.as_deref()),
258 nulls: 0,
259 integers: 0,
260 reals: 0,
261 texts: 0,
262 blobs: 0,
263 missing: 0,
264 largest_integer: 0,
265 longest_value: 0,
266 is_rowid_alias: def
267 .rowid_alias()
268 .is_some_and(|c| c.name.eq_ignore_ascii_case(&column.name)),
269 is_virtual: column.generated.is_virtual(),
270 })
271 .collect();
272
273 let mut rows = 0u64;
274 let mut largest_rowid = 0i64;
275
276 let root = object.root_page.ok_or_else(|| {
277 crate::error::SqliteError::malformed(
278 1,
279 format!("the table {} has no root page", object.name),
280 )
281 })?;
282
283 // rows() picks the walk from the root page, so a without rowid
284 // table is surveyed the same way as any other
285 for row in self.rows(root)? {
286 let row = row?;
287 rows += 1;
288 largest_rowid = largest_rowid.max(row.rowid.unwrap_or(0));
289
290 for (index, survey) in columns.iter_mut().enumerate() {
291 match layout.cell(&row, index) {
292 Cell::Virtual => {}
293 Cell::Missing => survey.missing += 1,
294 Cell::Rowid(rowid) => {
295 survey.integers += 1;
296 survey.largest_integer = survey.largest_integer.max(rowid.unsigned_abs());
297 }
298 Cell::Value(value) => {
299 if let SqliteValue::Integer(n) = value {
300 survey.largest_integer = survey.largest_integer.max(n.unsigned_abs());
301 }
302 survey.longest_value = survey.longest_value.max(match value {
303 SqliteValue::Text(t) => t.len(),
304 SqliteValue::Blob(b) => b.len(),
305 _ => 0,
306 });
307 match survey.affinity.logical_class(value) {
308 StorageClass::Null => survey.nulls += 1,
309 StorageClass::Integer => survey.integers += 1,
310 StorageClass::Real => survey.reals += 1,
311 StorageClass::Text => survey.texts += 1,
312 StorageClass::Blob => survey.blobs += 1,
313 }
314 }
315 }
316 }
317 }
318
319 Ok(TableSurvey {
320 name: def.name.clone(),
321 rows,
322 columns,
323 primary_key: def.primary_key.iter().map(|k| k.name.clone()).collect(),
324 without_rowid: def.without_rowid,
325 largest_rowid,
326 })
327 }
328}