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}