• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, NO_PARAMS};
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, NO_PARAMS, 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(NO_PARAMS)?;
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(?);`
pragma<F>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, pragma_value: &dyn ToSql, mut f: F, ) -> Result<()> where F: FnMut(&Row<'_>) -> Result<()>,215     pub fn pragma<F>(
216         &self,
217         schema_name: Option<DatabaseName<'_>>,
218         pragma_name: &str,
219         pragma_value: &dyn ToSql,
220         mut f: F,
221     ) -> Result<()>
222     where
223         F: FnMut(&Row<'_>) -> Result<()>,
224     {
225         let mut sql = Sql::new();
226         sql.push_pragma(schema_name, pragma_name)?;
227         // The argument may be either in parentheses
228         // or it may be separated from the pragma name by an equal sign.
229         // The two syntaxes yield identical results.
230         sql.open_brace();
231         sql.push_value(pragma_value)?;
232         sql.close_brace();
233         let mut stmt = self.prepare(&sql)?;
234         let mut rows = stmt.query(NO_PARAMS)?;
235         while let Some(result_row) = rows.next()? {
236             let row = result_row;
237             f(&row)?;
238         }
239         Ok(())
240     }
241 
242     /// Set a new value to `pragma_name`.
243     ///
244     /// Some pragmas will return the updated value which cannot be retrieved
245     /// with this method.
pragma_update( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, pragma_value: &dyn ToSql, ) -> Result<()>246     pub fn pragma_update(
247         &self,
248         schema_name: Option<DatabaseName<'_>>,
249         pragma_name: &str,
250         pragma_value: &dyn ToSql,
251     ) -> Result<()> {
252         let mut sql = Sql::new();
253         sql.push_pragma(schema_name, pragma_name)?;
254         // The argument may be either in parentheses
255         // or it may be separated from the pragma name by an equal sign.
256         // The two syntaxes yield identical results.
257         sql.push_equal_sign();
258         sql.push_value(pragma_value)?;
259         self.execute_batch(&sql)
260     }
261 
262     /// Set a new value to `pragma_name` and return the updated value.
263     ///
264     /// Only few pragmas automatically return the updated value.
pragma_update_and_check<F, T>( &self, schema_name: Option<DatabaseName<'_>>, pragma_name: &str, pragma_value: &dyn ToSql, f: F, ) -> Result<T> where F: FnOnce(&Row<'_>) -> Result<T>,265     pub fn pragma_update_and_check<F, T>(
266         &self,
267         schema_name: Option<DatabaseName<'_>>,
268         pragma_name: &str,
269         pragma_value: &dyn ToSql,
270         f: F,
271     ) -> Result<T>
272     where
273         F: FnOnce(&Row<'_>) -> Result<T>,
274     {
275         let mut sql = Sql::new();
276         sql.push_pragma(schema_name, pragma_name)?;
277         // The argument may be either in parentheses
278         // or it may be separated from the pragma name by an equal sign.
279         // The two syntaxes yield identical results.
280         sql.push_equal_sign();
281         sql.push_value(pragma_value)?;
282         self.query_row(&sql, NO_PARAMS, f)
283     }
284 }
285 
is_identifier(s: &str) -> bool286 fn is_identifier(s: &str) -> bool {
287     let chars = s.char_indices();
288     for (i, ch) in chars {
289         if i == 0 {
290             if !is_identifier_start(ch) {
291                 return false;
292             }
293         } else if !is_identifier_continue(ch) {
294             return false;
295         }
296     }
297     true
298 }
299 
is_identifier_start(c: char) -> bool300 fn is_identifier_start(c: char) -> bool {
301     (c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z') || c > '\x7F'
302 }
303 
is_identifier_continue(c: char) -> bool304 fn is_identifier_continue(c: char) -> bool {
305     c == '$'
306         || (c >= '0' && c <= '9')
307         || (c >= 'A' && c <= 'Z')
308         || c == '_'
309         || (c >= 'a' && c <= 'z')
310         || c > '\x7F'
311 }
312 
313 #[cfg(test)]
314 mod test {
315     use super::Sql;
316     use crate::pragma;
317     use crate::{Connection, DatabaseName};
318 
319     #[test]
pragma_query_value()320     fn pragma_query_value() {
321         let db = Connection::open_in_memory().unwrap();
322         let user_version: i32 = db
323             .pragma_query_value(None, "user_version", |row| row.get(0))
324             .unwrap();
325         assert_eq!(0, user_version);
326     }
327 
328     #[test]
329     #[cfg(feature = "modern_sqlite")]
pragma_func_query_value()330     fn pragma_func_query_value() {
331         use crate::NO_PARAMS;
332 
333         let db = Connection::open_in_memory().unwrap();
334         let user_version: i32 = db
335             .query_row(
336                 "SELECT user_version FROM pragma_user_version",
337                 NO_PARAMS,
338                 |row| row.get(0),
339             )
340             .unwrap();
341         assert_eq!(0, user_version);
342     }
343 
344     #[test]
pragma_query_no_schema()345     fn pragma_query_no_schema() {
346         let db = Connection::open_in_memory().unwrap();
347         let mut user_version = -1;
348         db.pragma_query(None, "user_version", |row| {
349             user_version = row.get(0)?;
350             Ok(())
351         })
352         .unwrap();
353         assert_eq!(0, user_version);
354     }
355 
356     #[test]
pragma_query_with_schema()357     fn pragma_query_with_schema() {
358         let db = Connection::open_in_memory().unwrap();
359         let mut user_version = -1;
360         db.pragma_query(Some(DatabaseName::Main), "user_version", |row| {
361             user_version = row.get(0)?;
362             Ok(())
363         })
364         .unwrap();
365         assert_eq!(0, user_version);
366     }
367 
368     #[test]
pragma()369     fn pragma() {
370         let db = Connection::open_in_memory().unwrap();
371         let mut columns = Vec::new();
372         db.pragma(None, "table_info", &"sqlite_master", |row| {
373             let column: String = row.get(1)?;
374             columns.push(column);
375             Ok(())
376         })
377         .unwrap();
378         assert_eq!(5, columns.len());
379     }
380 
381     #[test]
382     #[cfg(feature = "modern_sqlite")]
pragma_func()383     fn pragma_func() {
384         let db = Connection::open_in_memory().unwrap();
385         let mut table_info = db.prepare("SELECT * FROM pragma_table_info(?)").unwrap();
386         let mut columns = Vec::new();
387         let mut rows = table_info.query(&["sqlite_master"]).unwrap();
388 
389         while let Some(row) = rows.next().unwrap() {
390             let row = row;
391             let column: String = row.get(1).unwrap();
392             columns.push(column);
393         }
394         assert_eq!(5, columns.len());
395     }
396 
397     #[test]
pragma_update()398     fn pragma_update() {
399         let db = Connection::open_in_memory().unwrap();
400         db.pragma_update(None, "user_version", &1).unwrap();
401     }
402 
403     #[test]
pragma_update_and_check()404     fn pragma_update_and_check() {
405         let db = Connection::open_in_memory().unwrap();
406         let journal_mode: String = db
407             .pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))
408             .unwrap();
409         assert_eq!("off", &journal_mode);
410     }
411 
412     #[test]
is_identifier()413     fn is_identifier() {
414         assert!(pragma::is_identifier("full"));
415         assert!(pragma::is_identifier("r2d2"));
416         assert!(!pragma::is_identifier("sp ce"));
417         assert!(!pragma::is_identifier("semi;colon"));
418     }
419 
420     #[test]
double_quote()421     fn double_quote() {
422         let mut sql = Sql::new();
423         sql.push_schema_name(DatabaseName::Attached(r#"schema";--"#));
424         assert_eq!(r#""schema"";--""#, sql.as_str());
425     }
426 
427     #[test]
wrap_and_escape()428     fn wrap_and_escape() {
429         let mut sql = Sql::new();
430         sql.push_string_literal("value'; --");
431         assert_eq!("'value''; --'", sql.as_str());
432     }
433 
434     #[test]
locking_mode()435     fn locking_mode() {
436         let db = Connection::open_in_memory().unwrap();
437         let r = db.pragma_update(None, "locking_mode", &"exclusive");
438         if cfg!(feature = "extra_check") {
439             r.unwrap_err();
440         } else {
441             r.unwrap();
442         }
443     }
444 }
445