1use axum::{
4 extract::{Path, State},
5 http::StatusCode,
6 response::IntoResponse,
7 Json,
8};
9use std::sync::Arc;
10
11use crate::types::{CreateIndexRequest, ErrorResponse, IndexResponse, ListIndexesResponse};
12use crate::AppState;
13
14use super::helpers::{auto_core_error_response, error_response, get_vector_collection_or_404};
15
16#[utoipa::path(
18 post,
19 path = "/collections/{name}/indexes",
20 tag = "indexes",
21 request_body = CreateIndexRequest,
22 params(
23 ("name" = String, Path, description = "Collection name")
24 ),
25 responses(
26 (status = 201, description = "Index created", body = IndexResponse),
27 (status = 400, description = "Invalid request", body = ErrorResponse),
28 (status = 404, description = "Collection not found", body = ErrorResponse)
29 )
30)]
31pub async fn create_index(
32 State(state): State<Arc<AppState>>,
33 Path(name): Path<String>,
34 Json(req): Json<CreateIndexRequest>,
35) -> impl IntoResponse {
36 let collection = match get_vector_collection_or_404(&state, &name) {
37 Ok(c) => c,
38 Err(resp) => return resp,
39 };
40
41 let result = match dispatch_index_creation(&collection, &req) {
42 Ok(r) => r,
43 Err(resp) => return resp,
44 };
45
46 match result {
47 Ok(()) => {
48 let (cardinality, memory_bytes) = collection
50 .list_indexes()
51 .into_iter()
52 .find(|i| i.label == req.label && i.property == req.property)
53 .map_or((0, 0), |i| (i.cardinality, i.memory_bytes));
54
55 (
56 StatusCode::CREATED,
57 Json(IndexResponse {
58 label: req.label,
59 property: req.property,
60 index_type: req.index_type,
61 cardinality,
62 memory_bytes,
63 }),
64 )
65 .into_response()
66 }
67 Err(e) => auto_core_error_response(&e),
68 }
69}
70
71#[allow(clippy::result_large_err)]
73fn dispatch_index_creation(
74 collection: &velesdb_core::collection::VectorCollection,
75 req: &CreateIndexRequest,
76) -> Result<velesdb_core::error::Result<()>, axum::response::Response> {
77 match req.index_type.to_lowercase().as_str() {
78 "hash" => Ok(collection.create_property_index(&req.label, &req.property)),
79 "range" => Ok(collection.create_range_index(&req.label, &req.property)),
80 _ => Err(error_response(
81 StatusCode::BAD_REQUEST,
82 format!("Invalid index_type: {}. Valid: hash, range", req.index_type),
83 )),
84 }
85}
86
87#[utoipa::path(
89 get,
90 path = "/collections/{name}/indexes",
91 tag = "indexes",
92 params(
93 ("name" = String, Path, description = "Collection name")
94 ),
95 responses(
96 (status = 200, description = "List of indexes", body = ListIndexesResponse),
97 (status = 404, description = "Collection not found", body = ErrorResponse)
98 )
99)]
100pub async fn list_indexes(
101 State(state): State<Arc<AppState>>,
102 Path(name): Path<String>,
103) -> impl IntoResponse {
104 let collection = match get_vector_collection_or_404(&state, &name) {
105 Ok(c) => c,
106 Err(resp) => return resp,
107 };
108
109 let core_indexes = collection.list_indexes();
110 let indexes: Vec<IndexResponse> = core_indexes
111 .into_iter()
112 .map(|i| IndexResponse {
113 label: i.label,
114 property: i.property,
115 index_type: i.index_type,
116 cardinality: i.cardinality,
117 memory_bytes: i.memory_bytes,
118 })
119 .collect();
120 let total = indexes.len();
121
122 Json(ListIndexesResponse { indexes, total }).into_response()
123}
124
125#[utoipa::path(
127 delete,
128 path = "/collections/{name}/indexes/{label}/{property}",
129 tag = "indexes",
130 params(
131 ("name" = String, Path, description = "Collection name"),
132 ("label" = String, Path, description = "Node label"),
133 ("property" = String, Path, description = "Property name")
134 ),
135 responses(
136 (status = 200, description = "Index deleted", body = Object),
137 (status = 404, description = "Index or collection not found", body = ErrorResponse)
138 )
139)]
140pub async fn delete_index(
141 State(state): State<Arc<AppState>>,
142 Path((name, label, property)): Path<(String, String, String)>,
143) -> impl IntoResponse {
144 let collection = match get_vector_collection_or_404(&state, &name) {
145 Ok(c) => c,
146 Err(resp) => return resp,
147 };
148
149 match collection.drop_index(&label, &property) {
150 Ok(dropped) => {
151 if dropped {
152 Json(serde_json::json!({
153 "message": "Index deleted",
154 "label": label,
155 "property": property
156 }))
157 .into_response()
158 } else {
159 error_response(
160 StatusCode::NOT_FOUND,
161 format!("Index on {}.{} not found", label, property),
162 )
163 }
164 }
165 Err(e) => auto_core_error_response(&e),
166 }
167}