Skip to content

Commit

Permalink
Query parser: encode ?? as ?
Browse files Browse the repository at this point in the history
Also borrow from source template instead of copying its parts into separate strings
  • Loading branch information
serprex committed Sep 18, 2024
1 parent 9662a6e commit c0a2825
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 38 deletions.
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand Down
8 changes: 4 additions & 4 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
54 changes: 29 additions & 25 deletions src/sql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,41 @@ pub(crate) mod escape;
mod ser;

#[derive(Clone)]
pub(crate) enum SqlBuilder {
InProgress(Vec<Part>),
pub(crate) enum SqlBuilder<'a> {
InProgress(Vec<Part<'a>>),
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)
Expand All @@ -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");
}
Expand All @@ -70,23 +77,19 @@ impl SqlBuilder {

if let Some(fields) = row::join_column_names::<T>() {
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<String> {
Expand All @@ -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;
Expand Down
14 changes: 7 additions & 7 deletions src/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use crate::{
};

#[must_use]
pub struct Watch<V = Rows> {
pub struct Watch<'parts, V = Rows> {
client: Client,
sql: SqlBuilder,
sql: SqlBuilder<'parts>,
refresh: Option<Duration>,
limit: Option<usize>,
_kind: V,
Expand All @@ -23,7 +23,7 @@ pub struct Watch<V = Rows> {
pub struct Rows;
pub struct Events;

impl<V> Watch<V> {
impl<'parts, V> Watch<'parts, V> {
/// See [`Query::bind()`] for details.
///
/// [`Query::bind()`]: crate::query::Query::bind
Expand Down Expand Up @@ -71,8 +71,8 @@ impl<V> Watch<V> {
}
}

impl Watch<Rows> {
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.
Expand All @@ -91,7 +91,7 @@ impl Watch<Rows> {
}
}

pub fn only_events(self) -> Watch<Events> {
pub fn only_events(self) -> Watch<'a, Events> {
Watch {
client: self.client,
sql: self.sql,
Expand Down Expand Up @@ -126,7 +126,7 @@ impl Watch<Rows> {
}
}

impl Watch<Events> {
impl<'a> Watch<'a, Events> {
pub fn fetch(self) -> Result<EventCursor> {
Ok(EventCursor(self.cursor(true)?))
}
Expand Down

0 comments on commit c0a2825

Please sign in to comment.