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