Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - ReleaseDate

### Added

- sql: added `QualifiedIdentifier` type to represent qualified identifiers (e.g., `table.column`) safely as client-side bind values

## [0.14.0] - 2025-10-08

### Removed
Expand All @@ -28,13 +32,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
mock server, so it properly handles the response format and automatically disables parsing
`RowBinaryWithNamesAndTypes` header parsing and validation. Additionally, it is not required to call `with_url`
explicitly. See the [updated example](./examples/mock.rs).
- **BREAKING** query: `Query::fetch_bytes()` now expects `impl AsRef<str>` for `format` instead of `Into<String>`.
- **BREAKING** query: `Query::fetch_bytes()` now expects `impl AsRef<str>` for `format` instead of `Into<String>`.
Most usages should not be affected, however, unless passing a custom type that implements the latter but not the former.
([#311])
- query: due to `RowBinaryWithNamesAndTypes` format usage, there might be an impact on fetch performance, which largely
depends on how the dataset is defined. If you notice decreased performance, consider disabling validation by using
`Client::with_validation(false)`.
- serde: it is now possible to deserialize Map ClickHouse type into `HashMap<K, V>` (or `BTreeMap`, `IndexMap`,
- serde: it is now possible to deserialize Map ClickHouse type into `HashMap<K, V>` (or `BTreeMap`, `IndexMap`,
`DashMap`, etc.).
- tls: improved error messages in case of missing TLS features when using HTTPS ([#229]).
- crate: MSRV is now 1.79 due to borrowed rows support redesign in [#247].
Expand All @@ -55,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- client: extract the exception code from `X-ClickHouse-Exception-Code` in case of incorrect 200 OK response
- client: extract the exception code from `X-ClickHouse-Exception-Code` in case of incorrect 200 OK response
that could occur with ClickHouse server up to versions 24.x ([#256]).
- query: pass format as `?default_format` URL parameter instead of using `FORMAT` clause, allowing queries to have
trailing comments and/or semicolons ([#267], [#269], [#311]).
Expand Down
52 changes: 52 additions & 0 deletions src/sql/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,55 @@ impl Bind for Identifier<'_> {
escape::identifier(self.0, dst).map_err(|err| err.to_string())
}
}

/// A variant on `Identifier` which supports qualifying an identifier. For example,
/// `QualifiedIdentifier("foo", "bar")` will emit the SQL `\`foo\`.\`bar\``.
#[derive(Clone, Copy)]
pub struct QualifiedIdentifier<'a>(pub &'a str, pub &'a str);

#[sealed]
impl Bind for QualifiedIdentifier<'_> {
#[inline]
fn write(&self, dst: &mut impl fmt::Write) -> Result<(), String> {
if !self.0.is_empty() {
escape::identifier(self.0, dst).map_err(|err| err.to_string())?;
dst.write_char('.').map_err(|err| err.to_string())?;
}
escape::identifier(self.1, dst).map_err(|err| err.to_string())
}
}

#[cfg(test)]
mod tests {
use super::{Bind, QualifiedIdentifier};

fn bind_to_string(b: impl Bind) -> String {
let mut s = String::new();
b.write(&mut s).expect("bind should succeed");
s
}

#[test]
fn test_qualified_identifier() {
assert_eq!(
bind_to_string(QualifiedIdentifier("foo", "bar baz")),
"`foo`.`bar baz`"
);
assert_eq!(
bind_to_string(QualifiedIdentifier("", "bar baz")),
"`bar baz`"
);

assert_eq!(
bind_to_string(QualifiedIdentifier("`'.", ".................````")),
"`\\`\\'.`.`.................\\`\\`\\`\\``"
);

assert_eq!(
bind_to_string(QualifiedIdentifier("クリック", "ハウス")),
"`クリック`.`ハウス`"
);

assert_eq!(bind_to_string(QualifiedIdentifier(" ", " ")), "` `.` `");
}
}
2 changes: 1 addition & 1 deletion src/sql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
row::{self, Row},
};

pub use bind::{Bind, Identifier};
pub use bind::{Bind, Identifier, QualifiedIdentifier};

mod bind;
pub(crate) mod escape;
Expand Down