From e6a108510e28e96ac3456ec28886514b6685e829 Mon Sep 17 00:00:00 2001 From: pravic Date: Wed, 18 Sep 2024 01:17:14 +0300 Subject: [PATCH 1/3] feat(query): Display `SqlBuilder` as SQL string. Allow to print the current SQL query, whether it's bound or not. Useful for logging and diagnostics. --- src/sql/mod.rs | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/sql/mod.rs b/src/sql/mod.rs index edb42e7..6a8e07b 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::fmt::{self, Display}; use crate::{ error::{Error, Result}, @@ -11,19 +11,38 @@ mod bind; pub(crate) mod escape; mod ser; -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) enum SqlBuilder { InProgress(Vec), Failed(String), } -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) enum Part { Arg, Fields, Text(String), } +/// Display SQL query as string. +impl fmt::Display for SqlBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SqlBuilder::InProgress(parts) => { + for part in parts { + match part { + Part::Arg => write!(f, "?")?, + Part::Fields => write!(f, "?fields")?, + Part::Text(text) => write!(f, "{text}")?, + } + } + } + SqlBuilder::Failed(err) => write!(f, "{err}")?, + } + Ok(()) + } +} + impl SqlBuilder { pub(crate) fn new(template: &str) -> Self { let mut iter = template.split('?'); @@ -141,9 +160,29 @@ mod tests { #[test] fn bound_args() { let mut sql = SqlBuilder::new("SELECT ?fields FROM test WHERE a = ? AND b < ?"); + assert_eq!( + sql.to_string(), + "SELECT ?fields FROM test WHERE a = ? AND b < ?" + ); + sql.bind_arg("foo"); + assert_eq!( + sql.to_string(), + "SELECT ?fields FROM test WHERE a = 'foo' AND b < ?" + ); + sql.bind_arg(42); + assert_eq!( + sql.to_string(), + "SELECT ?fields FROM test WHERE a = 'foo' AND b < 42" + ); + sql.bind_fields::(); + assert_eq!( + sql.to_string(), + "SELECT `a`,`b` FROM test WHERE a = 'foo' AND b < 42" + ); + assert_eq!( sql.finish().unwrap(), r"SELECT `a`,`b` FROM test WHERE a = 'foo' AND b < 42" From bfaf39c534cfdc62d317c48ef9c82ef943090edd Mon Sep 17 00:00:00 2001 From: pravic Date: Wed, 18 Sep 2024 01:18:56 +0300 Subject: [PATCH 2/3] feat(query): Debug `Query` as SQL string. Allow to print the current SQL query, whether it's bound or not. Useful for logging and diagnostics. --- src/query.rs | 7 +++++++ tests/it/query.rs | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/query.rs b/src/query.rs index 5a03d17..b24d2c1 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,5 +1,6 @@ use hyper::{header::CONTENT_LENGTH, Method, Request}; use serde::Deserialize; +use std::fmt; use url::Url; use crate::headers::with_request_headers; @@ -22,6 +23,12 @@ pub struct Query { sql: SqlBuilder, } +impl fmt::Debug for Query { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{sql}", sql = self.sql) + } +} + impl Query { pub(crate) fn new(client: &Client, template: &str) -> Self { Self { diff --git a/tests/it/query.rs b/tests/it/query.rs index aacab47..927451d 100644 --- a/tests/it/query.rs +++ b/tests/it/query.rs @@ -214,3 +214,14 @@ async fn overrides_client_options() { // should override the client options assert_eq!(value, override_value); } + +#[tokio::test] +async fn prints_query() { + let client = prepare_database!(); + + let q = client.query("SELECT ?fields FROM test WHERE a = ? AND b < ?"); + assert_eq!( + format!("{q:?}"), + "SELECT ?fields FROM test WHERE a = ? AND b < ?" + ); +} From ada630eb3df07e8109cac69e13e7945c9d4e711d Mon Sep 17 00:00:00 2001 From: pravic Date: Fri, 20 Sep 2024 22:18:08 +0300 Subject: [PATCH 3/3] refactor: Add `Query::sql_display` instead. `Debug` for `Query` isn't a good idea. --- src/query.rs | 13 ++++++------- tests/it/query.rs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/query.rs b/src/query.rs index b24d2c1..3189b2f 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,6 +1,6 @@ use hyper::{header::CONTENT_LENGTH, Method, Request}; use serde::Deserialize; -use std::fmt; +use std::fmt::Display; use url::Url; use crate::headers::with_request_headers; @@ -23,12 +23,6 @@ pub struct Query { sql: SqlBuilder, } -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{sql}", sql = self.sql) - } -} - impl Query { pub(crate) fn new(client: &Client, template: &str) -> Self { Self { @@ -37,6 +31,11 @@ impl Query { } } + /// Display SQL query as string. + pub fn sql_display(&self) -> &impl Display { + &self.sql + } + /// Binds `value` to the next `?` in the query. /// /// The `value`, which must either implement [`Serialize`] or be an diff --git a/tests/it/query.rs b/tests/it/query.rs index 927451d..80e0158 100644 --- a/tests/it/query.rs +++ b/tests/it/query.rs @@ -221,7 +221,7 @@ async fn prints_query() { let q = client.query("SELECT ?fields FROM test WHERE a = ? AND b < ?"); assert_eq!( - format!("{q:?}"), + format!("{}", q.sql_display()), "SELECT ?fields FROM test WHERE a = ? AND b < ?" ); }