1use crate::auth::{AccessTokenAuth, AccessTokenFileAuth, Auth, BasicAuth, KeyPairAuth};
16use crate::capability::Capability;
17use crate::client_mgr::{GLOBAL_CLIENT_MANAGER, GLOBAL_RUNTIME};
18use crate::error_code::{need_refresh_token, ResponseWithErrorCode};
19use crate::global_cookie_store::GlobalCookieStore;
20use crate::login::{
21 LoginRequest, LoginResponseResult, RefreshResponse, RefreshSessionTokenRequest,
22 SessionTokenInfo,
23};
24use crate::presign::{presign_upload_to_stage, PresignMode, PresignedResponse, Reader};
25use crate::response::LoadResponse;
26use crate::stage::StageLocation;
27use crate::{
28 error::{Error, RequestKind, Result},
29 request::{PaginationConfig, QueryRequest, StageAttachmentConfig},
30 response::QueryResponse,
31 session::SessionState,
32 QueryStats,
33};
34use crate::{Page, Pages};
35use arrow_array::RecordBatch;
36use arrow_ipc::reader::StreamReader;
37use base64::engine::general_purpose::URL_SAFE;
38use base64::Engine;
39use bytes::Bytes;
40use log::{debug, error, info, warn};
41use once_cell::sync::Lazy;
42use parking_lot::Mutex;
43use percent_encoding::percent_decode_str;
44use reqwest::cookie::CookieStore;
45use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
46use reqwest::multipart::{Form, Part};
47use reqwest::{
48 Body, Client as HttpClient, Error as ReqwestError, Request, RequestBuilder, StatusCode,
49};
50use semver::Version;
51use serde::{de, Deserialize};
52use serde_json::{json, Value};
53use std::collections::{BTreeMap, HashMap};
54use std::error::Error as StdError;
55use std::io::ErrorKind;
56use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
57use std::sync::Arc;
58use std::time::{Duration, Instant};
59use tokio::time::sleep;
60use tokio_retry::strategy::jitter;
61use tokio_stream::StreamExt;
62use tokio_util::io::ReaderStream;
63use url::Url;
64
65const HEADER_QUERY_ID: &str = "X-DATABEND-QUERY-ID";
66const HEADER_TENANT: &str = "X-DATABEND-TENANT";
67const HEADER_STICKY_NODE: &str = "X-DATABEND-STICKY-NODE";
68const HEADER_WAREHOUSE: &str = "X-DATABEND-WAREHOUSE";
69const HEADER_STAGE_NAME: &str = "X-DATABEND-STAGE-NAME";
70const HEADER_ROUTE_HINT: &str = "X-DATABEND-ROUTE-HINT";
71const TXN_STATE_ACTIVE: &str = "Active";
72const HEADER_SQL: &str = "X-DATABEND-SQL";
73const HEADER_QUERY_CONTEXT: &str = "X-DATABEND-QUERY-CONTEXT";
74const HEADER_SESSION_ID: &str = "X-DATABEND-SESSION-ID";
75const CONTENT_TYPE_ARROW: &str = "application/vnd.apache.arrow.stream";
76const CONTENT_TYPE_ARROW_OR_JSON: &str = "application/vnd.apache.arrow.stream";
77const DEFAULT_USERNAME: &str = "root";
78
79static VERSION: Lazy<String> = Lazy::new(|| {
80 let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
81 version.to_string()
82});
83
84#[derive(Clone)]
85pub(crate) struct QueryState {
86 pub node_id: String,
87 pub last_access_time: Arc<Mutex<Instant>>,
88 pub timeout_secs: u64,
89}
90
91impl QueryState {
92 pub fn need_heartbeat(&self, now: Instant) -> bool {
93 let t = *self.last_access_time.lock();
94 now.duration_since(t).as_secs() > self.timeout_secs / 2
95 }
96}
97
98struct HttpResponseData {
99 status: StatusCode,
100 headers: HeaderMap,
101 body: Bytes,
102}
103
104pub struct APIClient {
105 pub(crate) session_id: String,
106 cli: HttpClient,
107 scheme: String,
108 host: String,
109 port: u16,
110
111 endpoint: Url,
112
113 auth: Arc<dyn Auth>,
114
115 tenant: Option<String>,
118 warehouse: Mutex<Option<String>>,
119 session_state: Mutex<SessionState>,
120 route_hint: RouteHintGenerator,
121
122 disable_login: bool,
123 query_result_format: String,
124 disable_session_token: bool,
125 session_token_info: Option<Arc<Mutex<(SessionTokenInfo, Instant)>>>,
126
127 closed: AtomicBool,
128
129 server_version: Option<Version>,
130
131 wait_time_secs: Option<i64>,
132 max_rows_in_buffer: Option<i64>,
133 max_rows_per_page: Option<i64>,
134
135 connect_timeout: Duration,
136 page_request_timeout: Duration,
137
138 tls_ca_file: Option<String>,
139
140 presign: Mutex<PresignMode>,
141 last_node_id: Mutex<Option<String>>,
142 last_query_id: Mutex<Option<String>>,
143
144 capability: Capability,
145
146 queries_need_heartbeat: Mutex<HashMap<String, QueryState>>,
147
148 retry_count: u32,
149 retry_delay_secs: u64,
150}
151
152impl Drop for APIClient {
153 fn drop(&mut self) {
154 self.close_with_spawn()
155 }
156}
157
158impl APIClient {
159 fn has_transient_io_source(mut source: Option<&(dyn StdError + 'static)>) -> bool {
160 while let Some(err) = source {
161 if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
162 match io_err.kind() {
163 ErrorKind::TimedOut
164 | ErrorKind::UnexpectedEof
165 | ErrorKind::ConnectionReset
166 | ErrorKind::ConnectionAborted
167 | ErrorKind::BrokenPipe
168 | ErrorKind::NotConnected
169 | ErrorKind::WouldBlock
170 | ErrorKind::Interrupted => return true,
171 _ => {}
172 }
173 }
174 source = err.source();
175 }
176 false
177 }
178
179 fn retry_reason_for_reqwest(err: &ReqwestError) -> Option<&'static str> {
180 if err.is_timeout() {
181 Some("request timeout")
182 } else if err.is_connect() {
183 Some("connection error")
184 } else if err.is_request() {
185 Some("request error")
186 } else if err.is_body() || err.is_decode() {
187 if Self::has_transient_io_source(err.source()) {
188 Some("response read transient I/O error")
189 } else {
190 None
191 }
192 } else {
193 None
194 }
195 }
196
197 fn request_query_id(request: &Request) -> Option<String> {
198 request
199 .headers()
200 .get(HEADER_QUERY_ID)
201 .and_then(|v| v.to_str().ok())
202 .map(|v| v.to_string())
203 }
204
205 fn with_request_context(
206 reqwest_error: ReqwestError,
207 request_kind: &RequestKind,
208 request: &Request,
209 retry_times: Option<u32>,
210 ) -> Error {
211 let error: Error = reqwest_error.into();
212 let error = error.with_context(request_kind.clone());
213 let error = if let Some(retry_times) = retry_times {
214 error.with_retry_times(retry_times)
215 } else {
216 error
217 };
218 if let Some(query_id) = Self::request_query_id(request) {
219 error.with_query_id(query_id)
220 } else {
221 error
222 }
223 }
224
225 fn status_body_to_error(status: StatusCode, body: &[u8]) -> Error {
226 match serde_json::from_slice::<ResponseWithErrorCode>(body) {
227 Ok(resp) if status == StatusCode::UNAUTHORIZED => Error::AuthFailure(resp.error),
228 Ok(resp) => Error::Logic(status, resp.error),
229 Err(_) => Error::response_error(status, body),
230 }
231 }
232
233 pub async fn new(dsn: &str, name: Option<String>) -> Result<Arc<Self>> {
234 let mut client = Self::from_dsn(dsn).await?;
235 client.build_client(name).await?;
236 if !client.disable_login {
237 client.login().await?;
238 }
239 if client.session_id.is_empty() {
240 client.session_id = format!("no_login_{}", uuid::Uuid::new_v4());
241 }
242 let client = Arc::new(client);
243 client.check_presign().await?;
244 GLOBAL_CLIENT_MANAGER.register_client(client.clone()).await;
245 Ok(client)
246 }
247
248 pub fn capability(&self) -> &Capability {
249 &self.capability
250 }
251
252 fn set_presign_mode(&self, mode: PresignMode) {
253 *self.presign.lock() = mode
254 }
255 fn get_presign_mode(&self) -> PresignMode {
256 *self.presign.lock()
257 }
258
259 async fn from_dsn(dsn: &str) -> Result<Self> {
260 let u = Url::parse(dsn)?;
261 let mut client = Self::default();
262 if let Some(host) = u.host_str() {
263 client.host = host.to_string();
264 }
265
266 let username = u.username().to_string();
267 if !username.is_empty() {
268 let password = u.password().unwrap_or_default();
269 let password = percent_decode_str(password).decode_utf8()?;
270 client.auth = Arc::new(BasicAuth::new(&username, password));
271 }
272
273 let mut session_state = SessionState::default();
274
275 let database = u.path().trim_start_matches('/');
276 if !database.is_empty() {
277 session_state.set_database(database);
278 }
279
280 let mut private_key_file: Option<String> = None;
281 let mut private_key_passphrase_file: Option<String> = None;
282
283 let mut scheme = "https";
284 for (k, v) in u.query_pairs() {
285 match k.as_ref() {
286 "wait_time_secs" => {
287 client.wait_time_secs = Some(v.parse()?);
288 }
289 "max_rows_in_buffer" => {
290 client.max_rows_in_buffer = Some(v.parse()?);
291 }
292 "max_rows_per_page" => {
293 client.max_rows_per_page = Some(v.parse()?);
294 }
295 "connect_timeout" => client.connect_timeout = Duration::from_secs(v.parse()?),
296 "page_request_timeout_secs" => {
297 client.page_request_timeout = {
298 let secs: u64 = v.parse()?;
299 Duration::from_secs(secs)
300 };
301 }
302 "presign" => {
303 let presign_mode = match v.as_ref() {
304 "auto" => PresignMode::Auto,
305 "detect" => PresignMode::Detect,
306 "on" => PresignMode::On,
307 "off" => PresignMode::Off,
308 _ => {
309 return Err(Error::BadArgument(format!(
310 "Invalid value for presign: {v}, should be one of auto/detect/on/off"
311 )))
312 }
313 };
314 client.set_presign_mode(presign_mode);
315 }
316 "tenant" => {
319 client.tenant = Some(v.to_string());
320 }
321 "warehouse" => {
322 client.warehouse = Mutex::new(Some(v.to_string()));
323 }
324 "role" => session_state.set_role(v),
325 "sslmode" => match v.as_ref() {
326 "disable" => scheme = "http",
327 "require" | "enable" => scheme = "https",
328 _ => {
329 return Err(Error::BadArgument(format!(
330 "Invalid value for sslmode: {v}"
331 )))
332 }
333 },
334 "tls_ca_file" => {
335 client.tls_ca_file = Some(v.to_string());
336 }
337 "access_token" => {
338 client.auth = Arc::new(AccessTokenAuth::new(v));
339 }
340 "access_token_file" => {
341 client.auth = Arc::new(AccessTokenFileAuth::new(v));
342 }
343 "private_key_file" => {
344 private_key_file = Some(v.to_string());
345 }
346 "private_key_passphrase_file" => {
347 private_key_passphrase_file = Some(v.to_string());
348 }
349 "login" => {
350 client.disable_login = match v.as_ref() {
351 "disable" => true,
352 "enable" => false,
353 _ => {
354 return Err(Error::BadArgument(format!("Invalid value for login: {v}")))
355 }
356 }
357 }
358 "session_token" => {
359 client.disable_session_token = match v.as_ref() {
360 "disable" => true,
361 "enable" => false,
362 _ => {
363 return Err(Error::BadArgument(format!(
364 "Invalid value for session_token: {v}"
365 )))
366 }
367 }
368 }
369 "body_format" | "query_result_format" => {
370 let v = v.to_string().to_lowercase();
371 match v.as_str() {
372 "json" | "arrow" => client.query_result_format = v.to_string(),
373 _ => {
374 return Err(Error::BadArgument(format!(
375 "Invalid value for query_result_format: {v}"
376 )))
377 }
378 }
379 }
380 "retry_count" => {
381 client.retry_count = v.parse()?;
382 }
383 "retry_delay_secs" => {
384 client.retry_delay_secs = v.parse()?;
385 }
386 _ => {
387 session_state.set(k, v);
388 }
389 }
390 }
391 if let Some(key_file) = private_key_file {
393 if username.is_empty() {
394 return Err(Error::BadArgument(
395 "username is required for key-pair authentication".to_string(),
396 ));
397 }
398 client.auth = Arc::new(KeyPairAuth::new(
399 &username,
400 &key_file,
401 private_key_passphrase_file.as_deref(),
402 )?);
403 }
404 client.port = match u.port() {
405 Some(p) => p,
406 None => match scheme {
407 "http" => 80,
408 "https" => 443,
409 _ => unreachable!(),
410 },
411 };
412 client.scheme = scheme.to_string();
413 client.endpoint = Url::parse(&format!("{}://{}:{}", scheme, client.host, client.port))?;
414 client.session_state = Mutex::new(session_state);
415
416 Ok(client)
417 }
418
419 pub fn host(&self) -> &str {
420 self.host.as_str()
421 }
422
423 pub fn port(&self) -> u16 {
424 self.port
425 }
426
427 pub fn scheme(&self) -> &str {
428 self.scheme.as_str()
429 }
430
431 async fn build_client(&mut self, name: Option<String>) -> Result<()> {
432 let ua = match name {
433 Some(n) => n,
434 None => format!("databend-client-rust/{}", VERSION.as_str()),
435 };
436 let cookie_provider = GlobalCookieStore::new();
437 let cookie = HeaderValue::from_str("cookie_enabled=true").unwrap();
438 let mut initial_cookies = [&cookie].into_iter();
439 cookie_provider.set_cookies(&mut initial_cookies, &Url::parse("https://a.com").unwrap());
440 let mut cli_builder = HttpClient::builder()
441 .user_agent(ua)
442 .connect_timeout(self.connect_timeout)
443 .cookie_provider(Arc::new(cookie_provider))
444 .pool_idle_timeout(Duration::from_secs(1));
445 #[cfg(any(feature = "rustls", feature = "native-tls"))]
446 if self.scheme == "https" {
447 if let Some(ref ca_file) = self.tls_ca_file {
448 let cert_pem = tokio::fs::read(ca_file).await?;
449 let cert = reqwest::Certificate::from_pem(&cert_pem)?;
450 cli_builder = cli_builder.add_root_certificate(cert);
451 }
452 }
453 self.cli = cli_builder.build()?;
454 Ok(())
455 }
456
457 async fn check_presign(self: &Arc<Self>) -> Result<()> {
458 let mode = match self.get_presign_mode() {
459 PresignMode::Auto => {
460 if self.host.ends_with(".databend.com")
461 || self.host.ends_with(".databend.cn")
462 || self.host.ends_with(".tidbcloud.com")
463 {
464 PresignMode::On
465 } else {
466 PresignMode::Off
467 }
468 }
469 PresignMode::Detect => match self.get_presigned_upload_url("@~/.bendsql/check").await {
470 Ok(_) => PresignMode::On,
471 Err(e) => {
472 warn!("presign mode off with error detected: {e}");
473 PresignMode::Off
474 }
475 },
476 mode => mode,
477 };
478 self.set_presign_mode(mode);
479 Ok(())
480 }
481
482 pub fn current_warehouse(&self) -> Option<String> {
483 let guard = self.warehouse.lock();
484 guard.clone()
485 }
486
487 pub fn current_catalog(&self) -> Option<String> {
488 let guard = self.session_state.lock();
489 guard.catalog.clone()
490 }
491
492 pub fn current_database(&self) -> Option<String> {
493 let guard = self.session_state.lock();
494 guard.database.clone()
495 }
496
497 pub fn set_warehouse(&self, warehouse: impl Into<String>) {
498 let mut guard = self.warehouse.lock();
499 *guard = Some(warehouse.into());
500 }
501
502 pub fn set_database(&self, database: impl Into<String>) {
503 let mut guard = self.session_state.lock();
504 guard.set_database(database);
505 }
506
507 pub fn set_role(&self, role: impl Into<String>) {
508 let mut guard = self.session_state.lock();
509 guard.set_role(role);
510 }
511
512 pub fn set_session(&self, key: impl Into<String>, value: impl Into<String>) {
513 let mut guard = self.session_state.lock();
514 guard.set(key, value);
515 }
516
517 pub async fn current_role(&self) -> Option<String> {
518 let guard = self.session_state.lock();
519 guard.role.clone()
520 }
521
522 fn in_active_transaction(&self) -> bool {
523 let guard = self.session_state.lock();
524 guard
525 .txn_state
526 .as_ref()
527 .map(|s| s.eq_ignore_ascii_case(TXN_STATE_ACTIVE))
528 .unwrap_or(false)
529 }
530
531 pub fn username(&self) -> String {
532 self.auth.username()
533 }
534
535 fn gen_query_id(&self) -> String {
536 uuid::Uuid::now_v7().simple().to_string()
537 }
538
539 async fn handle_session(&self, session: &Option<SessionState>) {
540 let session = match session {
541 Some(session) => session,
542 None => return,
543 };
544
545 {
547 let mut session_state = self.session_state.lock();
548 *session_state = session.clone();
549 }
550
551 if let Some(settings) = session.settings.as_ref() {
553 if let Some(v) = settings.get("warehouse") {
554 let mut warehouse = self.warehouse.lock();
555 *warehouse = Some(v.clone());
556 }
557 }
558 }
559
560 pub fn set_last_node_id(&self, node_id: String) {
561 *self.last_node_id.lock() = Some(node_id)
562 }
563
564 pub fn set_last_query_id(&self, query_id: Option<String>) {
565 *self.last_query_id.lock() = query_id
566 }
567
568 pub fn last_query_id(&self) -> Option<String> {
569 self.last_query_id.lock().clone()
570 }
571
572 fn last_node_id(&self) -> Option<String> {
573 self.last_node_id.lock().clone()
574 }
575
576 fn handle_warnings(&self, resp: &QueryResponse) {
577 if let Some(warnings) = &resp.warnings {
578 for w in warnings {
579 warn!(target: "server_warnings", "server warning: {w}");
580 }
581 }
582 }
583
584 pub async fn start_query(
585 self: &Arc<Self>,
586 sql: &str,
587 need_progress: bool,
588 params: Option<serde_json::Value>,
589 ) -> Result<Pages> {
590 info!("start query: {sql}");
591 let (resp, batches) = self.start_query_inner(sql, None, false, params).await?;
592 Pages::new(self.clone(), resp, batches, need_progress)
593 }
594
595 pub fn finalize_query(self: &Arc<Self>, query_id: &str) {
596 let mut mgr = self.queries_need_heartbeat.lock();
597 if let Some(state) = mgr.remove(query_id) {
598 let self_cloned = self.clone();
599 let query_id = query_id.to_owned();
600 GLOBAL_RUNTIME.spawn(async move {
601 if let Err(e) = self_cloned
602 .end_query(&query_id, false, Some(state.node_id.as_str()))
603 .await
604 {
605 error!("failed to final query {query_id}: {e}");
606 }
607 });
608 }
609 }
610
611 fn wrap_auth_or_session_token(&self, builder: RequestBuilder) -> Result<RequestBuilder> {
612 if let Some(info) = &self.session_token_info {
613 let info = info.lock();
614 Ok(builder.bearer_auth(info.0.session_token.clone()))
615 } else {
616 self.auth.wrap(builder)
617 }
618 }
619
620 async fn start_query_inner(
621 &self,
622 sql: &str,
623 stage_attachment_config: Option<StageAttachmentConfig<'_>>,
624 force_json_body: bool,
625 params: Option<serde_json::Value>,
626 ) -> Result<(QueryResponse, Vec<RecordBatch>)> {
627 if !self.in_active_transaction() {
628 self.route_hint.next();
629 }
630 let endpoint = self.endpoint.join("v1/query")?;
631
632 let session_state = self.session_state();
634 let need_sticky = session_state.need_sticky.unwrap_or(false);
635 let mut req = QueryRequest::new(sql)
636 .with_pagination(self.make_pagination())
637 .with_session(Some(session_state))
638 .with_stage_attachment(stage_attachment_config)
639 .with_params(params);
640
641 let query_id = self.gen_query_id();
643 let mut headers = self.make_headers(Some(&query_id))?;
644 if self.capability.arrow_data && self.query_result_format == "arrow" && !force_json_body {
645 debug!("accept arrow data");
646 headers.insert(ACCEPT, HeaderValue::from_static(CONTENT_TYPE_ARROW_OR_JSON));
647 req = req.with_arrow();
648 }
649
650 if need_sticky {
651 if let Some(node_id) = self.last_node_id() {
652 headers.insert(HEADER_STICKY_NODE, node_id.parse()?);
653 }
654 }
655 let mut builder = self.cli.post(endpoint.clone()).json(&req);
656 builder = self.wrap_auth_or_session_token(builder)?;
657 let request = builder
658 .headers(headers.clone())
659 .timeout(self.page_request_timeout)
660 .build()?;
661 let response = self
662 .query_request_helper(request, true, true, true, RequestKind::QueryStart)
663 .await?;
664 self.handle_page(response, true).await
665 }
666
667 fn is_arrow_data(headers: &HeaderMap) -> bool {
668 if let Some(typ) = headers.get(CONTENT_TYPE) {
669 if let Ok(t) = typ.to_str() {
670 return t == CONTENT_TYPE_ARROW;
671 }
672 }
673 false
674 }
675
676 async fn handle_page(
677 &self,
678 response: HttpResponseData,
679 is_first: bool,
680 ) -> Result<(QueryResponse, Vec<RecordBatch>)> {
681 let status = response.status;
682 if status != StatusCode::OK {
683 let error = Self::status_body_to_error(status, &response.body);
684 if !is_first {
685 if let Error::Logic(code, ec) = &error {
686 if *code == StatusCode::NOT_FOUND {
687 return Err(Error::QueryNotFound(ec.message.clone()));
688 }
689 }
690 }
691 return Err(error);
692 }
693 let is_arrow_data = Self::is_arrow_data(&response.headers);
694 if is_first {
695 if let Some(route_hint) = response.headers.get(HEADER_ROUTE_HINT) {
696 self.route_hint.set(route_hint.to_str().unwrap_or_default());
697 }
698 }
699 let mut body = response.body;
700 let mut batches = vec![];
701 if is_arrow_data {
702 if is_first {
703 debug!("received arrow data");
704 }
705 let cursor = std::io::Cursor::new(body.as_ref());
706 let reader = StreamReader::try_new(cursor, None)
707 .map_err(|e| Error::Decode(format!("failed to decode arrow stream: {e}")))?;
708 let schema = reader.schema();
709 let json_body = if let Some(json_resp) = schema.metadata.get("response_header") {
710 bytes::Bytes::copy_from_slice(json_resp.as_bytes())
711 } else {
712 return Err(Error::Decode(
713 "missing response_header metadata in arrow payload".to_string(),
714 ));
715 };
716 for batch in reader {
717 let batch = batch
718 .map_err(|e| Error::Decode(format!("failed to decode arrow batch: {e}")))?;
719 batches.push(batch);
720 }
721 body = json_body
722 };
723 let resp: QueryResponse = json_from_slice(&body)?;
724 self.handle_session(&resp.session).await;
725 if let Some(err) = &resp.error {
726 return Err(Error::QueryFailed(err.clone()));
727 }
728 if is_first {
729 self.handle_warnings(&resp);
730 self.set_last_query_id(Some(resp.id.clone()));
731 if let Some(node_id) = &resp.node_id {
732 self.set_last_node_id(node_id.clone());
733 }
734 }
735 Ok((resp, batches))
736 }
737
738 pub async fn query_page(
739 &self,
740 query_id: &str,
741 next_uri: &str,
742 node_id: &Option<String>,
743 ) -> Result<(QueryResponse, Vec<RecordBatch>)> {
744 info!("query page: {next_uri}");
745 let endpoint = self.endpoint.join(next_uri)?;
746 let mut headers = self.make_headers(Some(query_id))?;
747 if self.capability.arrow_data && self.query_result_format == "arrow" {
748 headers.insert(ACCEPT, HeaderValue::from_static(CONTENT_TYPE_ARROW_OR_JSON));
749 }
750 let mut builder = self.cli.get(endpoint.clone());
751 builder = self
752 .wrap_auth_or_session_token(builder)?
753 .headers(headers.clone())
754 .timeout(self.page_request_timeout);
755 if let Some(node_id) = node_id {
756 builder = builder.header(HEADER_STICKY_NODE, node_id)
757 }
758 let request = builder.build()?;
759
760 let response = self
761 .query_request_helper(request, false, true, true, RequestKind::QueryPage)
762 .await?;
763 self.handle_page(response, false).await
764 }
765
766 pub async fn kill_query(&self, query_id: &str) -> Result<()> {
767 self.end_query(query_id, true, None).await
768 }
769
770 pub async fn final_query(&self, query_id: &str, node_id: Option<&str>) -> Result<()> {
771 self.end_query(query_id, false, node_id).await
772 }
773
774 pub async fn end_query(
775 &self,
776 query_id: &str,
777 is_kill: bool,
778 node_id: Option<&str>,
779 ) -> Result<()> {
780 let (method, request_kind) = if is_kill {
781 ("kill", RequestKind::QueryKill)
782 } else {
783 ("final", RequestKind::QueryFinal)
784 };
785 let uri = format!("/v1/query/{query_id}/{method}");
786 let endpoint = self.endpoint.join(&uri)?;
787 let headers = self.make_headers(Some(query_id))?;
788
789 info!("{method} query: {uri}");
790
791 let mut builder = self.cli.post(endpoint.clone());
792 if let Some(node_id) = node_id {
793 builder = builder.header(HEADER_STICKY_NODE, node_id)
794 }
795 builder = self.wrap_auth_or_session_token(builder)?;
796 let resp = builder.headers(headers.clone()).send().await?;
797 if resp.status() != 200 {
798 return Err(Error::response_error(resp.status(), &resp.bytes().await?)
799 .with_context(request_kind)
800 .with_query_id(query_id));
801 }
802 Ok(())
803 }
804
805 pub async fn query_all(
806 self: &Arc<Self>,
807 sql: &str,
808 params: Option<serde_json::Value>,
809 ) -> Result<Page> {
810 self.query_all_inner(sql, false, params).await
811 }
812
813 pub async fn query_all_inner(
814 self: &Arc<Self>,
815 sql: &str,
816 force_json_body: bool,
817 params: Option<serde_json::Value>,
818 ) -> Result<Page> {
819 let (resp, batches) = self
820 .start_query_inner(sql, None, force_json_body, params)
821 .await?;
822 let mut pages = Pages::new(self.clone(), resp, batches, false)?;
823 let mut all = Page::default();
824 while let Some(page) = pages.next().await {
825 all.update(page?);
826 }
827 Ok(all)
828 }
829
830 fn session_state(&self) -> SessionState {
831 self.session_state.lock().clone()
832 }
833
834 fn make_pagination(&self) -> Option<PaginationConfig> {
835 if self.wait_time_secs.is_none()
836 && self.max_rows_in_buffer.is_none()
837 && self.max_rows_per_page.is_none()
838 {
839 return None;
840 }
841 let mut pagination = PaginationConfig {
842 wait_time_secs: None,
843 max_rows_in_buffer: None,
844 max_rows_per_page: None,
845 };
846 if let Some(wait_time_secs) = self.wait_time_secs {
847 pagination.wait_time_secs = Some(wait_time_secs);
848 }
849 if let Some(max_rows_in_buffer) = self.max_rows_in_buffer {
850 pagination.max_rows_in_buffer = Some(max_rows_in_buffer);
851 }
852 if let Some(max_rows_per_page) = self.max_rows_per_page {
853 pagination.max_rows_per_page = Some(max_rows_per_page);
854 }
855 Some(pagination)
856 }
857
858 fn make_headers(&self, query_id: Option<&str>) -> Result<HeaderMap> {
859 let mut headers = HeaderMap::new();
860 if let Some(tenant) = &self.tenant {
861 headers.insert(HEADER_TENANT, tenant.parse()?);
862 }
863 let warehouse = self.warehouse.lock().clone();
864 if let Some(warehouse) = warehouse {
865 headers.insert(HEADER_WAREHOUSE, warehouse.parse()?);
866 }
867 let route_hint = self.route_hint.current();
868 headers.insert(HEADER_ROUTE_HINT, route_hint.parse()?);
869 if let Some(query_id) = query_id {
870 headers.insert(HEADER_QUERY_ID, query_id.parse()?);
871 }
872 Ok(headers)
873 }
874
875 pub async fn insert_with_stage(
876 self: &Arc<Self>,
877 sql: &str,
878 stage: &str,
879 file_format_options: BTreeMap<&str, &str>,
880 copy_options: BTreeMap<&str, &str>,
881 ) -> Result<QueryStats> {
882 info!("insert with stage: {sql}, format: {file_format_options:?}, copy: {copy_options:?}");
883 let stage_attachment = Some(StageAttachmentConfig {
884 location: stage,
885 file_format_options: Some(file_format_options),
886 copy_options: Some(copy_options),
887 });
888 let (resp, batches) = self
889 .start_query_inner(sql, stage_attachment, true, None)
890 .await?;
891 let mut pages = Pages::new(self.clone(), resp, batches, false)?;
892 let mut all = Page::default();
893 while let Some(page) = pages.next().await {
894 all.update(page?);
895 }
896 Ok(all.stats)
897 }
898
899 async fn get_presigned_upload_url(self: &Arc<Self>, stage: &str) -> Result<PresignedResponse> {
900 info!("get presigned upload url: {stage}");
901 let sql = format!("PRESIGN UPLOAD {stage}");
902 let resp = self.query_all_inner(&sql, true, None).await?;
903 if resp.data.len() != 1 {
904 return Err(Error::Decode(
905 "Empty response from server for presigned request".to_string(),
906 ));
907 }
908 if resp.data[0].len() != 3 {
909 return Err(Error::Decode(
910 "Invalid response from server for presigned request".to_string(),
911 ));
912 }
913 let method = resp.data[0][0].clone().unwrap_or_default();
915 if method != "PUT" {
916 return Err(Error::Decode(format!(
917 "Invalid method for presigned upload request: {method}"
918 )));
919 }
920 let headers: BTreeMap<String, String> =
921 serde_json::from_str(resp.data[0][1].clone().unwrap_or("{}".to_string()).as_str())?;
922 let url = resp.data[0][2].clone().unwrap_or_default();
923 Ok(PresignedResponse {
924 method,
925 headers,
926 url,
927 })
928 }
929
930 pub async fn upload_to_stage(
931 self: &Arc<Self>,
932 stage: &str,
933 data: Reader,
934 size: u64,
935 ) -> Result<()> {
936 match self.get_presign_mode() {
937 PresignMode::Off => self.upload_to_stage_with_stream(stage, data, size).await,
938 PresignMode::On => {
939 let presigned = self.get_presigned_upload_url(stage).await?;
940 presign_upload_to_stage(presigned, data, size).await
941 }
942 PresignMode::Auto => {
943 unreachable!("PresignMode::Auto should be handled during client initialization")
944 }
945 PresignMode::Detect => {
946 unreachable!("PresignMode::Detect should be handled during client initialization")
947 }
948 }
949 }
950
951 async fn upload_to_stage_with_stream(
953 &self,
954 stage: &str,
955 data: Reader,
956 size: u64,
957 ) -> Result<()> {
958 info!("upload to stage with stream: {stage}, size: {size}");
959 if let Some(info) = self.need_pre_refresh_session().await {
960 self.refresh_session_token(info).await?;
961 }
962 let endpoint = self.endpoint.join("v1/upload_to_stage")?;
963 let location = StageLocation::try_from(stage)?;
964 let query_id = self.gen_query_id();
965 let mut headers = self.make_headers(Some(&query_id))?;
966 headers.insert(HEADER_STAGE_NAME, location.name.parse()?);
967 let stream = Body::wrap_stream(ReaderStream::new(data));
968 let part = Part::stream_with_length(stream, size).file_name(location.path);
969 let form = Form::new().part("upload", part);
970 let mut builder = self.cli.put(endpoint.clone());
971 builder = self.wrap_auth_or_session_token(builder)?;
972 let resp = builder.headers(headers).multipart(form).send().await?;
973 let status = resp.status();
974 if status != 200 {
975 return Err(Error::response_error(status, &resp.bytes().await?)
976 .with_context(RequestKind::UploadToStage)
977 .with_query_id(query_id));
978 }
979 Ok(())
980 }
981
982 pub fn decode_json_header<T>(key: &str, value: &str) -> Result<T, String>
985 where
986 T: de::DeserializeOwned,
987 {
988 if value.starts_with("{") {
989 serde_json::from_slice(value.as_bytes())
990 .map_err(|e| format!("Invalid value {value} for {key} JSON decode error: {e}",))?
991 } else {
992 let json = URL_SAFE.decode(value).map_err(|e| {
993 format!(
994 "Invalid value {} for {key}, base64 decode error: {}",
995 value, e
996 )
997 })?;
998 serde_json::from_slice(&json).map_err(|e| {
999 format!(
1000 "Invalid value {value} for {key}, JSON value {}, decode error: {e}",
1001 String::from_utf8_lossy(&json)
1002 )
1003 })
1004 }
1005 }
1006
1007 pub async fn streaming_load(
1008 &self,
1009 sql: &str,
1010 data: Reader,
1011 file_name: &str,
1012 ) -> Result<LoadResponse> {
1013 let body = Body::wrap_stream(ReaderStream::new(data));
1014 let part = Part::stream(body).file_name(file_name.to_string());
1015 let endpoint = self.endpoint.join("v1/streaming_load")?;
1016 let mut builder = self.cli.put(endpoint.clone());
1017 builder = self.wrap_auth_or_session_token(builder)?;
1018 let query_id = self.gen_query_id();
1019 let mut headers = self.make_headers(Some(&query_id))?;
1020 headers.insert(HEADER_SQL, sql.parse()?);
1021 let session = serde_json::to_string(&*self.session_state.lock())
1022 .expect("serialize session state should not fail");
1023 headers.insert(HEADER_QUERY_CONTEXT, session.parse()?);
1024 let form = Form::new().part("upload", part);
1025 let resp = builder.headers(headers).multipart(form).send().await?;
1026 let status = resp.status();
1027 if let Some(value) = resp.headers().get(HEADER_QUERY_CONTEXT) {
1028 match Self::decode_json_header::<SessionState>(
1029 HEADER_QUERY_CONTEXT,
1030 value.to_str().unwrap(),
1031 ) {
1032 Ok(session) => *self.session_state.lock() = session,
1033 Err(e) => {
1034 error!("Error decoding session state when streaming load: {e}");
1035 }
1036 }
1037 };
1038 if status != 200 {
1039 return Err(Error::response_error(status, &resp.bytes().await?)
1040 .with_context(RequestKind::StreamingLoad)
1041 .with_query_id(query_id));
1042 }
1043 let resp = resp.json::<LoadResponse>().await?;
1044 Ok(resp)
1045 }
1046
1047 async fn login(&mut self) -> Result<()> {
1048 let endpoint = self.endpoint.join("/v1/session/login")?;
1049 let headers = self.make_headers(None)?;
1050 let body = LoginRequest::from(&*self.session_state.lock());
1051 let mut builder = self.cli.post(endpoint.clone()).json(&body);
1052 if self.disable_session_token {
1053 builder = builder.query(&[("disable_session_token", true)]);
1054 }
1055 let builder = self.auth.wrap(builder)?;
1056 let request = builder
1057 .headers(headers.clone())
1058 .timeout(self.connect_timeout.saturating_add(Duration::from_secs(10)))
1059 .build()?;
1060 let response = self
1061 .query_request_helper(request, true, false, true, RequestKind::Login)
1062 .await?;
1063 if response.status == StatusCode::NOT_FOUND {
1064 info!("login return 404, skip login on the old version server");
1065 return Ok(());
1066 }
1067 if response.status != StatusCode::OK {
1068 return Err(Self::status_body_to_error(response.status, &response.body)
1069 .with_context(RequestKind::Login));
1070 }
1071 if let Some(v) = response.headers.get(HEADER_SESSION_ID) {
1072 if let Ok(s) = v.to_str() {
1073 self.session_id = s.to_string();
1074 }
1075 }
1076
1077 let response = json_from_slice(&response.body)?;
1078 match response {
1079 LoginResponseResult::Err { error } => return Err(Error::AuthFailure(error)),
1080 LoginResponseResult::Ok(info) => {
1081 let server_version = info
1082 .version
1083 .parse()
1084 .map_err(|e| Error::Decode(format!("invalid server version: {e}")))?;
1085 self.capability = Capability::from_server_version(&server_version);
1086 self.server_version = Some(server_version.clone());
1087 let session_id = self.session_id.as_str();
1088 if let Some(tokens) = info.tokens {
1089 info!(
1090 "[session {session_id}] login success with session token version = {server_version}",
1091 );
1092 self.session_token_info = Some(Arc::new(Mutex::new((tokens, Instant::now()))))
1093 } else {
1094 info!("[session {session_id}] login success, version = {server_version}");
1095 }
1096 }
1097 }
1098 Ok(())
1099 }
1100
1101 pub(crate) async fn try_heartbeat(&self) -> Result<()> {
1102 let endpoint = self.endpoint.join("/v1/session/heartbeat")?;
1103 let queries = self.queries_need_heartbeat.lock().clone();
1104 let mut node_to_queries = HashMap::<String, Vec<String>>::new();
1105 let now = Instant::now();
1106
1107 let mut query_ids = Vec::new();
1108 for (qid, state) in queries {
1109 if state.need_heartbeat(now) {
1110 query_ids.push(qid.to_string());
1111 if let Some(arr) = node_to_queries.get_mut(&state.node_id) {
1112 arr.push(qid);
1113 } else {
1114 node_to_queries.insert(state.node_id, vec![qid]);
1115 }
1116 }
1117 }
1118
1119 if node_to_queries.is_empty() && !self.session_state.lock().need_sticky.unwrap_or_default()
1120 {
1121 return Ok(());
1122 }
1123
1124 let body = json!({
1125 "node_to_queries": node_to_queries
1126 });
1127 let headers = self.make_headers(None)?;
1128 let builder = self.cli.post(endpoint.clone()).json(&body).headers(headers);
1129 let request = self.wrap_auth_or_session_token(builder)?.build()?;
1130 let response = self
1131 .query_request_helper(request, true, false, true, RequestKind::Heartbeat)
1132 .await?;
1133 if response.status != StatusCode::OK {
1134 return Err(Self::status_body_to_error(response.status, &response.body)
1135 .with_context(RequestKind::Heartbeat));
1136 }
1137 let json: Value = json_from_slice(&response.body)?;
1138 let session_id = self.session_id.as_str();
1139 info!("[session {session_id}] heartbeat request={body}, response={json}");
1140 if let Some(queries_to_remove) = json.get("queries_to_remove") {
1141 if let Some(arr) = queries_to_remove.as_array() {
1142 if !arr.is_empty() {
1143 let mut queries = self.queries_need_heartbeat.lock();
1144 for q in arr {
1145 if let Some(q) = q.as_str() {
1146 queries.remove(q);
1147 }
1148 }
1149 }
1150 }
1151 }
1152 let now = Instant::now();
1153 let mut queries = self.queries_need_heartbeat.lock();
1154 for qid in query_ids {
1155 if let Some(state) = queries.get_mut(&qid) {
1156 *state.last_access_time.lock() = now;
1157 }
1158 }
1159 Ok(())
1160 }
1161
1162 fn build_log_out_request(&self) -> Result<Request> {
1163 let endpoint = self.endpoint.join("/v1/session/logout")?;
1164
1165 let session_state = self.session_state();
1166 let need_sticky = session_state.need_sticky.unwrap_or(false);
1167 let mut headers = self.make_headers(None)?;
1168 if need_sticky {
1169 if let Some(node_id) = self.last_node_id() {
1170 headers.insert(HEADER_STICKY_NODE, node_id.parse()?);
1171 }
1172 }
1173 let builder = self.cli.post(endpoint.clone()).headers(headers.clone());
1174
1175 let builder = self.wrap_auth_or_session_token(builder)?;
1176 let req = builder.build()?;
1177 Ok(req)
1178 }
1179
1180 pub(crate) fn need_logout(&self) -> bool {
1181 self.session_token_info.is_some()
1182 || self.session_state.lock().need_keep_alive.unwrap_or(false)
1183 }
1184
1185 async fn refresh_session_token(
1186 &self,
1187 self_login_info: Arc<parking_lot::Mutex<(SessionTokenInfo, Instant)>>,
1188 ) -> Result<()> {
1189 let (session_token_info, _) = { self_login_info.lock().clone() };
1190 let endpoint = self.endpoint.join("/v1/session/refresh")?;
1191 let body = RefreshSessionTokenRequest {
1192 session_token: session_token_info.session_token.clone(),
1193 };
1194 let headers = self.make_headers(None)?;
1195 let request = self
1196 .cli
1197 .post(endpoint.clone())
1198 .json(&body)
1199 .headers(headers.clone())
1200 .bearer_auth(session_token_info.refresh_token.clone())
1201 .timeout(self.connect_timeout.saturating_add(Duration::from_secs(10)))
1202 .build()?;
1203 let response = self
1204 .query_request_helper(request, true, false, false, RequestKind::SessionRefresh)
1205 .await?;
1206 if response.status != StatusCode::OK {
1207 return Err(Self::status_body_to_error(response.status, &response.body)
1208 .with_context(RequestKind::SessionRefresh));
1209 }
1210 let response = json_from_slice(&response.body)?;
1211 match response {
1212 RefreshResponse::Err { error } => Err(Error::AuthFailure(error)),
1213 RefreshResponse::Ok(info) => {
1214 *self_login_info.lock() = (info, Instant::now());
1215 Ok(())
1216 }
1217 }
1218 }
1219
1220 async fn need_pre_refresh_session(&self) -> Option<Arc<Mutex<(SessionTokenInfo, Instant)>>> {
1221 if let Some(info) = &self.session_token_info {
1222 let (start, ttl) = {
1223 let guard = info.lock();
1224 (guard.1, guard.0.session_token_ttl_in_secs)
1225 };
1226 if Instant::now() > start + Duration::from_secs(ttl) {
1227 return Some(info.clone());
1228 }
1229 }
1230 None
1231 }
1232
1233 async fn query_request_helper(
1241 &self,
1242 mut request: Request,
1243 retry_if_503: bool,
1244 refresh_if_401: bool,
1245 reload_auth_if_401: bool,
1246 request_kind: RequestKind,
1247 ) -> Result<HttpResponseData> {
1248 let mut refreshed = false;
1249 let mut retries = 0;
1250 let max_retries = self.retry_count;
1251 let retry_delay = Duration::from_secs(self.retry_delay_secs);
1252
1253 loop {
1254 let req = request.try_clone().expect("request not cloneable");
1255 let response = match self.cli.execute(req).await {
1256 Ok(response) => response,
1257 Err(err) => {
1258 if let Some(reason) = Self::retry_reason_for_reqwest(&err) {
1259 if retries >= max_retries.saturating_sub(1) {
1260 return Err(Self::with_request_context(
1261 err,
1262 &request_kind,
1263 &request,
1264 Some(retries),
1265 ));
1266 }
1267 retries += 1;
1268 warn!(
1269 "retry {}/{} for {} due to: {} (error: {}), retrying after {} seconds",
1270 retries,
1271 max_retries,
1272 request.url(),
1273 reason,
1274 err,
1275 retry_delay.as_secs(),
1276 );
1277 sleep(jitter(retry_delay)).await;
1278 continue;
1279 }
1280 return Err(Self::with_request_context(
1281 err,
1282 &request_kind,
1283 &request,
1284 Some(retries),
1285 ));
1286 }
1287 };
1288
1289 let status = response.status();
1290 let headers = response.headers().clone();
1291 let body = match response.bytes().await {
1292 Ok(body) => body,
1293 Err(err) => {
1294 if let Some(reason) = Self::retry_reason_for_reqwest(&err) {
1295 if retries >= max_retries.saturating_sub(1) {
1296 return Err(Self::with_request_context(
1297 err,
1298 &request_kind,
1299 &request,
1300 Some(retries),
1301 ));
1302 }
1303 retries += 1;
1304 warn!(
1305 "retry {}/{} for {} due to: {} (error: {}), retrying after {} seconds",
1306 retries,
1307 max_retries,
1308 request.url(),
1309 reason,
1310 err,
1311 retry_delay.as_secs(),
1312 );
1313 sleep(jitter(retry_delay)).await;
1314 continue;
1315 }
1316 return Err(Self::with_request_context(
1317 err,
1318 &request_kind,
1319 &request,
1320 Some(retries),
1321 ));
1322 }
1323 };
1324
1325 if retry_if_503 && status == StatusCode::SERVICE_UNAVAILABLE {
1326 if retries >= max_retries.saturating_sub(1) {
1327 return Ok(HttpResponseData {
1328 status,
1329 headers,
1330 body,
1331 });
1332 }
1333 retries += 1;
1334 warn!(
1335 "retry {}/{} for {} due to: service unavailable (503), server may be starting, retrying after {} seconds",
1336 retries,
1337 max_retries,
1338 request.url(),
1339 retry_delay.as_secs()
1340 );
1341 sleep(jitter(retry_delay)).await;
1342 continue;
1343 }
1344
1345 if status == StatusCode::UNAUTHORIZED {
1346 request.headers_mut().remove(reqwest::header::AUTHORIZATION);
1347 let unauthorized_error =
1348 serde_json::from_slice::<ResponseWithErrorCode>(&body).ok();
1349 if let Some(session_token_info) = &self.session_token_info {
1350 let should_refresh = unauthorized_error
1351 .as_ref()
1352 .map(|r| need_refresh_token(r.error.code))
1353 .unwrap_or(false);
1354 if refresh_if_401 && should_refresh && !refreshed {
1355 Box::pin(self.refresh_session_token(session_token_info.clone())).await?;
1356 refreshed = true;
1357 retries = 0;
1358 warn!(
1359 "retry for {} due to: session token expired and refreshed, retrying after {} seconds",
1360 request.url(),
1361 retry_delay.as_secs()
1362 );
1363 sleep(jitter(retry_delay)).await;
1364 continue;
1365 }
1366 }
1367 if reload_auth_if_401 && self.auth.can_reload() {
1368 if retries >= max_retries.saturating_sub(1) {
1369 return Ok(HttpResponseData {
1370 status,
1371 headers,
1372 body,
1373 });
1374 }
1375 retries += 1;
1376 let builder =
1377 RequestBuilder::from_parts(HttpClient::new(), request.try_clone().unwrap());
1378 let builder = self.auth.wrap(builder)?;
1379 request = builder.build()?;
1380 warn!(
1381 "retry {}/{} for {} due to: authentication token reloaded, retrying after {} seconds",
1382 retries,
1383 max_retries,
1384 request.url(),
1385 retry_delay.as_secs()
1386 );
1387 sleep(jitter(retry_delay)).await;
1388 continue;
1389 }
1390 }
1391
1392 return Ok(HttpResponseData {
1393 status,
1394 headers,
1395 body,
1396 });
1397 }
1398 }
1399
1400 pub async fn logout(http_client: HttpClient, request: Request, session_id: &str) {
1401 if let Err(err) = http_client.execute(request).await {
1402 error!("[session {session_id}] logout request failed: {err}");
1403 } else {
1404 info!("[session {session_id}] logout success");
1405 };
1406 }
1407
1408 pub async fn close(&self) {
1409 let session_id = &self.session_id;
1410 info!("[session {session_id}] try closing now");
1411 if self
1412 .closed
1413 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1414 .is_ok()
1415 {
1416 GLOBAL_CLIENT_MANAGER.unregister_client(&self.session_id);
1417 if self.need_logout() {
1418 let cli = self.cli.clone();
1419 let req = self
1420 .build_log_out_request()
1421 .expect("failed to build logout request");
1422 Self::logout(cli, req, &self.session_id).await;
1423 }
1424 }
1425 }
1426 pub fn close_with_spawn(&self) {
1427 let session_id = &self.session_id;
1428 info!("[session {session_id}]: try closing with spawn");
1429 if self
1430 .closed
1431 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1432 .is_ok()
1433 {
1434 GLOBAL_CLIENT_MANAGER.unregister_client(&self.session_id);
1435 if self.need_logout() {
1436 let cli = self.cli.clone();
1437 let req = self
1438 .build_log_out_request()
1439 .expect("failed to build logout request");
1440 let session_id = self.session_id.clone();
1441 GLOBAL_RUNTIME.spawn(async move {
1442 Self::logout(cli, req, session_id.as_str()).await;
1443 });
1444 }
1445 }
1446 }
1447
1448 pub(crate) fn register_query_for_heartbeat(&self, query_id: &str, state: QueryState) {
1449 let mut queries = self.queries_need_heartbeat.lock();
1450 queries.insert(query_id.to_string(), state);
1451 }
1452}
1453
1454fn json_from_slice<'a, T>(body: &'a [u8]) -> Result<T>
1455where
1456 T: Deserialize<'a>,
1457{
1458 serde_json::from_slice::<T>(body).map_err(|e| {
1459 Error::Decode(format!(
1460 "fail to decode JSON response: {e}, body: {}",
1461 String::from_utf8_lossy(body)
1462 ))
1463 })
1464}
1465
1466impl Default for APIClient {
1467 fn default() -> Self {
1468 Self {
1469 session_id: Default::default(),
1470 cli: HttpClient::new(),
1471 scheme: "http".to_string(),
1472 endpoint: Url::parse("http://localhost:8080").unwrap(),
1473 host: "localhost".to_string(),
1474 port: 8000,
1475 tenant: None,
1476 warehouse: Mutex::new(None),
1477 auth: Arc::new(BasicAuth::new(DEFAULT_USERNAME, "")) as Arc<dyn Auth>,
1478 session_state: Mutex::new(SessionState::default()),
1479 wait_time_secs: None,
1480 max_rows_in_buffer: None,
1481 max_rows_per_page: None,
1482 connect_timeout: Duration::from_secs(10),
1483 page_request_timeout: Duration::from_secs(300),
1484 tls_ca_file: None,
1485 presign: Mutex::new(PresignMode::Auto),
1486 route_hint: RouteHintGenerator::new(),
1487 last_node_id: Default::default(),
1488 disable_session_token: true,
1489 disable_login: false,
1490 query_result_format: "json".to_string(),
1491 session_token_info: None,
1492 closed: AtomicBool::new(false),
1493 last_query_id: Default::default(),
1494 server_version: None,
1495 capability: Default::default(),
1496 queries_need_heartbeat: Default::default(),
1497 retry_count: 3,
1498 retry_delay_secs: 10,
1499 }
1500 }
1501}
1502
1503struct RouteHintGenerator {
1504 nonce: AtomicU64,
1505 current: std::sync::Mutex<String>,
1506}
1507
1508impl RouteHintGenerator {
1509 fn new() -> Self {
1510 let gen = Self {
1511 nonce: AtomicU64::new(0),
1512 current: std::sync::Mutex::new("".to_string()),
1513 };
1514 gen.next();
1515 gen
1516 }
1517
1518 fn current(&self) -> String {
1519 let guard = self.current.lock().unwrap();
1520 guard.clone()
1521 }
1522
1523 fn set(&self, hint: &str) {
1524 let mut guard = self.current.lock().unwrap();
1525 *guard = hint.to_string();
1526 }
1527
1528 fn next(&self) -> String {
1529 let nonce = self.nonce.fetch_add(1, Ordering::AcqRel);
1530 let uuid = uuid::Uuid::new_v4();
1531 let current = format!("rh:{uuid}:{nonce:06}");
1532 let mut guard = self.current.lock().unwrap();
1533 guard.clone_from(¤t);
1534 current
1535 }
1536}
1537
1538#[cfg(test)]
1539mod test {
1540 use super::*;
1541 use std::io::Write;
1542 use tempfile::NamedTempFile;
1543
1544 #[tokio::test]
1545 async fn parse_dsn() -> Result<()> {
1546 let dsn = "databend://username:password@app.databend.com/test?wait_time_secs=10&max_rows_in_buffer=5000000&max_rows_per_page=10000&warehouse=wh&sslmode=disable";
1547 let client = APIClient::from_dsn(dsn).await?;
1548 assert_eq!(client.host, "app.databend.com");
1549 assert_eq!(client.endpoint, Url::parse("http://app.databend.com:80")?);
1550 assert_eq!(client.wait_time_secs, Some(10));
1551 assert_eq!(client.max_rows_in_buffer, Some(5000000));
1552 assert_eq!(client.max_rows_per_page, Some(10000));
1553 assert_eq!(client.tenant, None);
1554 assert_eq!(
1555 *client.warehouse.try_lock().unwrap(),
1556 Some("wh".to_string())
1557 );
1558 Ok(())
1559 }
1560
1561 #[tokio::test]
1562 async fn parse_dsn_with_private_key_uses_keypair_auth() -> Result<()> {
1563 let output = std::process::Command::new("openssl")
1564 .args([
1565 "genpkey",
1566 "-algorithm",
1567 "RSA",
1568 "-pkeyopt",
1569 "rsa_keygen_bits:2048",
1570 ])
1571 .output();
1572 let output = match output {
1573 Ok(o) if o.status.success() => o,
1574 _ => return Ok(()),
1575 };
1576
1577 let mut key_file = NamedTempFile::new()?;
1578 key_file.write_all(&output.stdout)?;
1579
1580 let dsn = format!(
1581 "databend://username:password@app.databend.com/test?private_key_file={}&sslmode=disable",
1582 url::form_urlencoded::byte_serialize(key_file.path().to_string_lossy().as_bytes())
1583 .collect::<String>()
1584 );
1585 let client = APIClient::from_dsn(&dsn).await?;
1586 assert_eq!(client.auth.username(), "username");
1587 assert!(client.auth.can_reload());
1588
1589 let request = client
1590 .auth
1591 .wrap(client.cli.get(client.endpoint.join("v1/query")?))?
1592 .build()?;
1593 assert_eq!(
1594 request
1595 .headers()
1596 .get("X-DATABEND-AUTH-METHOD")
1597 .and_then(|value| value.to_str().ok()),
1598 Some("keypair")
1599 );
1600 let authorization = request
1601 .headers()
1602 .get(reqwest::header::AUTHORIZATION)
1603 .and_then(|value| value.to_str().ok())
1604 .unwrap_or_default();
1605 assert!(authorization.starts_with("Bearer "));
1606 assert_eq!(authorization["Bearer ".len()..].split('.').count(), 3);
1607 Ok(())
1608 }
1609
1610 #[tokio::test]
1611 async fn parse_dsn_with_private_key_requires_user() -> Result<()> {
1612 let output = std::process::Command::new("openssl")
1613 .args([
1614 "genpkey",
1615 "-algorithm",
1616 "RSA",
1617 "-pkeyopt",
1618 "rsa_keygen_bits:2048",
1619 ])
1620 .output();
1621 let output = match output {
1622 Ok(o) if o.status.success() => o,
1623 _ => return Ok(()),
1624 };
1625
1626 let mut key_file = NamedTempFile::new()?;
1627 key_file.write_all(&output.stdout)?;
1628
1629 let dsn = format!(
1630 "databend://app.databend.com/test?private_key_file={}&sslmode=disable",
1631 url::form_urlencoded::byte_serialize(key_file.path().to_string_lossy().as_bytes())
1632 .collect::<String>()
1633 );
1634 let err = APIClient::from_dsn(&dsn)
1635 .await
1636 .err()
1637 .expect("key-pair authentication should require an explicit username");
1638 assert!(
1639 err.to_string()
1640 .contains("username is required for key-pair authentication"),
1641 "unexpected error: {err}"
1642 );
1643
1644 Ok(())
1645 }
1646
1647 #[tokio::test]
1648 async fn parse_encoded_password() -> Result<()> {
1649 let dsn = "databend://username:3a%40SC(nYE1k%3D%7B%7BR@localhost";
1650 let client = APIClient::from_dsn(dsn).await?;
1651 assert_eq!(client.host(), "localhost");
1652 assert_eq!(client.port(), 443);
1653 Ok(())
1654 }
1655
1656 #[tokio::test]
1657 async fn parse_special_chars_password() -> Result<()> {
1658 let dsn = "databend://username:3a@SC(nYE1k={{R@localhost:8000";
1659 let client = APIClient::from_dsn(dsn).await?;
1660 assert_eq!(client.host(), "localhost");
1661 assert_eq!(client.port(), 8000);
1662 Ok(())
1663 }
1664}