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