Skip to main content

vibesql_server/http/
types.rs

1//! HTTP API request and response types
2
3use serde::{Deserialize, Serialize};
4use serde_json::{json, Value as JsonValue};
5use vibesql_types::SqlValue;
6
7/// Query request for REST API
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct QueryRequest {
10    /// SQL query string
11    pub sql: String,
12    /// Query parameters (optional)
13    #[serde(default)]
14    pub params: Vec<JsonValue>,
15    /// Limit for pagination (max rows to return)
16    #[serde(default)]
17    pub limit: Option<usize>,
18    /// Offset for pagination (rows to skip)
19    #[serde(default)]
20    pub offset: Option<usize>,
21}
22
23impl QueryRequest {
24    /// Convert JSON parameters to SqlValue
25    pub fn to_sql_values(&self) -> Result<Vec<SqlValue>, String> {
26        self.params.iter().map(json_to_sql_value).collect()
27    }
28}
29
30/// Query response for REST API
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct QueryResponse {
33    /// Column names
34    pub columns: Vec<String>,
35    /// Result rows with values
36    pub rows: Vec<Vec<JsonValue>>,
37    /// Number of rows returned
38    pub row_count: usize,
39    /// Total count in result set (before pagination)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub total_count: Option<usize>,
42    /// Current offset used in query
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub offset: Option<usize>,
45    /// Current limit used in query
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub limit: Option<usize>,
48}
49
50/// Mutation response (INSERT, UPDATE, DELETE)
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct MutationResponse {
53    /// Number of rows affected
54    pub rows_affected: usize,
55}
56
57/// Schema information for a table
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct TableInfo {
60    /// Table name
61    pub name: String,
62    /// Column definitions
63    pub columns: Vec<ColumnInfo>,
64}
65
66/// Column schema information
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ColumnInfo {
69    /// Column name
70    pub name: String,
71    /// SQL data type
72    pub data_type: String,
73    /// Whether column is nullable
74    pub nullable: bool,
75    /// Whether column is primary key
76    pub primary_key: bool,
77}
78
79/// Error response
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ErrorResponse {
82    /// Error message
83    pub error: String,
84    /// Optional error code
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub code: Option<String>,
87}
88
89impl ErrorResponse {
90    pub fn new(error: impl Into<String>) -> Self {
91        Self { error: error.into(), code: None }
92    }
93
94    pub fn with_code(error: impl Into<String>, code: impl Into<String>) -> Self {
95        Self { error: error.into(), code: Some(code.into()) }
96    }
97}
98
99/// Health check response
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct HealthResponse {
102    pub status: String,
103    pub version: String,
104}
105
106/// Convert SqlValue to JSON
107pub fn sql_value_to_json(val: &SqlValue) -> JsonValue {
108    match val {
109        SqlValue::Null => JsonValue::Null,
110        SqlValue::Boolean(b) => JsonValue::Bool(*b),
111        SqlValue::Integer(i) => json!(*i),
112        SqlValue::Smallint(i) => json!(*i as i64),
113        SqlValue::Bigint(i) => json!(*i),
114        SqlValue::Unsigned(u) => json!(*u),
115        SqlValue::Numeric(f) => {
116            if f.is_nan() || f.is_infinite() {
117                JsonValue::Null
118            } else {
119                json!(*f)
120            }
121        }
122        SqlValue::Float(f) => {
123            if f.is_nan() || f.is_infinite() {
124                JsonValue::Null
125            } else {
126                json!(*f as f64)
127            }
128        }
129        SqlValue::Real(f) => {
130            if f.is_nan() || f.is_infinite() {
131                JsonValue::Null
132            } else {
133                json!(*f as f64)
134            }
135        }
136        SqlValue::Double(f) => {
137            if f.is_nan() || f.is_infinite() {
138                JsonValue::Null
139            } else {
140                json!(*f)
141            }
142        }
143        SqlValue::Character(s) | SqlValue::Varchar(s) => JsonValue::String(s.to_string()),
144        SqlValue::Timestamp(ts) => JsonValue::String(format!("{:?}", ts)),
145        SqlValue::Date(d) => JsonValue::String(format!("{:?}", d)),
146        SqlValue::Time(t) => JsonValue::String(format!("{:?}", t)),
147        SqlValue::Interval(_) => JsonValue::Null, // TODO: proper interval serialization
148        SqlValue::Vector(v) => json!(v),          // Vector as JSON array of floats
149    }
150}
151
152/// Convert JSON to SqlValue
153pub fn json_to_sql_value(val: &JsonValue) -> Result<SqlValue, String> {
154    match val {
155        JsonValue::Null => Ok(SqlValue::Null),
156        JsonValue::Bool(b) => Ok(SqlValue::Boolean(*b)),
157        JsonValue::Number(n) => {
158            if let Some(i) = n.as_i64() {
159                Ok(SqlValue::Integer(i))
160            } else if let Some(f) = n.as_f64() {
161                Ok(SqlValue::Numeric(f))
162            } else {
163                Err("Invalid number".to_string())
164            }
165        }
166        JsonValue::String(s) => Ok(SqlValue::Varchar(arcstr::ArcStr::from(s.clone()))),
167        JsonValue::Array(_) => Err("Arrays not yet supported".to_string()),
168        JsonValue::Object(_) => Err("Objects not yet supported".to_string()),
169    }
170}
171
172// ============================================================================
173// Blob Storage Types
174// ============================================================================
175
176/// Response for blob upload
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct BlobUploadResponse {
179    /// Unique blob ID
180    pub id: String,
181    /// Size in bytes
182    pub size: i64,
183    /// MIME content type
184    pub content_type: String,
185    /// URL to access the blob
186    pub url: String,
187}
188
189/// Response for blob metadata
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct BlobMetadataResponse {
192    /// Unique blob ID
193    pub id: String,
194    /// Size in bytes
195    pub size: i64,
196    /// MIME content type
197    pub content_type: String,
198    /// ISO 8601 timestamp when blob was created
199    pub created_at: String,
200}
201
202// ============================================================================
203// Subscription Efficiency Stats Types
204// ============================================================================
205
206/// Reasons why partial updates were not used
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct PartialUpdateFallbacks {
209    /// Partial updates disabled in configuration
210    pub disabled: u64,
211    /// Ratio of changed columns exceeded threshold
212    pub threshold_exceeded: u64,
213    /// Row count mismatch between expected and actual
214    pub row_count_mismatch: u64,
215    /// Primary key columns didn't match between updates
216    pub pk_mismatch: u64,
217    /// No columns changed in the update
218    pub no_changes: u64,
219}
220
221/// Subscription partial update efficiency statistics
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct SubscriptionEfficiencyStats {
224    /// Overall efficiency ratio (partial bytes saved / total possible bytes)
225    pub partial_update_efficiency: f64,
226    /// Total bytes saved by using partial updates
227    pub total_bytes_saved: u64,
228    /// Breakdown of fallback reasons when partial updates weren't used
229    pub fallbacks: PartialUpdateFallbacks,
230    /// Total partial updates sent
231    pub partial_updates_sent: u64,
232    /// Total full updates sent
233    pub full_updates_sent: u64,
234}