Skip to main content

rusqlite/
pragma.rs

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