1use crate::bind::BoundExpr;
15use crate::catalog_view::ColumnInfo;
16use crate::directive::PragmaArgument;
17use crate::vtab::Declaration;
18
19pub 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
51pub 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
61fn 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
82pub fn argument_boolean(argument: &PragmaArgument) -> bool {
91 sqlite_boolean(&argument_text(argument), false)
92}
93
94pub fn argument_integer(argument: &PragmaArgument) -> i64 {
98 argument_text(argument).trim().parse().unwrap_or(0)
99}
100
101pub fn sqlite_int32(text: &str) -> Option<i32> {
111 let bytes = text.as_bytes();
112 let (negative, digits) = match bytes.first() {
113 Some(b'-') => (true, bytes.get(1..).unwrap_or_default()),
114 Some(b'+') => (false, bytes.get(1..).unwrap_or_default()),
115 _ => (false, bytes),
116 };
117 if digits.first() == Some(&b'0')
118 && matches!(digits.get(1), Some(b'x' | b'X'))
119 && digits.get(2).is_some_and(u8::is_ascii_hexdigit)
120 && !negative
121 {
122 return hex_int32(digits.get(2..).unwrap_or_default());
123 }
124 let significant: Vec<u8> = digits
125 .iter()
126 .copied()
127 .skip_while(|byte| *byte == b'0')
128 .take_while(u8::is_ascii_digit)
129 .collect();
130 if !digits.first().is_some_and(u8::is_ascii_digit) || significant.len() > 10 {
131 return None;
132 }
133 let value = significant.iter().fold(0i64, |held, digit| {
134 held.saturating_mul(10)
135 .saturating_add(i64::from(digit.saturating_sub(b'0')))
136 });
137 let signed = if negative { -value } else { value };
138 i32::try_from(signed).ok()
139}
140
141fn hex_int32(digits: &[u8]) -> Option<i32> {
145 let trimmed: Vec<u8> = digits
146 .iter()
147 .copied()
148 .skip_while(|byte| *byte == b'0')
149 .collect();
150 let used: Vec<u8> = trimmed
151 .iter()
152 .copied()
153 .take_while(u8::is_ascii_hexdigit)
154 .collect();
155 if used.len() > 8 || trimmed.get(used.len()).is_some_and(u8::is_ascii_hexdigit) {
156 return None;
157 }
158 let value = used.iter().fold(0u32, |held, digit| {
159 let nibble = char::from(*digit).to_digit(16).unwrap_or(0);
160 held.wrapping_mul(16).wrapping_add(nibble)
161 });
162 if value & 0x8000_0000 != 0 {
163 return None;
164 }
165 i32::try_from(value).ok()
166}
167
168pub fn sqlite_atoi(text: &str) -> i32 {
172 sqlite_int32(text).unwrap_or(0)
173}
174
175pub fn sqlite_integer(text: &str) -> (i64, bool) {
185 let bytes = text.as_bytes();
186 if bytes.first() == Some(&b'0') && matches!(bytes.get(1), Some(b'x' | b'X')) {
187 let digits: Vec<u8> = bytes
188 .get(2..)
189 .unwrap_or_default()
190 .iter()
191 .copied()
192 .skip_while(|byte| *byte == b'0')
193 .collect();
194 let used: Vec<u8> = digits
195 .iter()
196 .copied()
197 .take_while(u8::is_ascii_hexdigit)
198 .collect();
199 let value = used.iter().fold(0u64, |held, digit| {
200 let nibble = char::from(*digit).to_digit(16).unwrap_or(0);
201 held.wrapping_mul(16).wrapping_add(u64::from(nibble))
202 });
203 return (value as i64, used.len() == digits.len() && used.len() <= 16);
204 }
205 let rest = text.trim_start_matches([' ', '\t', '\n', '\r', '\u{b}', '\u{c}']);
206 let (negative, unsigned) = match rest.as_bytes().first() {
207 Some(b'-') => (true, rest.get(1..).unwrap_or_default()),
208 Some(b'+') => (false, rest.get(1..).unwrap_or_default()),
209 _ => (false, rest),
210 };
211 let digits: Vec<u8> = unsigned.bytes().take_while(u8::is_ascii_digit).collect();
212 let magnitude = digits.iter().fold(0i128, |held, digit| {
213 held.saturating_mul(10)
214 .saturating_add(i128::from(digit.saturating_sub(b'0')))
215 .min(i128::from(i64::MAX) + 1)
216 });
217 let after = unsigned.get(digits.len()..).unwrap_or_default();
218 let whole = !digits.is_empty()
219 && after
220 .trim_start_matches([' ', '\t', '\n', '\r', '\u{b}', '\u{c}'])
221 .is_empty();
222 let signed = if negative { -magnitude } else { magnitude };
223 let clamped = signed.clamp(i128::from(i64::MIN), i128::from(i64::MAX));
224 (
225 i64::try_from(clamped).unwrap_or(0),
226 whole && clamped == signed,
227 )
228}
229
230pub fn sqlite_safety_level(text: &str, omit_full: bool, default: u8) -> u8 {
242 if text.as_bytes().first().is_some_and(u8::is_ascii_digit) {
243 return u8::try_from(sqlite_atoi(text) & 0xff).unwrap_or(0);
244 }
245 let words: [(&str, u8); 8] = [
246 ("on", 1),
247 ("no", 0),
248 ("off", 0),
249 ("false", 0),
250 ("yes", 1),
251 ("true", 1),
252 ("extra", 3),
253 ("full", 2),
254 ];
255 for (word, value) in words {
256 if text.eq_ignore_ascii_case(word) && (!omit_full || value <= 1) {
257 return value;
258 }
259 }
260 default
261}
262
263pub fn sqlite_boolean(text: &str, default: bool) -> bool {
268 sqlite_safety_level(text, true, u8::from(default)) != 0
269}