/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /// This module supplies utilities to help with debugging SQL in these components. /// /// To take advantage of this module, you must enable the `debug-tools` feature for /// this crate. use rusqlite::{functions::Context, types::Value, Connection}; /// Print the entire contents of an arbitrary query. A common usage would be to pass /// `SELECT * FROM table` #[cfg(feature = "debug-tools")] pub fn print_query(conn: &Connection, query: &str) -> rusqlite::Result<()> { use text_table::{Row, Table}; let mut stmt = conn.prepare(query)?; let mut rows = stmt.query([])?; let mut table = Table::new(); let mut titles = Row::empty(); for col in rows.as_ref().expect("must have statement").columns() { titles = titles.add_cell(col.name()); } table.add_row(titles); while let Some(sql_row) = rows.next()? { let mut table_row = Row::empty(); for i in 0..sql_row.as_ref().column_count() { let val = match sql_row.get::<_, Value>(i)? { Value::Null => "null".to_string(), Value::Integer(i) => i.to_string(), Value::Real(f) => f.to_string(), Value::Text(s) => s, Value::Blob(b) => format!("", b.len()), }; table_row = table_row.add_cell(&val); } table.add_row(table_row); } // printstd ends up on stdout - if we just println!() extra buffering seems to happen? use std::io::Write; std::io::stdout() .write_all(format!("query: {query}\n").as_bytes()) .unwrap(); table.printstd(); Ok(()) } #[cfg(feature = "debug-tools")] #[inline(never)] fn dbg(ctx: &Context<'_>) -> rusqlite::Result { let mut s = Value::Null; for i in 0..ctx.len() { let raw = ctx.get_raw(i); let str_repr = match raw { rusqlite::types::ValueRef::Text(_) => raw.as_str().unwrap().to_owned(), _ => format!("{:?}", raw), }; eprint!("{} ", str_repr); s = raw.into(); } eprintln!(); Ok(s) } #[cfg(not(feature = "debug-tools"))] // It would be very bad if the `dbg()` function only existed when the `debug-tools` feature was // enabled - you could imagine some crate defining it as a `dev-dependency`, but then shipping // sql with an embedded `dbg()` call - tests and CI would pass, but it would fail in the real lib. fn dbg(ctx: &Context<'_>) -> rusqlite::Result { Ok(if ctx.is_empty() { Value::Null } else { ctx.get_raw(ctx.len() - 1).into() }) } /// You can call this function to add all sql functions provided by this module /// to your connection. The list of supported functions is described below. /// /// *Note:* you must enable the `debug-tools` feature for these functions to perform /// as described. If this feature is not enabled, functions of the same name will still /// exist, but will be no-ops. /// /// # dbg /// `dbg()` is a simple sql function that prints all args to stderr and returns the last argument. /// It's useful for instrumenting existing WHERE statements - eg, changing a statement /// ```sql /// WHERE a.bar = foo /// ``` /// to /// ```sql /// WHERE dbg("a bar", a.bar) = foo /// ``` /// will print the *literal* `a bar` followed by the *value* of `a.bar`, then return `a.bar`, /// so the semantics of the `WHERE` statement do not change. /// If you have a series of statements being executed (ie, statements separated by a `;`), /// adding a new statement like `SELECT dbg("hello");` is also possible. pub fn define_debug_functions(c: &Connection) -> rusqlite::Result<()> { use rusqlite::functions::FunctionFlags; c.create_scalar_function("dbg", -1, FunctionFlags::SQLITE_UTF8, dbg)?; Ok(()) } #[cfg(test)] mod test { use super::*; use crate::conn_ext::ConnExt; #[test] fn test_dbg() { let conn = Connection::open_with_flags(":memory:", rusqlite::OpenFlags::default()).unwrap(); define_debug_functions(&conn).unwrap(); assert_eq!( conn.conn_ext_query_one::("SELECT dbg('foo', 0);") .unwrap(), 0 ); assert_eq!( conn.conn_ext_query_one::("SELECT dbg('foo');") .unwrap(), "foo" ); assert_eq!( conn.conn_ext_query_one::>("SELECT dbg();") .unwrap(), None ); } }