Skip to main content

uqa_client/
sql_stream_frame.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use std::collections::BTreeMap;
8use std::fmt;
9
10use serde::{Deserialize, Serialize};
11use uqa_core::Value;
12
13/// One decoded frame from the stable UQA NDJSON SQL stream.
14#[derive(Clone, Deserialize, PartialEq, Serialize)]
15#[serde(tag = "type", rename_all = "snake_case")]
16pub enum SQLStreamFrame {
17    Metadata {
18        columns: Vec<String>,
19        row_count: usize,
20        spilled_to_disk: bool,
21        request_id: String,
22    },
23    Row {
24        row: BTreeMap<String, Value>,
25    },
26    Complete {
27        row_count: usize,
28        request_id: String,
29    },
30    Error {
31        code: String,
32        message: String,
33        request_id: String,
34        #[serde(
35            default,
36            deserialize_with = "deserialize_diagnostic",
37            skip_serializing_if = "Option::is_none"
38        )]
39        diagnostic: Option<crate::SQLDiagnostic>,
40    },
41}
42
43fn deserialize_diagnostic<'de, D: serde::Deserializer<'de>>(
44    deserializer: D,
45) -> Result<Option<crate::SQLDiagnostic>, D::Error> {
46    let value = Option::<serde_json::Value>::deserialize(deserializer)?;
47    Ok(value.and_then(|value| serde_json::from_value(value).ok()))
48}
49
50impl SQLStreamFrame {
51    pub fn request_id(&self) -> Option<&str> {
52        match self {
53            Self::Metadata { request_id, .. }
54            | Self::Complete { request_id, .. }
55            | Self::Error { request_id, .. } => Some(request_id),
56            Self::Row { .. } => None,
57        }
58    }
59
60    pub const fn is_terminal(&self) -> bool {
61        matches!(self, Self::Complete { .. } | Self::Error { .. })
62    }
63}
64
65impl fmt::Debug for SQLStreamFrame {
66    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67        formatter.write_str("SQLStreamFrame([REDACTED])")
68    }
69}