From c0a2825e9a8562ed1e0615ccd5258347f066d126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 18 Sep 2024 15:22:13 +0000 Subject: [PATCH] Query parser: encode ?? as ? Also borrow from source template instead of copying its parts into separate strings --- src/lib.rs | 4 ++-- src/query.rs | 8 ++++---- src/sql/mod.rs | 54 +++++++++++++++++++++++++++----------------------- src/watch.rs | 14 ++++++------- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 692211fd..37360f87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -299,7 +299,7 @@ impl Client { } /// Starts a new SELECT/DDL query. - pub fn query(&self, query: &str) -> query::Query { + pub fn query<'a>(&self, query: &'a str) -> query::Query<'a> { query::Query::new(self, query) } @@ -308,7 +308,7 @@ impl Client { /// The `query` can be either the table name or a SELECT query. /// In the second case, a new LV table is created. #[cfg(feature = "watch")] - pub fn watch(&self, query: &str) -> watch::Watch { + pub fn watch<'a>(&self, query: &'a str) -> watch::Watch<'a> { watch::Watch::new(self, query) } diff --git a/src/query.rs b/src/query.rs index 5a03d178..14b35327 100644 --- a/src/query.rs +++ b/src/query.rs @@ -17,13 +17,13 @@ const MAX_QUERY_LEN_TO_USE_GET: usize = 8192; #[must_use] #[derive(Clone)] -pub struct Query { +pub struct Query<'a> { client: Client, - sql: SqlBuilder, + sql: SqlBuilder<'a>, } -impl Query { - pub(crate) fn new(client: &Client, template: &str) -> Self { +impl<'parts> Query<'parts> { + pub(crate) fn new(client: &Client, template: &'parts str) -> Self { Self { client: client.clone(), sql: SqlBuilder::new(template), diff --git a/src/sql/mod.rs b/src/sql/mod.rs index edb42e71..92a8f02f 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -12,34 +12,41 @@ pub(crate) mod escape; mod ser; #[derive(Clone)] -pub(crate) enum SqlBuilder { - InProgress(Vec), +pub(crate) enum SqlBuilder<'a> { + InProgress(Vec>), Failed(String), } #[derive(Clone)] -pub(crate) enum Part { +pub(crate) enum Part<'a> { Arg, Fields, - Text(String), + Str(&'a str), + String(String), } -impl SqlBuilder { - pub(crate) fn new(template: &str) -> Self { - let mut iter = template.split('?'); - let prefix = String::from(iter.next().unwrap()); - let mut parts = vec![Part::Text(prefix)]; - - for s in iter { - let text = if let Some(text) = s.strip_prefix("fields") { +impl<'parts> SqlBuilder<'parts> { + pub(crate) fn new(template: &'parts str) -> Self { + let mut parts = Vec::new(); + let mut rest = template; + while let Some(idx) = rest.find('?') { + if idx != 0 { + parts.push(Part::Str(&rest[..idx])); + } + rest = &rest[idx + 1..]; + if let Some(restqq) = rest.strip_prefix('?') { + parts.push(Part::Str("?")); + rest = restqq; + } else if let Some(restfields) = rest.strip_prefix("fields") { parts.push(Part::Fields); - text + rest = restfields; } else { parts.push(Part::Arg); - s - }; + } + } - parts.push(Part::Text(text.into())); + if !rest.is_empty() { + parts.push(Part::Str(rest)); } SqlBuilder::InProgress(parts) @@ -57,7 +64,7 @@ impl SqlBuilder { return self.error(format_args!("invalid argument: {err}")); } - *part = Part::Text(s); + *part = Part::String(s); } else { self.error("unexpected bind(), all arguments are already bound"); } @@ -70,23 +77,19 @@ impl SqlBuilder { if let Some(fields) = row::join_column_names::() { for part in parts.iter_mut().filter(|p| matches!(p, Part::Fields)) { - *part = Part::Text(fields.clone()); + *part = Part::String(fields.clone()); } } else if parts.iter().any(|p| matches!(p, Part::Fields)) { self.error("argument ?fields cannot be used with non-struct row types"); } } - pub(crate) fn append(&mut self, suffix: &str) { + pub(crate) fn append(&mut self, part: &'static str) { let Self::InProgress(parts) = self else { return; }; - if let Some(Part::Text(text)) = parts.last_mut() { - text.push_str(suffix); - } else { - // Do nothing, it will fail in `finish()`. - } + parts.push(Part::Str(part)); } pub(crate) fn finish(mut self) -> Result { @@ -95,7 +98,8 @@ impl SqlBuilder { if let Self::InProgress(parts) = &self { for part in parts { match part { - Part::Text(text) => sql.push_str(text), + Part::Str(text) => sql.push_str(text), + Part::String(text) => sql.push_str(&text[..]), Part::Arg => { self.error("unbound query argument"); break; diff --git a/src/watch.rs b/src/watch.rs index 8833c139..96cdc66e 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -12,9 +12,9 @@ use crate::{ }; #[must_use] -pub struct Watch { +pub struct Watch<'parts, V = Rows> { client: Client, - sql: SqlBuilder, + sql: SqlBuilder<'parts>, refresh: Option, limit: Option, _kind: V, @@ -23,7 +23,7 @@ pub struct Watch { pub struct Rows; pub struct Events; -impl Watch { +impl<'parts, V> Watch<'parts, V> { /// See [`Query::bind()`] for details. /// /// [`Query::bind()`]: crate::query::Query::bind @@ -71,8 +71,8 @@ impl Watch { } } -impl Watch { - pub(crate) fn new(client: &Client, template: &str) -> Self { +impl<'a> Watch<'a, Rows> { + pub(crate) fn new(client: &Client, template: &'a str) -> Self { let client = client .clone() // TODO: check again. @@ -91,7 +91,7 @@ impl Watch { } } - pub fn only_events(self) -> Watch { + pub fn only_events(self) -> Watch<'a, Events> { Watch { client: self.client, sql: self.sql, @@ -126,7 +126,7 @@ impl Watch { } } -impl Watch { +impl<'a> Watch<'a, Events> { pub fn fetch(self) -> Result { Ok(EventCursor(self.cursor(true)?)) }