use crate::affinity::{Affinity, StorageClass};
use crate::ddl::TableDef;
use crate::error::Result;
use crate::record::SqliteValue;
use crate::schema::SchemaObject;
use crate::source::Source;
use crate::tree::Row;
use crate::Reader;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Slot {
Stored(usize),
RowidAlias(usize),
Virtual,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Cell<'a> {
Value(&'a SqliteValue),
Rowid(i64),
Missing,
Virtual,
}
pub struct RowLayout {
slots: Vec<Slot>,
}
impl RowLayout {
pub fn new(def: &TableDef) -> RowLayout {
if def.without_rowid {
return RowLayout::keyed(def);
}
let alias = def.rowid_alias().map(|c| c.name.clone());
let mut slots = Vec::with_capacity(def.columns.len());
let mut position = 0usize;
for column in &def.columns {
if column.generated.is_virtual() {
slots.push(Slot::Virtual);
continue;
}
let is_alias = alias
.as_deref()
.is_some_and(|a| a.eq_ignore_ascii_case(&column.name));
slots.push(if is_alias {
Slot::RowidAlias(position)
} else {
Slot::Stored(position)
});
position += 1;
}
RowLayout { slots }
}
fn keyed(def: &TableDef) -> RowLayout {
let mut order: Vec<usize> = Vec::with_capacity(def.columns.len());
for key in &def.primary_key {
if let Some(index) = def.column_index(&key.name) {
if !order.contains(&index) {
order.push(index);
}
}
}
for index in 0..def.columns.len() {
if !order.contains(&index) {
order.push(index);
}
}
let mut slots = vec![Slot::Virtual; def.columns.len()];
let mut position = 0usize;
for index in order {
if def.columns[index].generated.is_virtual() {
continue;
}
slots[index] = Slot::Stored(position);
position += 1;
}
RowLayout { slots }
}
pub fn declared_columns(&self) -> usize {
self.slots.len()
}
pub fn stored_columns(&self) -> usize {
self.slots
.iter()
.filter(|s| !matches!(s, Slot::Virtual))
.count()
}
pub fn cell<'a>(&self, row: &'a Row, index: usize) -> Cell<'a> {
match self.slots.get(index) {
None => Cell::Missing,
Some(Slot::Virtual) => Cell::Virtual,
Some(Slot::RowidAlias(at)) => match row.rowid {
Some(rowid) => Cell::Rowid(rowid),
None => match row.values.get(*at) {
Some(value) => Cell::Value(value),
None => Cell::Missing,
},
},
Some(Slot::Stored(at)) => match row.values.get(*at) {
Some(value) => Cell::Value(value),
None => Cell::Missing,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnSurvey {
pub name: String,
pub declared_type: Option<String>,
pub affinity: Affinity,
pub nulls: u64,
pub integers: u64,
pub reals: u64,
pub texts: u64,
pub blobs: u64,
pub missing: u64,
pub largest_integer: u64,
pub longest_value: usize,
pub is_rowid_alias: bool,
pub is_virtual: bool,
}
impl ColumnSurvey {
pub fn classes(&self) -> Vec<StorageClass> {
let mut present = Vec::new();
for (count, class) in [
(self.integers, StorageClass::Integer),
(self.reals, StorageClass::Real),
(self.texts, StorageClass::Text),
(self.blobs, StorageClass::Blob),
] {
if count > 0 {
present.push(class);
}
}
present
}
pub fn has_nulls(&self) -> bool {
self.nulls > 0 || self.missing > 0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableSurvey {
pub name: String,
pub rows: u64,
pub columns: Vec<ColumnSurvey>,
pub primary_key: Vec<String>,
pub without_rowid: bool,
pub largest_rowid: i64,
}
impl<S: Source> Reader<S> {
pub fn survey_table(&self, object: &SchemaObject) -> Result<TableSurvey> {
let def = object.table_def()?;
let layout = RowLayout::new(&def);
let mut columns: Vec<ColumnSurvey> = def
.columns
.iter()
.map(|column| ColumnSurvey {
name: column.name.clone(),
declared_type: column.declared_type.clone(),
affinity: Affinity::of(column.declared_type.as_deref()),
nulls: 0,
integers: 0,
reals: 0,
texts: 0,
blobs: 0,
missing: 0,
largest_integer: 0,
longest_value: 0,
is_rowid_alias: def
.rowid_alias()
.is_some_and(|c| c.name.eq_ignore_ascii_case(&column.name)),
is_virtual: column.generated.is_virtual(),
})
.collect();
let mut rows = 0u64;
let mut largest_rowid = 0i64;
let root = object.root_page.ok_or_else(|| {
crate::error::SqliteError::malformed(
1,
format!("the table {} has no root page", object.name),
)
})?;
for row in self.rows(root)? {
let row = row?;
rows += 1;
largest_rowid = largest_rowid.max(row.rowid.unwrap_or(0));
for (index, survey) in columns.iter_mut().enumerate() {
match layout.cell(&row, index) {
Cell::Virtual => {}
Cell::Missing => survey.missing += 1,
Cell::Rowid(rowid) => {
survey.integers += 1;
survey.largest_integer = survey.largest_integer.max(rowid.unsigned_abs());
}
Cell::Value(value) => {
if let SqliteValue::Integer(n) = value {
survey.largest_integer = survey.largest_integer.max(n.unsigned_abs());
}
survey.longest_value = survey.longest_value.max(match value {
SqliteValue::Text(t) => t.len(),
SqliteValue::Blob(b) => b.len(),
_ => 0,
});
match survey.affinity.logical_class(value) {
StorageClass::Null => survey.nulls += 1,
StorageClass::Integer => survey.integers += 1,
StorageClass::Real => survey.reals += 1,
StorageClass::Text => survey.texts += 1,
StorageClass::Blob => survey.blobs += 1,
}
}
}
}
}
Ok(TableSurvey {
name: def.name.clone(),
rows,
columns,
primary_key: def.primary_key.iter().map(|k| k.name.clone()).collect(),
without_rowid: def.without_rowid,
largest_rowid,
})
}
}