1use std::fmt;
16use std::str::FromStr;
17use std::time::Duration;
18use std::time::SystemTime;
19
20use reqwest::StatusCode;
21use reqwest::header::HeaderMap;
22use serde::Deserialize;
23use serde::Serialize;
24use uuid::Uuid;
25
26use crate::Error;
27use crate::ErrorKind;
28use crate::ResultSet;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum AppendState {
34 Committed,
36 Rejected,
38 Unknown,
40}
41
42impl fmt::Display for AppendState {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Self::Committed => f.write_str("committed"),
46 Self::Rejected => f.write_str("rejected"),
47 Self::Unknown => f.write_str("unknown"),
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct AppendRowsResult {
55 pub status: AppendState,
56 pub num_rows_inserted: i64,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct AppendRowError {
62 pub row_index: u64,
63 pub column: String,
64 pub message: String,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct AppendErrorDetails {
70 pub status: AppendState,
71 #[serde(default)]
72 pub row_errors: Vec<AppendRowError>,
73 #[serde(default)]
74 pub row_errors_truncated: bool,
75}
76
77pub(crate) async fn decode_statement_response(
78 r: reqwest::Response,
79 retryable_transport: bool,
80) -> Result<StatementStatus, Error> {
81 let code = r.status();
82 let headers = r.headers().clone();
83 let payload = r.bytes().await.map_err(|err| {
84 let error =
85 Error::new(ErrorKind::Unexpected, "failed to read response body").set_source(err);
86 let error = if retryable_transport {
87 error.set_temporary()
88 } else {
89 error
90 };
91 apply_response_metadata(error, code, &headers)
92 })?;
93 if code.is_success() {
94 let result = serde_json::from_slice(&payload).map_err(|err| {
95 apply_response_metadata(
96 Error::new(ErrorKind::Unexpected, "failed to decode response body").set_source(err),
97 code,
98 &headers,
99 )
100 })?;
101 return Ok(result);
102 }
103
104 Err(decode_http_error(
105 code,
106 &headers,
107 &payload,
108 ErrorKind::Unexpected,
109 ))
110}
111
112pub(crate) fn decode_http_error(
113 status: StatusCode,
114 headers: &HeaderMap,
115 payload: &[u8],
116 kind: ErrorKind,
117) -> Error {
118 let parsed = parse_error_payload(payload).unwrap_or_else(|| ParsedErrorPayload {
119 code: None,
120 details: None,
121 message: String::from_utf8_lossy(payload).into_owned(),
122 request_id: None,
123 retryable: None,
124 });
125 let mut error = apply_response_metadata(
126 Error::new(kind, parsed.message)
127 .set_server_retryable(parsed.retryable)
128 .set_api_error(parsed.code, parsed.details),
129 status,
130 headers,
131 );
132 if let Some(request_id) = parsed.request_id {
133 error = error.set_request_id(request_id);
134 }
135 let retryable = parsed.retryable.unwrap_or_else(|| {
136 matches!(
137 status,
138 StatusCode::REQUEST_TIMEOUT | StatusCode::TOO_MANY_REQUESTS
139 ) || status.is_server_error()
140 });
141 if retryable {
142 error.set_temporary()
143 } else {
144 error.set_permanent()
145 }
146}
147
148#[derive(Debug)]
149struct ParsedErrorPayload {
150 code: Option<String>,
151 details: Option<serde_json::Value>,
152 message: String,
153 request_id: Option<String>,
154 retryable: Option<bool>,
155}
156
157fn parse_error_payload(payload: &[u8]) -> Option<ParsedErrorPayload> {
158 let payload = serde_json::from_slice::<serde_json::Value>(payload).ok()?;
159 let object = payload.as_object()?;
160 if let Some(message) = object.get("message").and_then(serde_json::Value::as_str) {
161 return Some(ParsedErrorPayload {
162 code: object
163 .get("code")
164 .and_then(serde_json::Value::as_str)
165 .map(str::to_string),
166 details: object.get("details").cloned(),
167 message: message.to_string(),
168 request_id: object
169 .get("request_id")
170 .and_then(serde_json::Value::as_str)
171 .map(str::to_string),
172 retryable: object.get("retryable").and_then(serde_json::Value::as_bool),
173 });
174 }
175
176 let nested = object.get("error")?.as_object()?;
177 Some(ParsedErrorPayload {
178 code: nested
179 .get("code")
180 .and_then(serde_json::Value::as_str)
181 .map(str::to_string),
182 details: nested.get("details").cloned(),
183 message: nested.get("message")?.as_str()?.to_string(),
184 request_id: object
185 .get("request_id")
186 .and_then(serde_json::Value::as_str)
187 .map(str::to_string),
188 retryable: nested.get("retryable").and_then(serde_json::Value::as_bool),
189 })
190}
191
192pub(crate) fn apply_response_metadata(
193 mut error: Error,
194 status: StatusCode,
195 headers: &HeaderMap,
196) -> Error {
197 error = error.set_http_status(status);
198 if let Some(request_id) = header_string(headers, "x-request-id") {
199 error = error.set_request_id(request_id);
200 }
201 if let Some(retry_after) = parse_retry_after(headers, SystemTime::now()) {
202 error = error.set_retry_after(retry_after);
203 }
204 error
205}
206
207fn header_string(headers: &HeaderMap, name: &'static str) -> Option<String> {
208 headers
209 .get(name)
210 .and_then(|value| value.to_str().ok())
211 .map(str::to_string)
212}
213
214fn parse_retry_after(headers: &HeaderMap, now: SystemTime) -> Option<Duration> {
215 let value = headers.get(reqwest::header::RETRY_AFTER)?.to_str().ok()?;
216 if let Ok(seconds) = value.parse::<u64>() {
217 return Some(Duration::from_secs(seconds));
218 }
219 let at = httpdate::parse_http_date(value).ok()?;
220 Some(at.duration_since(now).unwrap_or(Duration::ZERO))
221}
222
223#[derive(Debug, Serialize)]
224pub(crate) struct StatementRequest {
225 pub statement: String,
226 #[serde(default)]
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub statement_id: Option<Uuid>,
229 #[serde(default)]
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub max_elapsed_seconds: Option<f64>,
232 #[serde(default, skip_serializing_if = "Option::is_none")]
233 pub max_estimated_seconds: Option<f64>,
234 pub format: &'static str,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(tag = "status")]
239pub enum StatementStatus {
240 #[serde(rename = "pending")]
241 Pending(StatementStatusPending),
242 #[serde(rename = "running")]
243 Running(StatementStatusRunning),
244 #[serde(rename = "finished")]
245 Finished(StatementStatusFinished),
246 #[serde(rename = "failed")]
247 Failed(StatementStatusFailed),
248 #[serde(rename = "cancelled")]
249 Cancelled(StatementStatusCancelled),
250}
251
252#[non_exhaustive]
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct StatementStatusPending {
255 pub statement_id: Uuid,
256 pub created_at: jiff::Timestamp,
257 pub progress: StatementEstimatedProgress,
258}
259
260#[non_exhaustive]
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct StatementStatusRunning {
263 pub statement_id: Uuid,
264 pub created_at: jiff::Timestamp,
265 pub progress: StatementEstimatedProgress,
266}
267
268#[non_exhaustive]
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct StatementStatusFinished {
271 pub statement_id: Uuid,
272 pub created_at: jiff::Timestamp,
273 pub progress: StatementEstimatedProgress,
274
275 result_set: StatementResultSet,
276}
277
278impl StatementStatusFinished {
279 pub fn result_set(&self) -> ResultSet {
280 ResultSet::from_statement_result_set(self.result_set.clone())
281 }
282}
283
284#[non_exhaustive]
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct StatementStatusFailed {
287 pub statement_id: Uuid,
288 pub created_at: jiff::Timestamp,
289 pub progress: StatementEstimatedProgress,
290 pub error: StatementErrorDetails,
291}
292
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct StatementErrorDetails {
296 pub code: String,
297 pub message: String,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub details: Option<serde_json::Value>,
300}
301
302#[non_exhaustive]
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct StatementStatusCancelled {
305 pub statement_id: Uuid,
306 pub created_at: jiff::Timestamp,
307 pub progress: StatementEstimatedProgress,
308}
309
310impl StatementStatus {
311 pub fn statement_id(&self) -> Uuid {
312 match self {
313 StatementStatus::Pending(s) => s.statement_id,
314 StatementStatus::Running(s) => s.statement_id,
315 StatementStatus::Finished(s) => s.statement_id,
316 StatementStatus::Failed(s) => s.statement_id,
317 StatementStatus::Cancelled(s) => s.statement_id,
318 }
319 }
320
321 pub fn created_at(&self) -> jiff::Timestamp {
322 match self {
323 StatementStatus::Pending(s) => s.created_at,
324 StatementStatus::Running(s) => s.created_at,
325 StatementStatus::Finished(s) => s.created_at,
326 StatementStatus::Failed(s) => s.created_at,
327 StatementStatus::Cancelled(s) => s.created_at,
328 }
329 }
330
331 pub fn progress(&self) -> &StatementEstimatedProgress {
332 match self {
333 StatementStatus::Pending(s) => &s.progress,
334 StatementStatus::Running(s) => &s.progress,
335 StatementStatus::Finished(s) => &s.progress,
336 StatementStatus::Failed(s) => &s.progress,
337 StatementStatus::Cancelled(s) => &s.progress,
338 }
339 }
340
341 pub fn is_finished(&self) -> bool {
342 matches!(self, StatementStatus::Finished(..))
343 }
344
345 pub fn is_terminated(&self) -> bool {
346 matches!(
347 self,
348 StatementStatus::Finished(..)
349 | StatementStatus::Failed(..)
350 | StatementStatus::Cancelled(..)
351 )
352 }
353}
354
355#[derive(Default, Debug, Clone, Serialize, Deserialize)]
356pub struct StatementEstimatedProgress {
357 #[serde(deserialize_with = "deserialize_progress_float")]
359 pub total_percentage: f64,
360 pub nanos_from_submitted: i64,
362 pub nanos_from_started: i64,
364 #[serde(flatten)]
365 pub details: StatementProgress,
366}
367
368#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub struct StatementProgress {
370 #[serde(deserialize_with = "deserialize_progress_float")]
372 pub compute_seconds: f64,
373 pub total_stages: i64,
374 pub total_partitions: i64,
375 pub total_rows: i64,
376 pub total_compressed_bytes: i64,
377 pub total_uncompressed_bytes: i64,
378 pub scanned_stages: i64,
379 pub scanned_partitions: i64,
380 pub scanned_rows: i64,
381 pub scanned_compressed_bytes: i64,
382 pub scanned_uncompressed_bytes: i64,
383 pub skipped_partitions: i64,
384 pub skipped_rows: i64,
385 pub skipped_compressed_bytes: i64,
386 pub skipped_uncompressed_bytes: i64,
387}
388
389fn deserialize_progress_float<'de, D: serde::Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
392 serde_json::Number::deserialize(d)?
393 .as_f64()
394 .ok_or_else(|| serde::de::Error::custom("progress number is outside the f64 range"))
395}
396
397impl StatementProgress {
398 pub fn total_percentage(&self) -> f64 {
399 let scan_progress = if self.total_rows == 0 {
400 0.0
401 } else {
402 (self.scanned_rows + self.skipped_rows) as f64 / self.total_rows as f64 * 100.0
403 };
404
405 let stage_progress = if self.total_stages == 0 {
406 0.0
407 } else {
408 self.scanned_stages as f64 / self.total_stages as f64 * 100.0
409 };
410
411 scan_progress.max(stage_progress)
412 }
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct StatementResultSet {
417 pub metadata: ResultSetMetadata,
418 #[serde(flatten)]
419 pub data: ResultSetData,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
423#[serde(tag = "format")]
424pub enum ResultSetData {
425 #[serde(rename = "json")]
426 Json { rows: Vec<Vec<Option<String>>> },
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct ResultSetMetadata {
431 pub fields: Vec<FieldMetadata>,
432 pub num_rows: usize,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct FieldMetadata {
437 pub name: String,
438 pub data_type: DataType,
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
442pub enum DataType {
443 #[serde(rename = "int")]
444 Int,
445 #[serde(rename = "uint")]
446 UInt,
447 #[serde(rename = "float")]
448 Float,
449 #[serde(rename = "binary")]
450 Binary,
451 #[serde(rename = "string")]
452 String,
453 #[serde(rename = "boolean")]
454 Boolean,
455 #[serde(rename = "timestamp")]
456 Timestamp,
457 #[serde(rename = "interval")]
458 Interval,
459 #[serde(rename = "array")]
460 Array,
461 #[serde(rename = "object")]
462 Object,
463 #[serde(rename = "any")]
464 Any,
465 #[serde(rename = "null")]
466 Null,
467}
468
469impl FromStr for DataType {
470 type Err = Error;
471
472 fn from_str(s: &str) -> Result<Self, Self::Err> {
473 match s {
474 "int" => Ok(Self::Int),
475 "uint" => Ok(Self::UInt),
476 "float" => Ok(Self::Float),
477 "binary" => Ok(Self::Binary),
478 "string" => Ok(Self::String),
479 "boolean" => Ok(Self::Boolean),
480 "timestamp" => Ok(Self::Timestamp),
481 "interval" => Ok(Self::Interval),
482 "array" => Ok(Self::Array),
483 "object" => Ok(Self::Object),
484 "any" => Ok(Self::Any),
485 "null" => Ok(Self::Null),
486 _ => Err(Error::new(
487 ErrorKind::Unexpected,
488 format!("unrecognized data type: {s}"),
489 )),
490 }
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use std::time::Duration;
497 use std::time::SystemTime;
498
499 use reqwest::header::HeaderMap;
500 use reqwest::header::HeaderValue;
501
502 use super::StatementStatus;
503 use super::decode_http_error;
504 use super::parse_error_payload;
505 use super::parse_retry_after;
506 use crate::ErrorKind;
507
508 #[test]
509 fn failed_statement_preserves_structured_error_details() {
510 let status: StatementStatus = serde_json::from_str(
511 r#"{
512 "status":"failed",
513 "statement_id":"01992f9b-8f54-7cd0-96b5-93ff4a218468",
514 "created_at":"2026-08-26T00:00:00Z",
515 "progress":{
516 "total_percentage":100.0,
517 "compute_seconds":0.25,
518 "nanos_from_submitted":1,
519 "nanos_from_started":1,
520 "total_stages":1,
521 "total_partitions":1,
522 "total_rows":101,
523 "total_compressed_bytes":0,
524 "total_uncompressed_bytes":0,
525 "scanned_stages":1,
526 "scanned_partitions":1,
527 "scanned_rows":101,
528 "scanned_compressed_bytes":0,
529 "scanned_uncompressed_bytes":0,
530 "skipped_partitions":0,
531 "skipped_rows":0,
532 "skipped_compressed_bytes":0,
533 "skipped_uncompressed_bytes":0
534 },
535 "error":{
536 "code":"row_limit_exceeded",
537 "message":"row limit exceeded",
538 "details":{"total_rows":101,"max_total_rows":100}
539 }
540 }"#,
541 )
542 .unwrap();
543
544 let StatementStatus::Failed(failed) = status else {
545 panic!("expected failed statement status");
546 };
547 let error = failed.error;
548 assert_eq!(error.code, "row_limit_exceeded");
549 assert_eq!(error.details.unwrap()["max_total_rows"], 100);
550 }
551
552 #[test]
553 fn http_error_message_supports_direct_and_nested_payloads() {
554 assert_eq!(
555 parse_error_payload(br#"{"message":"direct failure","error":"details"}"#)
556 .unwrap()
557 .message,
558 "direct failure"
559 );
560 assert_eq!(
561 parse_error_payload(br#"{"error":{"message":"nested failure"}}"#)
562 .unwrap()
563 .message,
564 "nested failure"
565 );
566 }
567
568 #[test]
569 fn http_error_metadata_prefers_payload_and_honors_retryable() {
570 let mut headers = HeaderMap::new();
571 headers.insert("x-request-id", HeaderValue::from_static("header-request"));
572 headers.insert(reqwest::header::RETRY_AFTER, HeaderValue::from_static("7"));
573
574 let error = decode_http_error(
575 reqwest::StatusCode::SERVICE_UNAVAILABLE,
576 &headers,
577 br#"{"message":"busy","request_id":"body-request","retryable":false}"#,
578 ErrorKind::Unexpected,
579 );
580
581 assert_eq!(error.message(), "busy");
582 assert_eq!(
583 error.http_status(),
584 Some(reqwest::StatusCode::SERVICE_UNAVAILABLE)
585 );
586 assert_eq!(error.request_id(), Some("body-request"));
587 assert_eq!(error.retry_after(), Some(Duration::from_secs(7)));
588 assert!(!error.is_retryable());
589
590 let nested = decode_http_error(
591 reqwest::StatusCode::BAD_REQUEST,
592 &HeaderMap::new(),
593 br#"{"error":{"message":"try later","retryable":true}}"#,
594 ErrorKind::Unexpected,
595 );
596 assert_eq!(nested.message(), "try later");
597 assert!(nested.is_retryable());
598 }
599
600 #[test]
601 fn retry_after_accepts_http_dates() {
602 let mut headers = HeaderMap::new();
603 headers.insert(
604 reqwest::header::RETRY_AFTER,
605 HeaderValue::from_static("Thu, 01 Jan 1970 00:00:10 GMT"),
606 );
607 assert_eq!(
608 parse_retry_after(&headers, SystemTime::UNIX_EPOCH),
609 Some(Duration::from_secs(10))
610 );
611 }
612}