1 //! Pragma helpers
2
3 use std::ops::Deref;
4
5 use crate::error::Error;
6 use crate::ffi;
7 use crate::types::{ToSql, ToSqlOutput, ValueRef};
8 use crate::{Connection, DatabaseName, Result, Row};
9
10 pub struct Sql {
11 buf: String,
12 }
13
14 impl Sql {
new() -> Sql15 pub fn new() -> Sql {
16 Sql { buf: String::new() }
17 }
18
push_pragma( &mut self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, ) -> Result<()>19 pub fn push_pragma(
20 &mut self,
21 schema_name: Option<DatabaseName<'_>>,
22 pragma_name: &str,
23 ) -> Result<()> {
24 self.push_keyword("PRAGMA")?;
25 self.push_space();
26 if let Some(schema_name) = schema_name {
27 self.push_schema_name(schema_name);
28 self.push_dot();
29 }
30 self.push_keyword(pragma_name)
31 }
32
push_keyword(&mut self, keyword: &str) -> Result<()>33 pub fn push_keyword(&mut self, keyword: &str) -> Result<()> {
34 if !keyword.is_empty() && is_identifier(keyword) {
35 self.buf.push_str(keyword);
36 Ok(())
37 } else {
38 Err(Error::SqliteFailure(
39 ffi::Error::new(ffi::SQLITE_MISUSE),
40 Some(format!("Invalid keyword \"{keyword}\"")),
41 ))
42 }
43 }
44
push_schema_name(&mut self, schema_name: DatabaseName<'_>)45 pub fn push_schema_name(&mut self, schema_name: DatabaseName<'_>) {
46 match schema_name {
47 DatabaseName::Main => self.buf.push_str("main"),
48 DatabaseName::Temp => self.buf.push_str("temp"),
49 DatabaseName::Attached(s) => self.push_identifier(s),
50 };
51 }
52
push_identifier(&mut self, s: &str)53 pub fn push_identifier(&mut self, s: &str) {
54 if is_identifier(s) {
55 self.buf.push_str(s);
56 } else {
57 self.wrap_and_escape(s, '"');
58 }
59 }
60
push_value(&mut self, value: &dyn ToSql) -> Result<()>61 pub fn push_value(&mut self, value: &dyn ToSql) -> Result<()> {
62 let value = value.to_sql()?;
63 let value = match value {
64 ToSqlOutput::Borrowed(v) => v,
65 ToSqlOutput::Owned(ref v) => ValueRef::from(v),
66 #[cfg(feature = "blob")]
67 ToSqlOutput::ZeroBlob(_) => {
68 return Err(Error::SqliteFailure(
69 ffi::Error::new(ffi::SQLITE_MISUSE),
70 Some(format!("Unsupported value \"{value:?}\"")),
71 ));
72 }
73 #[cfg(feature = "functions")]
74 ToSqlOutput::Arg(_) => {
75 return Err(Error::SqliteFailure(
76 ffi::Error::new(ffi::SQLITE_MISUSE),
77 Some(format!("Unsupported value \"{value:?}\"")),
78 ));
79 }
80 #[cfg(feature = "array")]
81 ToSqlOutput::Array(_) => {
82 return Err(Error::SqliteFailure(
83 ffi::Error::new(ffi::SQLITE_MISUSE),
84 Some(format!("Unsupported value \"{value:?}\"")),
85 ));
86 }
87 };
88 match value {
89 ValueRef::Integer(i) => {
90 self.push_int(i);
91 }
92 ValueRef::Real(r) => {
93 self.push_real(r);
94 }
95 ValueRef::Text(s) => {
96 let s = std::str::from_utf8(s)?;
97 self.push_string_literal(s);
98 }
99 _ => {
100 return Err(Error::SqliteFailure(
101 ffi::Error::new(ffi::SQLITE_MISUSE),
102 Some(format!("Unsupported value \"{value:?}\"")),
103 ));
104 }
105 };
106 Ok(())
107 }
108
push_string_literal(&mut self, s: &str)109 pub fn push_string_literal(&mut self, s: &str) {
110 self.wrap_and_escape(s, '\'');
111 }
112
push_int(&mut self, i: i64)113 pub fn push_int(&mut self, i: i64) {
114 self.buf.push_str(&i.to_string());
115 }
116
push_real(&mut self, f: f64)117 pub fn push_real(&mut self, f: f64) {
118 self.buf.push_str(&f.to_string());
119 }
120
push_space(&mut self)121 pub fn push_space(&mut self) {
122 self.buf.push(' ');
123 }
124
push_dot(&mut self)125 pub fn push_dot(&mut self) {
126 self.buf.push('.');
127 }
128
push_equal_sign(&mut self)129 pub fn push_equal_sign(&mut self) {
130 self.buf.push('=');
131 }
132
open_brace(&mut self)133 pub fn open_brace(&mut self) {
134 self.buf.push('(');
135 }
136
close_brace(&mut self)137 pub fn close_brace(&mut self) {
138 self.buf.push(')');
139 }
140
as_str(&self) -> &str141 pub fn as_str(&self) -> &str {
142 &self.buf
143 }
144
wrap_and_escape(&mut self, s: &str, quote: char)145 fn wrap_and_escape(&mut self, s: &str, quote: char) {
146 self.buf.push(quote);
147 let chars = s.chars();
148 for ch in chars {
149 // escape `quote` by doubling it
150 if ch == quote {
151 self.buf.push(ch);
152 }
153 self.buf.push(ch);
154 }
155 self.buf.push(quote);
156 }
157 }
158
159 impl Deref for Sql {
160 type Target = str;
161
deref(&self) -> &str162 fn deref(&self) -> &str {
163 self.as_str()
164 }
165 }
166
167 impl Connection {
168 /// Query the current value of `pragma_name`.
169 ///
170 /// Some pragmas will return multiple rows/values which cannot be retrieved
171 /// with this method.
172 ///
173 /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
174 /// `SELECT user_version FROM pragma_user_version;`
pragma_query_value<T, F>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, f: F, ) -> Result<T> where F: FnOnce(&Row<'_>) -> Result<T>,175 pub fn pragma_query_value<T, F>(
176 &self,
177 schema_name: Option<DatabaseName<'_>>,
178 pragma_name: &str,
179 f: F,
180 ) -> Result<T>
181 where
182 F: FnOnce(&Row<'_>) -> Result<T>,
183 {
184 let mut query = Sql::new();
185 query.push_pragma(schema_name, pragma_name)?;
186 self.query_row(&query, [], f)
187 }
188
189 /// Query the current rows/values of `pragma_name`.
190 ///
191 /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
192 /// `SELECT * FROM pragma_collation_list;`
pragma_query<F>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, mut f: F, ) -> Result<()> where F: FnMut(&Row<'_>) -> Result<()>,193 pub fn pragma_query<F>(
194 &self,
195 schema_name: Option<DatabaseName<'_>>,
196 pragma_name: &str,
197 mut f: F,
198 ) -> Result<()>
199 where
200 F: FnMut(&Row<'_>) -> Result<()>,
201 {
202 let mut query = Sql::new();
203 query.push_pragma(schema_name, pragma_name)?;
204 let mut stmt = self.prepare(&query)?;
205 let mut rows = stmt.query([])?;
206 while let Some(result_row) = rows.next()? {
207 let row = result_row;
208 f(row)?;
209 }
210 Ok(())
211 }
212
213 /// Query the current value(s) of `pragma_name` associated to
214 /// `pragma_value`.
215 ///
216 /// This method can be used with query-only pragmas which need an argument
217 /// (e.g. `table_info('one_tbl')`) or pragmas which returns value(s)
218 /// (e.g. `integrity_check`).
219 ///
220 /// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
221 /// `SELECT * FROM pragma_table_info(?1);`
pragma<F, V>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, pragma_value: V, mut f: F, ) -> Result<()> where F: FnMut(&Row<'_>) -> Result<()>, V: ToSql,222 pub fn pragma<F, V>(
223 &self,
224 schema_name: Option<DatabaseName<'_>>,
225 pragma_name: &str,
226 pragma_value: V,
227 mut f: F,
228 ) -> Result<()>
229 where
230 F: FnMut(&Row<'_>) -> Result<()>,
231 V: ToSql,
232 {
233 let mut sql = Sql::new();
234 sql.push_pragma(schema_name, pragma_name)?;
235 // The argument may be either in parentheses
236 // or it may be separated from the pragma name by an equal sign.
237 // The two syntaxes yield identical results.
238 sql.open_brace();
239 sql.push_value(&pragma_value)?;
240 sql.close_brace();
241 let mut stmt = self.prepare(&sql)?;
242 let mut rows = stmt.query([])?;
243 while let Some(result_row) = rows.next()? {
244 let row = result_row;
245 f(row)?;
246 }
247 Ok(())
248 }
249
250 /// Set a new value to `pragma_name`.
251 ///
252 /// Some pragmas will return the updated value which cannot be retrieved
253 /// with this method.
pragma_update<V>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, pragma_value: V, ) -> Result<()> where V: ToSql,254 pub fn pragma_update<V>(
255 &self,
256 schema_name: Option<DatabaseName<'_>>,
257 pragma_name: &str,
258 pragma_value: V,
259 ) -> Result<()>
260 where
261 V: ToSql,
262 {
263 let mut sql = Sql::new();
264 sql.push_pragma(schema_name, pragma_name)?;
265 // The argument may be either in parentheses
266 // or it may be separated from the pragma name by an equal sign.
267 // The two syntaxes yield identical results.
268 sql.push_equal_sign();
269 sql.push_value(&pragma_value)?;
270 self.execute_batch(&sql)
271 }
272
273 /// Set a new value to `pragma_name` and return the updated value.
274 ///
275 /// Only few pragmas automatically return the updated value.
pragma_update_and_check<F, T, V>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, pragma_value: V, f: F, ) -> Result<T> where F: FnOnce(&Row<'_>) -> Result<T>, V: ToSql,276 pub fn pragma_update_and_check<F, T, V>(
277 &self,
278 schema_name: Option<DatabaseName<'_>>,
279 pragma_name: &str,
280 pragma_value: V,
281 f: F,
282 ) -> Result<T>
283 where
284 F: FnOnce(&Row<'_>) -> Result<T>,
285 V: ToSql,
286 {
287 let mut sql = Sql::new();
288 sql.push_pragma(schema_name, pragma_name)?;
289 // The argument may be either in parentheses
290 // or it may be separated from the pragma name by an equal sign.
291 // The two syntaxes yield identical results.
292 sql.push_equal_sign();
293 sql.push_value(&pragma_value)?;
294 self.query_row(&sql, [], f)
295 }
296 }
297
is_identifier(s: &str) -> bool298 fn is_identifier(s: &str) -> bool {
299 let chars = s.char_indices();
300 for (i, ch) in chars {
301 if i == 0 {
302 if !is_identifier_start(ch) {
303 return false;
304 }
305 } else if !is_identifier_continue(ch) {
306 return false;
307 }
308 }
309 true
310 }
311
is_identifier_start(c: char) -> bool312 fn is_identifier_start(c: char) -> bool {
313 c.is_ascii_uppercase() || c == '_' || c.is_ascii_lowercase() || c > '\x7F'
314 }
315
is_identifier_continue(c: char) -> bool316 fn is_identifier_continue(c: char) -> bool {
317 c == '$'
318 || c.is_ascii_digit()
319 || c.is_ascii_uppercase()
320 || c == '_'
321 || c.is_ascii_lowercase()
322 || c > '\x7F'
323 }
324
325 #[cfg(test)]
326 mod test {
327 use super::Sql;
328 use crate::pragma;
329 use crate::{Connection, DatabaseName, Result};
330
331 #[test]
pragma_query_value() -> Result<()>332 fn pragma_query_value() -> Result<()> {
333 let db = Connection::open_in_memory()?;
334 let user_version: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
335 assert_eq!(0, user_version);
336 Ok(())
337 }
338
339 #[test]
340 #[cfg(feature = "modern_sqlite")]
pragma_func_query_value() -> Result<()>341 fn pragma_func_query_value() -> Result<()> {
342 let db = Connection::open_in_memory()?;
343 let user_version: i32 = db.one_column("SELECT user_version FROM pragma_user_version")?;
344 assert_eq!(0, user_version);
345 Ok(())
346 }
347
348 #[test]
pragma_query_no_schema() -> Result<()>349 fn pragma_query_no_schema() -> Result<()> {
350 let db = Connection::open_in_memory()?;
351 let mut user_version = -1;
352 db.pragma_query(None, "user_version", |row| {
353 user_version = row.get(0)?;
354 Ok(())
355 })?;
356 assert_eq!(0, user_version);
357 Ok(())
358 }
359
360 #[test]
pragma_query_with_schema() -> Result<()>361 fn pragma_query_with_schema() -> Result<()> {
362 let db = Connection::open_in_memory()?;
363 let mut user_version = -1;
364 db.pragma_query(Some(DatabaseName::Main), "user_version", |row| {
365 user_version = row.get(0)?;
366 Ok(())
367 })?;
368 assert_eq!(0, user_version);
369 Ok(())
370 }
371
372 #[test]
pragma() -> Result<()>373 fn pragma() -> Result<()> {
374 let db = Connection::open_in_memory()?;
375 let mut columns = Vec::new();
376 db.pragma(None, "table_info", "sqlite_master", |row| {
377 let column: String = row.get(1)?;
378 columns.push(column);
379 Ok(())
380 })?;
381 assert_eq!(5, columns.len());
382 Ok(())
383 }
384
385 #[test]
386 #[cfg(feature = "modern_sqlite")]
pragma_func() -> Result<()>387 fn pragma_func() -> Result<()> {
388 let db = Connection::open_in_memory()?;
389 let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?1)")?;
390 let mut columns = Vec::new();
391 let mut rows = table_info.query(["sqlite_master"])?;
392
393 while let Some(row) = rows.next()? {
394 let column: String = row.get(1)?;
395 columns.push(column);
396 }
397 assert_eq!(5, columns.len());
398 Ok(())
399 }
400
401 #[test]
pragma_update() -> Result<()>402 fn pragma_update() -> Result<()> {
403 let db = Connection::open_in_memory()?;
404 db.pragma_update(None, "user_version", 1)
405 }
406
407 #[test]
pragma_update_and_check() -> Result<()>408 fn pragma_update_and_check() -> Result<()> {
409 let db = Connection::open_in_memory()?;
410 let journal_mode: String =
411 db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
412 assert!(
413 journal_mode == "off" || journal_mode == "memory",
414 "mode: {journal_mode:?}"
415 );
416 // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
417 let mode =
418 db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get::<_, String>(0))?;
419 assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
420
421 let param: &dyn crate::ToSql = &"OFF";
422 let mode =
423 db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?;
424 assert!(mode == "off" || mode == "memory", "mode: {mode:?}");
425 Ok(())
426 }
427
428 #[test]
is_identifier()429 fn is_identifier() {
430 assert!(pragma::is_identifier("full"));
431 assert!(pragma::is_identifier("r2d2"));
432 assert!(!pragma::is_identifier("sp ce"));
433 assert!(!pragma::is_identifier("semi;colon"));
434 }
435
436 #[test]
double_quote()437 fn double_quote() {
438 let mut sql = Sql::new();
439 sql.push_schema_name(DatabaseName::Attached(r#"schema";--"#));
440 assert_eq!(r#""schema"";--""#, sql.as_str());
441 }
442
443 #[test]
wrap_and_escape()444 fn wrap_and_escape() {
445 let mut sql = Sql::new();
446 sql.push_string_literal("value'; --");
447 assert_eq!("'value''; --'", sql.as_str());
448 }
449
450 #[test]
locking_mode() -> Result<()>451 fn locking_mode() -> Result<()> {
452 let db = Connection::open_in_memory()?;
453 let r = db.pragma_update(None, "locking_mode", "exclusive");
454 if cfg!(feature = "extra_check") {
455 r.unwrap_err();
456 } else {
457 r?;
458 }
459 Ok(())
460 }
461 }
462