1use 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 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 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 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 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 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 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 sql.push_equal_sign();
242 sql.push_value(&pragma_value)?;
243 self.execute_batch(&sql)
244 }
245
246 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 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 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}