use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const VELESQL_CONTRACT_VERSION: &str = "2.1.0";
#[derive(Debug, Deserialize, ToSchema)]
pub struct CreateCollectionRequest {
#[schema(example = "documents")]
pub name: String,
#[schema(example = 768)]
pub dimension: Option<usize>,
#[serde(default = "default_metric")]
#[schema(example = "cosine")]
pub metric: String,
#[serde(default = "default_storage_mode")]
#[schema(example = "full")]
pub storage_mode: String,
#[serde(default = "default_collection_type")]
#[schema(example = "vector")]
pub collection_type: String,
}
fn default_collection_type() -> String {
"vector".to_string()
}
fn default_metric() -> String {
"cosine".to_string()
}
fn default_storage_mode() -> String {
"full".to_string()
}
#[derive(Debug, Serialize, ToSchema)]
pub struct CollectionResponse {
pub name: String,
pub dimension: usize,
pub metric: String,
pub point_count: usize,
pub storage_mode: String,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct UpsertPointsRequest {
pub points: Vec<PointRequest>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct PointRequest {
pub id: u64,
pub vector: Vec<f32>,
pub payload: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct SearchRequest {
pub vector: Vec<f32>,
#[serde(default = "default_top_k")]
pub top_k: usize,
#[serde(default)]
#[schema(example = "balanced")]
pub mode: Option<String>,
#[serde(default)]
#[schema(example = 128)]
pub ef_search: Option<usize>,
#[serde(default)]
#[schema(example = 30000)]
pub timeout_ms: Option<u64>,
#[serde(default)]
pub filter: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct BatchSearchRequest {
pub searches: Vec<SearchRequest>,
}
fn default_top_k() -> usize {
10
}
#[must_use]
pub fn mode_to_ef_search(mode: &str) -> Option<usize> {
match mode.to_lowercase().as_str() {
"fast" => Some(64),
"balanced" => Some(128),
"accurate" => Some(256),
"perfect" => Some(usize::MAX),
_ => None,
}
}
#[derive(Debug, Serialize, ToSchema)]
pub struct SearchResponse {
pub results: Vec<SearchResultResponse>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct BatchSearchResponse {
pub results: Vec<SearchResponse>,
pub timing_ms: f64,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct SearchResultResponse {
pub id: u64,
pub score: f32,
pub payload: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ErrorResponse {
pub error: String,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct TextSearchRequest {
#[schema(example = "rust programming")]
pub query: String,
#[serde(default = "default_top_k")]
#[schema(example = 10)]
pub top_k: usize,
#[serde(default)]
pub filter: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct HybridSearchRequest {
pub vector: Vec<f32>,
#[schema(example = "rust programming")]
pub query: String,
#[serde(default = "default_top_k")]
#[schema(example = 10)]
pub top_k: usize,
#[serde(default = "default_vector_weight")]
#[schema(example = 0.5)]
pub vector_weight: f32,
#[serde(default)]
pub filter: Option<serde_json::Value>,
}
fn default_vector_weight() -> f32 {
0.5
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct MultiQuerySearchRequest {
pub vectors: Vec<Vec<f32>>,
#[serde(default = "default_top_k")]
#[schema(example = 10)]
pub top_k: usize,
#[serde(default = "default_fusion_strategy")]
#[schema(example = "rrf")]
pub strategy: String,
#[serde(default = "default_rrf_k")]
#[schema(example = 60)]
pub rrf_k: u32,
#[serde(default = "default_avg_weight")]
#[schema(example = 0.5)]
pub avg_weight: f32,
#[serde(default = "default_max_weight")]
#[schema(example = 0.3)]
pub max_weight: f32,
#[serde(default = "default_hit_weight")]
#[schema(example = 0.2)]
pub hit_weight: f32,
#[serde(default)]
pub filter: Option<serde_json::Value>,
}
fn default_fusion_strategy() -> String {
"rrf".to_string()
}
fn default_rrf_k() -> u32 {
60
}
fn default_avg_weight() -> f32 {
0.5
}
fn default_max_weight() -> f32 {
0.3
}
fn default_hit_weight() -> f32 {
0.2
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct QueryRequest {
pub query: String,
#[serde(default)]
pub params: std::collections::HashMap<String, serde_json::Value>,
#[serde(default)]
pub collection: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum QueryType {
Search,
Aggregation,
Rows,
Graph,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct QueryResponse {
pub results: Vec<SearchResultResponse>,
pub timing_ms: f64,
pub took_ms: u64,
pub rows_returned: usize,
pub meta: QueryResponseMeta,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct QueryResponseMeta {
pub velesql_contract_version: String,
pub count: usize,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct UnifiedQueryResponse {
#[serde(rename = "type")]
pub query_type: QueryType,
pub count: usize,
pub timing_ms: f64,
pub results: serde_json::Value,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<String>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct AggregationResponse {
pub result: serde_json::Value,
pub timing_ms: f64,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct QueryErrorResponse {
pub error: QueryErrorDetail,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct ExplainRequest {
#[schema(example = "SELECT * FROM docs WHERE category = 'tech' AND vector NEAR $v LIMIT 10")]
pub query: String,
#[serde(default)]
pub params: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ExplainResponse {
pub query: String,
pub query_type: String,
pub collection: String,
pub plan: Vec<ExplainStep>,
pub estimated_cost: ExplainCost,
pub features: ExplainFeatures,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ExplainStep {
pub step: usize,
pub operation: String,
pub description: String,
pub estimated_rows: Option<usize>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ExplainCost {
pub uses_index: bool,
pub index_name: Option<String>,
pub selectivity: f64,
pub complexity: String,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Serialize, ToSchema)]
pub struct ExplainFeatures {
pub has_vector_search: bool,
pub has_filter: bool,
pub has_order_by: bool,
pub has_group_by: bool,
pub has_aggregation: bool,
pub has_join: bool,
pub has_fusion: bool,
pub limit: Option<u64>,
pub offset: Option<u64>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct QueryErrorDetail {
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
pub position: usize,
pub query: String,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct VelesqlErrorDetail {
pub code: String,
pub message: String,
pub hint: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct VelesqlErrorResponse {
pub error: VelesqlErrorDetail,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct CreateIndexRequest {
#[schema(example = "Person")]
pub label: String,
#[schema(example = "email")]
pub property: String,
#[serde(default = "default_index_type")]
#[schema(example = "hash")]
pub index_type: String,
}
fn default_index_type() -> String {
"hash".to_string()
}
#[derive(Debug, Serialize, ToSchema)]
pub struct IndexResponse {
pub label: String,
pub property: String,
pub index_type: String,
pub cardinality: usize,
pub memory_bytes: usize,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct ListIndexesResponse {
pub indexes: Vec<IndexResponse>,
pub total: usize,
}