use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{Tuple, Value, Schema, Column};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableListResponse {
pub tables: Vec<TableInfoResponse>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableInfoResponse {
pub name: String,
pub column_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub row_count: Option<u64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DataQueryParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub columns: Option<String>,
#[serde(default = "default_page")]
pub page: u32,
#[serde(default = "default_limit")]
pub limit: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub as_of: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_by: Option<String>,
#[serde(default)]
pub include_total: bool,
}
fn default_page() -> u32 {
1
}
fn default_limit() -> u32 {
100
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataQueryResponse {
pub columns: Vec<ColumnInfo>,
pub rows: Vec<HashMap<String, serde_json::Value>>,
pub pagination: PaginationInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<QueryMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnInfo {
pub name: String,
pub data_type: String,
pub nullable: bool,
pub primary_key: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationInfo {
pub page: u32,
pub limit: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u64>,
pub has_more: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub as_of_timestamp: Option<u64>,
pub row_count: usize,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InsertDataRequest {
pub rows: Vec<HashMap<String, serde_json::Value>>,
#[serde(default)]
pub return_ids: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertDataResponse {
pub inserted: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub row_ids: Option<Vec<u64>>,
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateDataRequest {
pub values: HashMap<String, serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateDataResponse {
pub updated: u64,
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DeleteDataRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteDataResponse {
pub deleted: u64,
pub message: String,
}
impl From<&Column> for ColumnInfo {
fn from(column: &Column) -> Self {
ColumnInfo {
name: column.name.clone(),
data_type: format!("{:?}", column.data_type),
nullable: column.nullable,
primary_key: column.primary_key,
}
}
}
impl From<&Schema> for Vec<ColumnInfo> {
fn from(schema: &Schema) -> Self {
schema.columns.iter().map(ColumnInfo::from).collect()
}
}
pub fn tuple_to_map(tuple: &Tuple, schema: &Schema) -> HashMap<String, serde_json::Value> {
let mut map = HashMap::new();
for (idx, value) in tuple.values.iter().enumerate() {
if let Some(column) = schema.columns.get(idx) {
map.insert(column.name.clone(), value_to_json(value));
}
}
map
}
pub fn value_to_json(value: &Value) -> serde_json::Value {
match value {
Value::Null => serde_json::Value::Null,
Value::Boolean(b) => serde_json::Value::Bool(*b),
Value::Int2(i) => serde_json::Value::Number((*i).into()),
Value::Int4(i) => serde_json::Value::Number((*i).into()),
Value::Int8(i) => serde_json::Value::Number((*i).into()),
Value::Float4(f) => {
serde_json::Number::from_f64(*f as f64)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}
Value::Float8(f) => {
serde_json::Number::from_f64(*f)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}
Value::Numeric(n) => {
n.parse::<serde_json::Number>()
.map(serde_json::Value::Number)
.unwrap_or_else(|_| serde_json::Value::String(n.clone()))
}
Value::String(s) => serde_json::Value::String(s.clone()),
Value::Bytes(b) => {
use base64::Engine;
serde_json::Value::String(base64::prelude::BASE64_STANDARD.encode(b))
}
Value::Uuid(u) => serde_json::Value::String(u.to_string()),
Value::Timestamp(ts) => serde_json::Value::String(ts.to_rfc3339()),
Value::Date(d) => serde_json::Value::String(d.format("%Y-%m-%d").to_string()),
Value::Time(t) => serde_json::Value::String(t.format("%H:%M:%S%.f").to_string()),
Value::Json(json_str) => {
serde_json::from_str(json_str).unwrap_or(serde_json::Value::String(json_str.clone()))
}
Value::Array(arr) => {
serde_json::Value::Array(
arr.iter().map(value_to_json).collect()
)
}
Value::Vector(vec) => {
serde_json::Value::Array(
vec.iter()
.filter_map(|f| serde_json::Number::from_f64(*f as f64))
.map(serde_json::Value::Number)
.collect()
)
}
Value::DictRef { dict_id } => serde_json::Value::String(format!("dict:{}", dict_id)),
Value::CasRef { hash } => serde_json::Value::String(format!("cas:{}", hex::encode(hash))),
Value::ColumnarRef => serde_json::Value::Null,
Value::Interval(microseconds) => serde_json::Value::Number((*microseconds).into()),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchInferResponse {
pub schemas: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relationships: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationResponse {
pub optimized_ddl: String,
pub changes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub estimated_improvement: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaComparisonResponse {
pub differences: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub migration_sql: Option<String>,
pub compatibility_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NaturalLanguageSchemaResponse {
pub schema: String,
pub explanation: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub samples: Option<Vec<serde_json::Value>>,
pub suggestions: Vec<String>,
}
pub fn json_to_value(json: &serde_json::Value, target_type: &crate::DataType) -> Result<Value, String> {
match (json, target_type) {
(serde_json::Value::Null, _) => Ok(Value::Null),
(serde_json::Value::Bool(b), crate::DataType::Boolean) => Ok(Value::Boolean(*b)),
(serde_json::Value::Number(n), crate::DataType::Int2) => {
n.as_i64()
.and_then(|i| i16::try_from(i).ok())
.map(Value::Int2)
.ok_or_else(|| format!("Invalid Int2 value: {}", n))
}
(serde_json::Value::Number(n), crate::DataType::Int4) => {
n.as_i64()
.and_then(|i| i32::try_from(i).ok())
.map(Value::Int4)
.ok_or_else(|| format!("Invalid Int4 value: {}", n))
}
(serde_json::Value::Number(n), crate::DataType::Int8) => {
n.as_i64()
.map(Value::Int8)
.ok_or_else(|| format!("Invalid Int8 value: {}", n))
}
(serde_json::Value::Number(n), crate::DataType::Float4) => {
n.as_f64()
.map(|f| Value::Float4(f as f32))
.ok_or_else(|| format!("Invalid Float4 value: {}", n))
}
(serde_json::Value::Number(n), crate::DataType::Float8) => {
n.as_f64()
.map(Value::Float8)
.ok_or_else(|| format!("Invalid Float8 value: {}", n))
}
(serde_json::Value::String(s), crate::DataType::Text | crate::DataType::Varchar(_) | crate::DataType::Char(_)) => {
Ok(Value::String(s.clone()))
}
(serde_json::Value::String(s), crate::DataType::Bytea) => {
use base64::Engine;
if let Some(hex_str) = s.strip_prefix("\\x") {
hex::decode(hex_str)
.map(Value::Bytes)
.map_err(|e| format!("Invalid hex bytes: {}", e))
} else {
base64::prelude::BASE64_STANDARD.decode(s.as_bytes())
.map(Value::Bytes)
.map_err(|e| format!("Invalid base64 bytes: {}", e))
}
}
(serde_json::Value::String(s), crate::DataType::Uuid) => {
s.parse::<uuid::Uuid>()
.map(Value::Uuid)
.map_err(|e| format!("Invalid UUID: {}", e))
}
(serde_json::Value::String(s), crate::DataType::Timestamp | crate::DataType::Timestamptz) => {
s.parse::<chrono::DateTime<chrono::Utc>>()
.map(Value::Timestamp)
.map_err(|e| format!("Invalid timestamp: {}", e))
}
(serde_json::Value::String(s), crate::DataType::Json | crate::DataType::Jsonb) => {
Ok(Value::Json(s.clone()))
}
(serde_json::Value::Object(_) | serde_json::Value::Array(_), crate::DataType::Json | crate::DataType::Jsonb) => {
Ok(Value::Json(json.to_string()))
}
(serde_json::Value::Array(arr), crate::DataType::Vector(expected_dim)) => {
let values: Result<Vec<f32>, String> = arr.iter()
.map(|v| v.as_f64()
.map(|f| f as f32)
.ok_or_else(|| format!("Invalid vector element: {}", v))
)
.collect();
let values = values?;
if values.len() != *expected_dim {
return Err(format!(
"Vector dimension mismatch: expected {}, got {}",
expected_dim,
values.len()
));
}
Ok(Value::Vector(values))
}
_ => Err(format!(
"Cannot convert {:?} to {:?}",
json,
target_type
)),
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::DataType;
#[test]
fn test_value_to_json() {
assert_eq!(value_to_json(&Value::Null), serde_json::Value::Null);
assert_eq!(value_to_json(&Value::Boolean(true)), serde_json::Value::Bool(true));
assert_eq!(value_to_json(&Value::Int4(42)), serde_json::Value::Number(42.into()));
assert_eq!(value_to_json(&Value::String("test".to_string())), serde_json::Value::String("test".to_string()));
}
#[test]
fn test_json_to_value() {
let json = serde_json::Value::Number(42.into());
let result = json_to_value(&json, &DataType::Int4);
assert!(result.is_ok());
assert!(matches!(result.unwrap(), Value::Int4(42)));
let json = serde_json::Value::String("test".to_string());
let result = json_to_value(&json, &DataType::Text);
assert!(result.is_ok());
assert!(matches!(result.unwrap(), Value::String(s) if s == "test"));
}
#[test]
fn test_column_info_conversion() {
let column = Column {
name: "id".to_string(),
data_type: DataType::Int4,
nullable: false,
primary_key: true,
source_table: None,
source_table_name: None,
default_expr: None,
unique: false,
storage_mode: crate::ColumnStorageMode::Default,
};
let info = ColumnInfo::from(&column);
assert_eq!(info.name, "id");
assert!(!info.nullable);
assert!(info.primary_key);
}
#[test]
fn test_tuple_to_map() {
let schema = Schema::new(vec![
Column {
name: "id".to_string(),
data_type: DataType::Int4,
nullable: false,
primary_key: true,
source_table: None,
source_table_name: None,
default_expr: None,
unique: false,
storage_mode: crate::ColumnStorageMode::Default,
},
Column {
name: "name".to_string(),
data_type: DataType::Text,
nullable: true,
primary_key: false,
source_table: None,
source_table_name: None,
default_expr: None,
unique: false,
storage_mode: crate::ColumnStorageMode::Default,
},
]);
let tuple = Tuple::new(vec![
Value::Int4(1),
Value::String("Alice".to_string()),
]);
let map = tuple_to_map(&tuple, &schema);
assert_eq!(map.len(), 2);
assert_eq!(map.get("id"), Some(&serde_json::Value::Number(1.into())));
assert_eq!(map.get("name"), Some(&serde_json::Value::String("Alice".to_string())));
}
}