1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4
5#[macro_use]
6extern crate static_assertions;
7
8use self::{error::Result, http_client::HttpClient};
9use std::{collections::HashMap, fmt::Display, sync::Arc};
10
11pub use self::{compression::Compression, row::Row};
12pub use clickhouse_derive::Row;
13
14pub mod error;
15pub mod insert;
16#[cfg(feature = "inserter")]
17pub mod inserter;
18pub mod query;
19pub mod serde;
20pub mod sql;
21#[cfg(feature = "test-util")]
22pub mod test;
23#[cfg(feature = "watch")]
24pub mod watch;
25
26mod bytes_ext;
27mod compression;
28mod cursors;
29mod headers;
30mod http_client;
31mod request_body;
32mod response;
33mod row;
34mod rowbinary;
35#[cfg(feature = "inserter")]
36mod ticks;
37
38#[derive(Clone)]
40pub struct Client {
41 http: Arc<dyn HttpClient>,
42
43 url: String,
44 database: Option<String>,
45 authentication: Authentication,
46 compression: Compression,
47 options: HashMap<String, String>,
48 headers: HashMap<String, String>,
49 products_info: Vec<ProductInfo>,
50}
51
52#[derive(Clone)]
53struct ProductInfo {
54 name: String,
55 version: String,
56}
57
58impl Display for ProductInfo {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(f, "{}/{}", self.name, self.version)
61 }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub(crate) enum Authentication {
66 Credentials {
67 user: Option<String>,
68 password: Option<String>,
69 },
70 Jwt {
71 access_token: String,
72 },
73}
74
75impl Default for Authentication {
76 fn default() -> Self {
77 Self::Credentials {
78 user: None,
79 password: None,
80 }
81 }
82}
83
84impl Default for Client {
85 fn default() -> Self {
86 Self::with_http_client(http_client::default())
87 }
88}
89
90impl Client {
91 pub fn with_http_client(client: impl HttpClient) -> Self {
95 Self {
96 http: Arc::new(client),
97 url: String::new(),
98 database: None,
99 authentication: Authentication::default(),
100 compression: Compression::default(),
101 options: HashMap::new(),
102 headers: HashMap::new(),
103 products_info: Vec::default(),
104 }
105 }
106
107 pub fn with_url(mut self, url: impl Into<String>) -> Self {
115 self.url = url.into();
116 self
117 }
118
119 pub fn with_database(mut self, database: impl Into<String>) -> Self {
127 self.database = Some(database.into());
128 self
129 }
130
131 pub fn with_user(mut self, user: impl Into<String>) -> Self {
142 match self.authentication {
143 Authentication::Jwt { .. } => {
144 panic!("`user` cannot be set together with `access_token`");
145 }
146 Authentication::Credentials { password, .. } => {
147 self.authentication = Authentication::Credentials {
148 user: Some(user.into()),
149 password,
150 };
151 }
152 }
153 self
154 }
155
156 pub fn with_password(mut self, password: impl Into<String>) -> Self {
167 match self.authentication {
168 Authentication::Jwt { .. } => {
169 panic!("`password` cannot be set together with `access_token`");
170 }
171 Authentication::Credentials { user, .. } => {
172 self.authentication = Authentication::Credentials {
173 user,
174 password: Some(password.into()),
175 };
176 }
177 }
178 self
179 }
180
181 pub fn with_access_token(mut self, access_token: impl Into<String>) -> Self {
194 match self.authentication {
195 Authentication::Credentials { user, password }
196 if user.is_some() || password.is_some() =>
197 {
198 panic!("`access_token` cannot be set together with `user` or `password`");
199 }
200 _ => {
201 self.authentication = Authentication::Jwt {
202 access_token: access_token.into(),
203 }
204 }
205 }
206 self
207 }
208
209 pub fn with_compression(mut self, compression: Compression) -> Self {
219 self.compression = compression;
220 self
221 }
222
223 pub fn with_option(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
231 self.options.insert(name.into(), value.into());
232 self
233 }
234
235 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
243 self.headers.insert(name.into(), value.into());
244 self
245 }
246
247 pub fn with_product_info(
286 mut self,
287 product_name: impl Into<String>,
288 product_version: impl Into<String>,
289 ) -> Self {
290 self.products_info.push(ProductInfo {
291 name: product_name.into(),
292 version: product_version.into(),
293 });
294 self
295 }
296
297 pub fn insert<T: Row>(&self, table: &str) -> Result<insert::Insert<T>> {
302 insert::Insert::new(self, table)
303 }
304
305 #[cfg(feature = "inserter")]
307 pub fn inserter<T: Row>(&self, table: &str) -> Result<inserter::Inserter<T>> {
308 inserter::Inserter::new(self, table)
309 }
310
311 pub fn query(&self, query: &str) -> query::Query {
313 query::Query::new(self, query)
314 }
315
316 #[cfg(feature = "watch")]
321 pub fn watch(&self, query: &str) -> watch::Watch {
322 watch::Watch::new(self, query)
323 }
324
325 pub(crate) fn add_option(&mut self, name: impl Into<String>, value: impl Into<String>) {
328 self.options.insert(name.into(), value.into());
329 }
330}
331
332#[doc(hidden)]
335pub mod _priv {
336 #[cfg(feature = "lz4")]
337 pub fn lz4_compress(uncompressed: &[u8]) -> super::Result<bytes::Bytes> {
338 crate::compression::lz4::compress(uncompressed)
339 }
340}
341
342#[cfg(test)]
343mod client_tests {
344 use crate::{Authentication, Client};
345
346 #[test]
347 fn it_can_use_credentials_auth() {
348 assert_eq!(
349 Client::default()
350 .with_user("bob")
351 .with_password("secret")
352 .authentication,
353 Authentication::Credentials {
354 user: Some("bob".into()),
355 password: Some("secret".into()),
356 }
357 );
358 }
359
360 #[test]
361 fn it_can_use_credentials_auth_user_only() {
362 assert_eq!(
363 Client::default().with_user("alice").authentication,
364 Authentication::Credentials {
365 user: Some("alice".into()),
366 password: None,
367 }
368 );
369 }
370
371 #[test]
372 fn it_can_use_credentials_auth_password_only() {
373 assert_eq!(
374 Client::default().with_password("secret").authentication,
375 Authentication::Credentials {
376 user: None,
377 password: Some("secret".into()),
378 }
379 );
380 }
381
382 #[test]
383 fn it_can_override_credentials_auth() {
384 assert_eq!(
385 Client::default()
386 .with_user("bob")
387 .with_password("secret")
388 .with_user("alice")
389 .with_password("something_else")
390 .authentication,
391 Authentication::Credentials {
392 user: Some("alice".into()),
393 password: Some("something_else".into()),
394 }
395 );
396 }
397
398 #[test]
399 fn it_can_use_jwt_auth() {
400 assert_eq!(
401 Client::default().with_access_token("my_jwt").authentication,
402 Authentication::Jwt {
403 access_token: "my_jwt".into(),
404 }
405 );
406 }
407
408 #[test]
409 fn it_can_override_jwt_auth() {
410 assert_eq!(
411 Client::default()
412 .with_access_token("my_jwt")
413 .with_access_token("my_jwt_2")
414 .authentication,
415 Authentication::Jwt {
416 access_token: "my_jwt_2".into(),
417 }
418 );
419 }
420
421 #[test]
422 #[should_panic(expected = "`access_token` cannot be set together with `user` or `password`")]
423 fn it_cannot_use_jwt_after_with_user() {
424 let _ = Client::default()
425 .with_user("bob")
426 .with_access_token("my_jwt");
427 }
428
429 #[test]
430 #[should_panic(expected = "`access_token` cannot be set together with `user` or `password`")]
431 fn it_cannot_use_jwt_after_with_password() {
432 let _ = Client::default()
433 .with_password("secret")
434 .with_access_token("my_jwt");
435 }
436
437 #[test]
438 #[should_panic(expected = "`access_token` cannot be set together with `user` or `password`")]
439 fn it_cannot_use_jwt_after_both_with_user_and_with_password() {
440 let _ = Client::default()
441 .with_user("alice")
442 .with_password("secret")
443 .with_access_token("my_jwt");
444 }
445
446 #[test]
447 #[should_panic(expected = "`user` cannot be set together with `access_token`")]
448 fn it_cannot_use_with_user_after_jwt() {
449 let _ = Client::default()
450 .with_access_token("my_jwt")
451 .with_user("alice");
452 }
453
454 #[test]
455 #[should_panic(expected = "`password` cannot be set together with `access_token`")]
456 fn it_cannot_use_with_password_after_jwt() {
457 let _ = Client::default()
458 .with_access_token("my_jwt")
459 .with_password("secret");
460 }
461}