sql_support/
debug_tools.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/// This module supplies utilities to help with debugging SQL in these components.
6///
7/// To take advantage of this module, you must enable the `debug-tools` feature for
8/// this crate.
9use rusqlite::{functions::Context, types::Value, Connection};
10
11/// Print the entire contents of an arbitrary query. A common usage would be to pass
12/// `SELECT * FROM table`
13#[cfg(feature = "debug-tools")]
14pub fn print_query(conn: &Connection, query: &str) -> rusqlite::Result<()> {
15    use text_table::{Row, Table};
16
17    let mut stmt = conn.prepare(query)?;
18    let mut rows = stmt.query([])?;
19    let mut table = Table::new();
20    let mut titles = Row::empty();
21    for col in rows.as_ref().expect("must have statement").columns() {
22        titles = titles.add_cell(col.name());
23    }
24    table.add_row(titles);
25    while let Some(sql_row) = rows.next()? {
26        let mut table_row = Row::empty();
27        for i in 0..sql_row.as_ref().column_count() {
28            let val = match sql_row.get::<_, Value>(i)? {
29                Value::Null => "null".to_string(),
30                Value::Integer(i) => i.to_string(),
31                Value::Real(f) => f.to_string(),
32                Value::Text(s) => s,
33                Value::Blob(b) => format!("<blob with {} bytes>", b.len()),
34            };
35            table_row = table_row.add_cell(&val);
36        }
37        table.add_row(table_row);
38    }
39    // printstd ends up on stdout - if we just println!() extra buffering seems to happen?
40    use std::io::Write;
41    std::io::stdout()
42        .write_all(format!("query: {query}\n").as_bytes())
43        .unwrap();
44    table.printstd();
45    Ok(())
46}
47
48#[cfg(feature = "debug-tools")]
49#[inline(never)]
50fn dbg(ctx: &Context<'_>) -> rusqlite::Result<Value> {
51    let mut s = Value::Null;
52    for i in 0..ctx.len() {
53        let raw = ctx.get_raw(i);
54        let str_repr = match raw {
55            rusqlite::types::ValueRef::Text(_) => raw.as_str().unwrap().to_owned(),
56            _ => format!("{:?}", raw),
57        };
58        eprint!("{} ", str_repr);
59        s = raw.into();
60    }
61    eprintln!();
62    Ok(s)
63}
64
65#[cfg(not(feature = "debug-tools"))]
66// It would be very bad if the `dbg()` function only existed when the `debug-tools` feature was
67// enabled - you could imagine some crate defining it as a `dev-dependency`, but then shipping
68// sql with an embedded `dbg()` call - tests and CI would pass, but it would fail in the real lib.
69fn dbg(ctx: &Context<'_>) -> rusqlite::Result<Value> {
70    Ok(if ctx.is_empty() {
71        Value::Null
72    } else {
73        ctx.get_raw(ctx.len() - 1).into()
74    })
75}
76
77/// You can call this function to add all sql functions provided by this module
78/// to your connection. The list of supported functions is described below.
79///
80/// *Note:* you must enable the `debug-tools` feature for these functions to perform
81/// as described. If this feature is not enabled, functions of the same name will still
82/// exist, but will be no-ops.
83///
84/// # dbg
85/// `dbg()` is a simple sql function that prints all args to stderr and returns the last argument.
86/// It's useful for instrumenting existing WHERE statements - eg, changing a statement
87/// ```sql
88/// WHERE a.bar = foo
89/// ```
90/// to
91/// ```sql
92/// WHERE dbg("a bar", a.bar) = foo
93/// ```
94/// will print the *literal* `a bar` followed by the *value* of `a.bar`, then return `a.bar`,
95/// so the semantics of the `WHERE` statement do not change.
96/// If you have a series of statements being executed (ie, statements separated by a `;`),
97/// adding a new statement like `SELECT dbg("hello");` is also possible.
98pub fn define_debug_functions(c: &Connection) -> rusqlite::Result<()> {
99    use rusqlite::functions::FunctionFlags;
100    c.create_scalar_function("dbg", -1, FunctionFlags::SQLITE_UTF8, dbg)?;
101    Ok(())
102}
103
104#[cfg(test)]
105mod test {
106    use super::*;
107    use crate::conn_ext::ConnExt;
108
109    #[test]
110    fn test_dbg() {
111        let conn = Connection::open_with_flags(":memory:", rusqlite::OpenFlags::default()).unwrap();
112        define_debug_functions(&conn).unwrap();
113        assert_eq!(
114            conn.conn_ext_query_one::<i64>("SELECT dbg('foo', 0);")
115                .unwrap(),
116            0
117        );
118        assert_eq!(
119            conn.conn_ext_query_one::<String>("SELECT dbg('foo');")
120                .unwrap(),
121            "foo"
122        );
123        assert_eq!(
124            conn.conn_ext_query_one::<Option<String>>("SELECT dbg();")
125                .unwrap(),
126            None
127        );
128    }
129}