vibesql_executor/
sqlite_stat.rs

1//! SQLite Statistics Virtual Tables
2//!
3//! Implements `sqlite_stat1`, `sqlite_stat2`, `sqlite_stat3`, and `sqlite_stat4` for SQLite
4//! compatibility. These tables store index statistics computed by the ANALYZE command.
5//!
6//! Currently, only `sqlite_stat1` is fully implemented, which is the most commonly used.
7//!
8//! Schema:
9//! ```sql
10//! CREATE TABLE sqlite_stat1 (
11//!   tbl TEXT,   -- table name
12//!   idx TEXT,   -- index name (NULL for table statistics)
13//!   stat TEXT   -- space-separated statistics (row_count selectivity...)
14//! );
15//! ```
16//!
17//! Reference: https://www.sqlite.org/fileformat2.html#stat1tab
18
19use vibesql_catalog::{ColumnSchema, TableSchema};
20use vibesql_storage::Row;
21use vibesql_types::{DataType, SqlValue};
22
23use crate::{errors::ExecutorError, select::SelectResult};
24
25/// Check if a table reference is a sqlite_stat table
26pub fn is_sqlite_stat_table(table_name: &str) -> bool {
27    let normalized = table_name.to_lowercase();
28    matches!(normalized.as_str(), "sqlite_stat1" | "sqlite_stat2" | "sqlite_stat3" | "sqlite_stat4")
29}
30
31/// Check if a table reference is specifically sqlite_stat1
32pub fn is_sqlite_stat1_table(table_name: &str) -> bool {
33    table_name.eq_ignore_ascii_case("sqlite_stat1")
34}
35
36/// Get the schema for sqlite_stat1
37pub fn get_sqlite_stat1_table_schema() -> TableSchema {
38    TableSchema::new(
39        "sqlite_stat1".to_string(),
40        vec![
41            ColumnSchema::new("tbl".to_string(), DataType::Varchar { max_length: None }, false),
42            ColumnSchema::new("idx".to_string(), DataType::Varchar { max_length: None }, true),
43            ColumnSchema::new("stat".to_string(), DataType::Varchar { max_length: None }, false),
44        ],
45    )
46}
47
48/// Execute a sqlite_stat1 query
49///
50/// This returns statistics that were manually inserted via INSERT INTO sqlite_stat1.
51/// Unlike SQLite where ANALYZE populates sqlite_stat1 automatically, VibeSQL stores
52/// statistics internally and uses sqlite_stat1 only for manual overrides.
53///
54/// To use statistics in VibeSQL:
55/// 1. Run ANALYZE to compute internal statistics
56/// 2. Optionally INSERT INTO sqlite_stat1 to override specific statistics
57pub fn execute_sqlite_stat1_query(
58    _catalog: &vibesql_catalog::Catalog,
59    database: &vibesql_storage::Database,
60) -> Result<SelectResult, ExecutorError> {
61    let schema = get_sqlite_stat1_table_schema();
62    let column_names: Vec<String> = schema.columns.iter().map(|c| c.name.clone()).collect();
63    let mut rows = Vec::new();
64
65    // Return manually inserted rows
66    // SQLite compatibility: allow users to INSERT statistics for optimizer tuning
67    for ((tbl, idx), stat) in database.get_all_sqlite_stat1() {
68        rows.push(Row::new(vec![
69            SqlValue::Varchar(arcstr::ArcStr::from(tbl.as_str())),
70            match idx {
71                Some(i) => SqlValue::Varchar(arcstr::ArcStr::from(i.as_str())),
72                None => SqlValue::Null,
73            },
74            SqlValue::Varchar(arcstr::ArcStr::from(stat.as_str())),
75        ]));
76    }
77
78    Ok(SelectResult { columns: column_names, rows })
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_is_sqlite_stat_table() {
87        assert!(is_sqlite_stat_table("sqlite_stat1"));
88        assert!(is_sqlite_stat_table("SQLITE_STAT1"));
89        assert!(is_sqlite_stat_table("Sqlite_Stat1"));
90        assert!(is_sqlite_stat_table("sqlite_stat2"));
91        assert!(is_sqlite_stat_table("sqlite_stat3"));
92        assert!(is_sqlite_stat_table("sqlite_stat4"));
93        assert!(!is_sqlite_stat_table("sqlite_master"));
94        assert!(!is_sqlite_stat_table("users"));
95    }
96
97    #[test]
98    fn test_get_sqlite_stat1_table_schema() {
99        let schema = get_sqlite_stat1_table_schema();
100
101        assert_eq!(schema.name, "sqlite_stat1");
102        assert_eq!(schema.columns.len(), 3);
103        assert_eq!(schema.columns[0].name, "tbl");
104        assert_eq!(schema.columns[1].name, "idx");
105        assert_eq!(schema.columns[2].name, "stat");
106    }
107}