Skip to content

Commit

Permalink
feat(sql): impl Bind for all Serialize
Browse files Browse the repository at this point in the history
  • Loading branch information
loyd committed Jan 17, 2022
1 parent 746d026 commit 4d09894
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 139 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
- watch: run `WATCH` queries with `max_execution_time=0`.

### Added
- client: `Client::with_http_client` to use custom `hyper::Client`, e.g. for https ([@iwinux](https://github.com/iwinux)).

### Changed
- watch: run `WATCH` queries with `max_execution_time=0`.
- bind: implement `Bind` for all `Serialize` instances, ([#33](https://github.com/loyd/clickhouse.rs/issues/33)).

## [0.9.3] - 2021-12-21
### Added
- Implement `Primitive` for `f64` and `f32`, [#29](https://github.com/loyd/clickhouse.rs/issues/29).
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ async-compression = { version = "0.3.6", features = ["tokio"], optional = true }
tokio-util = { version = "0.6.0", default-features = false, features = ["codec", "io"], optional = true }
lz4-sys = { version = "1.9.2", optional = true }
clickhouse-rs-cityhash-sys = { version = "0.1.2", optional = true }
sealed = "0.3.0"

[dev-dependencies]
criterion = "0.3.2"
Expand Down
3 changes: 2 additions & 1 deletion examples/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ async fn inserter(client: &Client) -> Result<()> {

async fn fetch(client: &Client) -> Result<()> {
let mut cursor = client
.query("SELECT ?fields FROM some WHERE no BETWEEN ? AND ?")
.query("SELECT ?fields FROM some WHERE name = ? AND no BETWEEN ? AND ?")
.bind("foo")
.bind(500)
.bind(504)
.fetch::<MyRow<'_>>()?;
Expand Down
9 changes: 4 additions & 5 deletions src/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ use hyper::{
client::{connect::Connect, ResponseFuture},
Body, Request,
};
use sealed::sealed;

use crate::sealed::Sealed;

pub trait HttpClient: Sealed + Send + Sync + 'static {
#[sealed]
pub trait HttpClient: Send + Sync + 'static {
fn _request(&self, req: Request<Body>) -> ResponseFuture;
}

impl<C> Sealed for hyper::Client<C> {}

#[sealed]
impl<C> HttpClient for hyper::Client<C>
where
C: Connect + Clone + Send + Sync + 'static,
Expand Down
4 changes: 0 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ mod response;
mod row;
mod rowbinary;

mod sealed {
pub trait Sealed {}
}

const TCP_KEEPALIVE: Duration = Duration::from_secs(60);

// ClickHouse uses 3s by default.
Expand Down
119 changes: 13 additions & 106 deletions src/sql/bind.rs
Original file line number Diff line number Diff line change
@@ -1,125 +1,32 @@
use std::fmt;

use super::escape;
use crate::sealed::Sealed;
use sealed::sealed;
use serde::Serialize;

pub trait Bind: Sealed {
#[doc(hidden)]
fn reserve(&self) -> usize;
use super::{escape, ser};

#[sealed]
pub trait Bind {
#[doc(hidden)]
fn write(&self, dst: impl fmt::Write) -> fmt::Result;
}

macro_rules! impl_num {
($ty:ty, $reserve:literal) => {
impl Sealed for $ty {}

impl Bind for $ty {
#[inline]
fn reserve(&self) -> usize {
$reserve
}

#[inline]
fn write(&self, mut dst: impl fmt::Write) -> fmt::Result {
write!(dst, "{}", self)
}
}
};
}

impl_num!(i8, 4);
impl_num!(u8, 3);
impl_num!(i16, 6);
impl_num!(u16, 5);
impl_num!(i32, 11);
impl_num!(u32, 10);
impl_num!(i64, 20);
impl_num!(u64, 20);
impl_num!(i128, 40);
impl_num!(u128, 39);

impl Sealed for &str {}

impl Bind for &str {
#[inline]
fn reserve(&self) -> usize {
self.len()
}

#[inline]
fn write(&self, dst: impl fmt::Write) -> fmt::Result {
escape::string(self, dst)
}
}

impl Sealed for String {}

impl Bind for String {
#[inline]
fn reserve(&self) -> usize {
self.len()
}

#[inline]
fn write(&self, dst: impl fmt::Write) -> fmt::Result {
escape::string(self, dst)
}
fn write(&self, dst: impl fmt::Write) -> Result<(), String>;
}

impl Sealed for &String {}

impl Bind for &String {
#[inline]
fn reserve(&self) -> usize {
self.len()
}

#[sealed]
impl<S: Serialize> Bind for S {
#[inline]
fn write(&self, dst: impl fmt::Write) -> fmt::Result {
escape::string(self, dst)
}
}

impl<'a, T: Bind> Sealed for &'a [T] {}

impl<'a, T: Bind> Bind for &'a [T] {
#[inline]
fn reserve(&self) -> usize {
let commas_count = self.len().saturating_sub(1);
self.iter().map(Bind::reserve).sum::<usize>() + commas_count + 2
}

#[inline]
fn write(&self, mut dst: impl fmt::Write) -> fmt::Result {
write!(&mut dst, "[")?;
let mut iter = self.iter();
if let Some(item) = iter.next() {
item.write(&mut dst)?;
}
for item in iter {
write!(&mut dst, ",")?;
item.write(&mut dst)?;
}
write!(dst, "]")
fn write(&self, mut dst: impl fmt::Write) -> Result<(), String> {
ser::write_arg(&mut dst, self)
}
}

/// Bound the provided string as an identifier.
/// It can be used for table names, for instance.
pub struct Identifier<'a>(pub &'a str);

impl<'a> Sealed for Identifier<'a> {}

#[sealed]
impl<'a> Bind for Identifier<'a> {
#[inline]
fn reserve(&self) -> usize {
self.0.len() + 2
}

#[inline]
fn write(&self, dst: impl fmt::Write) -> fmt::Result {
escape::identifier(self.0, dst)
fn write(&self, dst: impl fmt::Write) -> Result<(), String> {
escape::identifier(self.0, dst).map_err(|err| err.to_string())
}
}
1 change: 1 addition & 0 deletions src/sql/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) fn identifier(src: &str, dst: impl fmt::Write) -> fmt::Result {
fn escape(src: &str, mut dst: impl fmt::Write, ch: char) -> fmt::Result {
dst.write_char(ch)?;

// TODO: escape newlines?
for (idx, part) in src.split(ch).enumerate() {
if idx > 0 {
dst.write_char('\\')?;
Expand Down
11 changes: 5 additions & 6 deletions src/sql/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::fmt;

use crate::{
error::{Error, Result},
row::{self, Row},
Expand All @@ -9,11 +7,12 @@ pub use bind::{Bind, Identifier};

mod bind;
pub(crate) mod escape;
mod ser;

#[derive(Clone)]
pub(crate) enum SqlBuilder {
InProgress { parts: Vec<Part>, size: usize },
Failed(fmt::Error),
Failed(String),
}

#[derive(Clone)]
Expand Down Expand Up @@ -49,7 +48,7 @@ impl SqlBuilder {
pub(crate) fn bind_arg(&mut self, value: impl Bind) {
if let Self::InProgress { parts, size } = self {
if let Some(part) = parts.iter_mut().find(|p| matches!(p, Part::Arg)) {
let mut s = String::with_capacity(value.reserve());
let mut s = String::new();

if let Err(err) = value.write(&mut s) {
*self = SqlBuilder::Failed(err);
Expand Down Expand Up @@ -101,13 +100,13 @@ impl SqlBuilder {
}
}))
}
Self::Failed(err) => Err(Error::InvalidParams(Box::new(err))),
Self::Failed(err) => Err(Error::InvalidParams(err.into())),
}
}
}

#[cfg(test)]
mod test {
mod tests {
use super::*;

// XXX: need for `derive(Row)`. Provide `row(crate = ..)` instead.
Expand Down
Loading

0 comments on commit 4d09894

Please sign in to comment.