Skip to content

Commit 115e2f8

Browse files
committed
feat: add QualifiedIdentifier type
1 parent 3db51fa commit 115e2f8

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased] - ReleaseDate
1010

11+
### Added
12+
13+
- sql: added `QualifiedIdentifier` type to represent qualified identifiers (e.g., `table.column`) safely as client-side bind values
14+
1115
## [0.14.0] - 2025-10-08
1216

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

5660
### Fixed
5761

58-
- client: extract the exception code from `X-ClickHouse-Exception-Code` in case of incorrect 200 OK response
62+
- client: extract the exception code from `X-ClickHouse-Exception-Code` in case of incorrect 200 OK response
5963
that could occur with ClickHouse server up to versions 24.x ([#256]).
6064
- query: pass format as `?default_format` URL parameter instead of using `FORMAT` clause, allowing queries to have
6165
trailing comments and/or semicolons ([#267], [#269], [#311]).

src/sql/bind.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,55 @@ impl Bind for Identifier<'_> {
3131
escape::identifier(self.0, dst).map_err(|err| err.to_string())
3232
}
3333
}
34+
35+
/// A variant on `Identifier` which supports qualifying an identifier. For example,
36+
/// `QualifiedIdentifier("foo", "bar")` will emit the SQL `\`foo\`.\`bar\``.
37+
#[derive(Clone, Copy)]
38+
pub struct QualifiedIdentifier<'a>(pub &'a str, pub &'a str);
39+
40+
#[sealed]
41+
impl Bind for QualifiedIdentifier<'_> {
42+
#[inline]
43+
fn write(&self, dst: &mut impl fmt::Write) -> Result<(), String> {
44+
if !self.0.is_empty() {
45+
escape::identifier(self.0, dst).map_err(|err| err.to_string())?;
46+
dst.write_char('.').map_err(|err| err.to_string())?;
47+
}
48+
escape::identifier(self.1, dst).map_err(|err| err.to_string())
49+
}
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use super::{Bind, QualifiedIdentifier};
55+
56+
fn bind_to_string(b: impl Bind) -> String {
57+
let mut s = String::new();
58+
b.write(&mut s).expect("bind should succeed");
59+
s
60+
}
61+
62+
#[test]
63+
fn test_qualified_identifier() {
64+
assert_eq!(
65+
bind_to_string(QualifiedIdentifier("foo", "bar baz")),
66+
"`foo`.`bar baz`"
67+
);
68+
assert_eq!(
69+
bind_to_string(QualifiedIdentifier("", "bar baz")),
70+
"`bar baz`"
71+
);
72+
73+
assert_eq!(
74+
bind_to_string(QualifiedIdentifier("`'.", ".................````")),
75+
"`\\`\\'.`.`.................\\`\\`\\`\\``"
76+
);
77+
78+
assert_eq!(
79+
bind_to_string(QualifiedIdentifier("クリック", "ハウス")),
80+
"`クリック`.`ハウス`"
81+
);
82+
83+
assert_eq!(bind_to_string(QualifiedIdentifier(" ", " ")), "` `.` `");
84+
}
85+
}

src/sql/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
row::{self, Row},
66
};
77

8-
pub use bind::{Bind, Identifier};
8+
pub use bind::{Bind, Identifier, QualifiedIdentifier};
99

1010
mod bind;
1111
pub(crate) mod escape;

0 commit comments

Comments
 (0)