Skip to main content

inillucent_sql/
declare.rs

1//! Reading a virtual table's declaration and a pragma's argument.
2//!
3//! Invariant: nothing here touches a database. These are four pure functions
4//! over types this crate already owns, a `Declaration` and a `PragmaArgument`,
5//! and they are here rather than in a connection crate because **both** engines
6//! need them and neither should have to depend on the other to get them.
7//!
8//! They lived in `inillucent-session`, which is the old engine's connection, and
9//! the new engine's statement path imported them from there. That was the last
10//! thing tying the new engine to the old one that was not itself an engine: two
11//! helpers that parse text. Moving them down removes the edge without changing
12//! a caller, because `inillucent-session` re-exports both under their old paths.
13
14use crate::bind::BoundExpr;
15use crate::catalog_view::ColumnInfo;
16use crate::directive::PragmaArgument;
17use crate::vtab::Declaration;
18
19/// Returns the columns a module's declaration provides.
20///
21/// A declared column carries a name, a declared type, an affinity, a collation
22/// and whether it is hidden. Everything else a `ColumnInfo` can say - NOT NULL,
23/// a default, a primary-key position, a generated expression - is something a
24/// `CREATE TABLE` says and a module's declaration does not, so it is left at
25/// the value that means "unsaid" rather than guessed at.
26///
27/// @param declaration - what the module answered when it was connected
28pub fn declared_columns(declaration: &Declaration) -> Vec<ColumnInfo> {
29    declaration
30        .columns
31        .iter()
32        .map(|column| ColumnInfo {
33            folded: column.name.to_ascii_lowercase(),
34            name: column.name.clone(),
35            declared_type: column.declared_type.clone(),
36            affinity: column.affinity,
37            collation: column.collation.clone(),
38            not_null: false,
39            not_null_conflict: None,
40            primary_key_conflict: None,
41            default_sql: None,
42            primary_key_position: None,
43            hidden: column.hidden,
44            generated: false,
45            stored: true,
46            generated_sql: None,
47        })
48        .collect()
49}
50
51/// Reads a pragma argument as text.
52///
53/// @param argument - the argument as the parser produced it
54pub fn argument_text(argument: &PragmaArgument) -> String {
55    match argument {
56        PragmaArgument::Name(name) => String::from_utf8_lossy(name).into_owned(),
57        PragmaArgument::Value(expr) => expression_text(expr),
58    }
59}
60
61/// Returns the text a bound pragma argument spells.
62///
63/// `PRAGMA cache_size = -4000` is a unary minus over a literal rather than a
64/// negative literal, because that is what the grammar has. Reading only the
65/// literal made every negative setting read as zero.
66///
67/// @param expr - the argument's expression
68fn expression_text(expr: &BoundExpr) -> String {
69    match expr {
70        BoundExpr::Text(text) => String::from_utf8_lossy(text).into_owned(),
71        BoundExpr::Integer(value) => value.to_string(),
72        BoundExpr::Real(value) => value.to_string(),
73        BoundExpr::Unary { op, operand } => match op {
74            crate::ast::UnaryOp::Negate => format!("-{}", expression_text(operand)),
75            crate::ast::UnaryOp::Identity => expression_text(operand),
76            _ => String::new(),
77        },
78        _ => String::new(),
79    }
80}
81
82/// Reads a pragma argument as the boolean SQLite accepts.
83///
84/// SQLite reads `on`, `yes` and `true` as one and everything else it cannot
85/// parse as zero, which is why `PRAGMA foreign_keys = maybe` turns them off.
86///
87/// @param argument - the argument as the parser produced it
88pub fn argument_boolean(argument: &PragmaArgument) -> bool {
89    let text = argument_text(argument);
90    let folded = text.trim().to_ascii_lowercase();
91    match folded.as_str() {
92        "on" | "yes" | "true" => true,
93        "off" | "no" | "false" => false,
94        _ => folded
95            .parse::<i64>()
96            .map(|value| value != 0)
97            .unwrap_or(false),
98    }
99}
100
101/// Reads a pragma argument as an integer.
102///
103/// @param argument - the argument as the parser produced it
104pub fn argument_integer(argument: &PragmaArgument) -> i64 {
105    argument_text(argument).trim().parse().unwrap_or(0)
106}