use base64::{Engine as _, engine::general_purpose::STANDARD};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct QueryRequest {
pub sql: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Vec<QueryParameter>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub as_of: Option<AsOfSpec>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ExecuteRequest {
pub sql: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Vec<QueryParameter>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "lowercase")]
pub enum QueryParameter {
Null,
Int4(i32),
Int8(i64),
Float4(f32),
Float8(f64),
String(String),
Boolean(bool),
Json(serde_json::Value),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum AsOfSpec {
Now,
Timestamp {
value: u64,
},
Transaction {
value: u64,
},
Scn {
value: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryResponse {
pub columns: Vec<String>,
pub column_types: Vec<String>,
pub rows: Vec<HashMap<String, serde_json::Value>>,
pub row_count: usize,
pub execution_time_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecuteResponse {
pub statement_type: String,
pub affected_rows: u64,
pub execution_time_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl From<QueryParameter> for crate::Value {
fn from(param: QueryParameter) -> Self {
match param {
QueryParameter::Null => crate::Value::Null,
QueryParameter::Int4(v) => crate::Value::Int4(v),
QueryParameter::Int8(v) => crate::Value::Int8(v),
QueryParameter::Float4(v) => crate::Value::Float4(v),
QueryParameter::Float8(v) => crate::Value::Float8(v),
QueryParameter::String(v) => crate::Value::String(v),
QueryParameter::Boolean(v) => crate::Value::Boolean(v),
QueryParameter::Json(v) => crate::Value::Json(v.to_string()),
}
}
}
impl From<&crate::Value> for serde_json::Value {
fn from(value: &crate::Value) -> Self {
match value {
crate::Value::Null => serde_json::Value::Null,
crate::Value::Boolean(v) => serde_json::Value::Bool(*v),
crate::Value::Int2(v) => serde_json::Value::Number((*v).into()),
crate::Value::Int4(v) => serde_json::Value::Number((*v).into()),
crate::Value::Int8(v) => serde_json::Value::Number((*v).into()),
crate::Value::Float4(v) => {
serde_json::Number::from_f64(*v as f64)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}
crate::Value::Float8(v) => {
serde_json::Number::from_f64(*v)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}
crate::Value::Numeric(v) => {
v.parse::<serde_json::Number>()
.map(serde_json::Value::Number)
.unwrap_or_else(|_| serde_json::Value::String(v.clone()))
}
crate::Value::String(v) => serde_json::Value::String(v.clone()),
crate::Value::Bytes(v) => {
serde_json::Value::String(STANDARD.encode(v))
}
crate::Value::Uuid(v) => serde_json::Value::String(v.to_string()),
crate::Value::Timestamp(v) => serde_json::Value::String(v.to_rfc3339()),
crate::Value::Date(v) => serde_json::Value::String(v.format("%Y-%m-%d").to_string()),
crate::Value::Time(v) => serde_json::Value::String(v.format("%H:%M:%S%.f").to_string()),
crate::Value::Json(v) => {
serde_json::from_str(v).unwrap_or(serde_json::Value::Null)
}
crate::Value::Array(v) => {
serde_json::Value::Array(
v.iter().map(|val| serde_json::Value::from(val)).collect()
)
}
crate::Value::Vector(v) => {
serde_json::Value::Array(
v.iter().map(|f| {
serde_json::Number::from_f64(*f as f64)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}).collect()
)
}
crate::Value::DictRef { dict_id } => {
serde_json::Value::String(format!("dict:{}", dict_id))
}
crate::Value::CasRef { hash } => {
serde_json::Value::String(format!("cas:{}", hex::encode(hash)))
}
crate::Value::ColumnarRef => serde_json::Value::Null,
crate::Value::Interval(microseconds) => {
serde_json::Value::Number((*microseconds).into())
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_query_request_serialization() {
let request = QueryRequest {
sql: "SELECT * FROM users WHERE id = $1".to_string(),
params: Some(vec![QueryParameter::Int4(1)]),
as_of: None,
timeout_ms: Some(5000),
};
let json = serde_json::to_string(&request).unwrap();
let deserialized: QueryRequest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.sql, "SELECT * FROM users WHERE id = $1");
assert!(deserialized.params.is_some());
assert_eq!(deserialized.timeout_ms, Some(5000));
}
#[test]
fn test_execute_request_serialization() {
let request = ExecuteRequest {
sql: "INSERT INTO users (id, name) VALUES ($1, $2)".to_string(),
params: Some(vec![
QueryParameter::Int4(1),
QueryParameter::String("Alice".to_string()),
]),
timeout_ms: None,
};
let json = serde_json::to_string(&request).unwrap();
let deserialized: ExecuteRequest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.sql, "INSERT INTO users (id, name) VALUES ($1, $2)");
assert!(deserialized.params.is_some());
}
#[test]
fn test_as_of_spec_serialization() {
let specs = vec![
AsOfSpec::Now,
AsOfSpec::Timestamp { value: 1234567890 },
AsOfSpec::Transaction { value: 42 },
AsOfSpec::Scn { value: 100 },
];
for spec in specs {
let json = serde_json::to_string(&spec).unwrap();
let deserialized: AsOfSpec = serde_json::from_str(&json).unwrap();
drop(deserialized);
}
}
#[test]
fn test_query_parameter_conversion() {
let param = QueryParameter::Int4(42);
let value: crate::Value = param.into();
assert!(matches!(value, crate::Value::Int4(42)));
let param = QueryParameter::String("test".to_string());
let value: crate::Value = param.into();
assert!(matches!(value, crate::Value::String(_)));
}
#[test]
fn test_query_response_serialization() {
let mut row = HashMap::new();
row.insert("id".to_string(), serde_json::json!(1));
row.insert("name".to_string(), serde_json::json!("Alice"));
let response = QueryResponse {
columns: vec!["id".to_string(), "name".to_string()],
column_types: vec!["int4".to_string(), "text".to_string()],
rows: vec![row],
row_count: 1,
execution_time_ms: 42,
};
let json = serde_json::to_string(&response).unwrap();
let deserialized: QueryResponse = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.row_count, 1);
assert_eq!(deserialized.columns.len(), 2);
assert_eq!(deserialized.execution_time_ms, 42);
}
#[test]
fn test_execute_response_serialization() {
let response = ExecuteResponse {
statement_type: "INSERT".to_string(),
affected_rows: 5,
execution_time_ms: 23,
message: Some("Successfully inserted 5 rows".to_string()),
};
let json = serde_json::to_string(&response).unwrap();
let deserialized: ExecuteResponse = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.statement_type, "INSERT");
assert_eq!(deserialized.affected_rows, 5);
assert_eq!(deserialized.execution_time_ms, 23);
}
}