use std::collections::BTreeSet; use actix_web as aweb; use actix_web::{delete, get, post, put, web, HttpResponse}; use indexmap::IndexMap; use serde::Deserialize; use serde_json::Value; use crate::error::ResponseError; use crate::routes::{IndexParam, IndexUpdateResponse}; use crate::Data; type Document = IndexMap; #[derive(Default, Deserialize)] pub struct DocumentParam { index_uid: String, document_id: String, } #[get("/indexes/{index_uid}/documents/{document_id}")] pub async fn get_document( data: web::Data, path: web::Path, ) -> aweb::Result> { let index = data .db .open_index(&path.index_uid) .ok_or(ResponseError::IndexNotFound(path.index_uid.clone()))?; let document_id = meilisearch_core::serde::compute_document_id(path.document_id.clone()); let reader = data .db .main_read_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let response = index .document::(&reader, None, document_id) .map_err(|_| ResponseError::DocumentNotFound(path.document_id.clone()))? .ok_or(ResponseError::DocumentNotFound(path.document_id.clone()))?; Ok(web::Json(response)) } #[delete("/indexes/{index_uid}/documents/{document_id}")] pub async fn delete_document( data: web::Data, path: web::Path, ) -> aweb::Result { let index = data .db .open_index(&path.index_uid) .ok_or(ResponseError::IndexNotFound(path.index_uid.clone()))?; let document_id = meilisearch_core::serde::compute_document_id(path.document_id.clone()); let mut update_writer = data .db .update_write_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let mut documents_deletion = index.documents_deletion(); documents_deletion.delete_document_by_id(document_id); let update_id = documents_deletion .finalize(&mut update_writer) .map_err(|err| ResponseError::Internal(err.to_string()))?; update_writer .commit() .map_err(|err| ResponseError::Internal(err.to_string()))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } #[derive(Default, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct BrowseQuery { offset: Option, limit: Option, attributes_to_retrieve: Option, } #[get("/indexes/{index_uid}/documents")] pub async fn get_all_documents( data: web::Data, path: web::Path, params: web::Query, ) -> aweb::Result>> { let index = data .db .open_index(&path.index_uid) .ok_or(ResponseError::IndexNotFound(path.index_uid.clone()))?; let offset = params.offset.unwrap_or(0); let limit = params.limit.unwrap_or(20); let reader = data .db .main_read_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let documents_ids: Result, _> = index .documents_fields_counts .documents_ids(&reader) .map_err(|_| ResponseError::Internal(path.index_uid.clone()))? .skip(offset) .take(limit) .collect(); let documents_ids = documents_ids.map_err(|err| ResponseError::Internal(err.to_string()))?; let attributes = params .attributes_to_retrieve .clone() .map(|a| a.split(',').map(|a| a.to_string()).collect()); let mut response_body = Vec::::new(); for document_id in documents_ids { if let Ok(Some(document)) = index.document(&reader, attributes.clone(), document_id) { response_body.push(document); } } Ok(web::Json(response_body)) } fn find_primary_key(document: &IndexMap) -> Option { for key in document.keys() { if key.to_lowercase().contains("id") { return Some(key.to_string()); } } None } #[derive(Default, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct UpdateDocumentsQuery { primary_key: Option, } async fn update_multiple_documents( data: web::Data, path: web::Path, params: web::Query, body: web::Json>, is_partial: bool, ) -> aweb::Result { let index = data .db .open_index(path.index_uid.clone()) .ok_or(ResponseError::IndexNotFound(path.index_uid.clone()))?; let reader = data .db .main_read_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let mut schema = index .main .schema(&reader) .map_err(|err| ResponseError::Internal(err.to_string()))? .ok_or(ResponseError::Internal( "Impossible to retrieve the schema".to_string(), ))?; if schema.primary_key().is_none() { let id = match params.primary_key.clone() { Some(id) => id, None => body.first().and_then(|docs| find_primary_key(docs)).ok_or( ResponseError::BadRequest("Impossible to infer the primary key".to_string()), )?, }; let mut writer = data .db .main_write_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; schema .set_primary_key(&id) .map_err(|e| ResponseError::Internal(e.to_string()))?; index .main .put_schema(&mut writer, &schema) .map_err(|e| ResponseError::Internal(e.to_string()))?; writer .commit() .map_err(|err| ResponseError::Internal(err.to_string()))?; } let mut document_addition = if is_partial { index.documents_partial_addition() } else { index.documents_addition() }; for document in body.into_inner() { document_addition.update_document(document); } let mut update_writer = data .db .update_write_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let update_id = document_addition .finalize(&mut update_writer) .map_err(|e| ResponseError::Internal(e.to_string()))?; update_writer .commit() .map_err(|err| ResponseError::Internal(err.to_string()))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } #[post("/indexes/{index_uid}/documents")] pub async fn add_documents( data: web::Data, path: web::Path, params: web::Query, body: web::Json>, ) -> aweb::Result { update_multiple_documents(data, path, params, body, false).await } #[put("/indexes/{index_uid}/documents")] pub async fn update_documents( data: web::Data, path: web::Path, params: web::Query, body: web::Json>, ) -> aweb::Result { update_multiple_documents(data, path, params, body, true).await } #[post("/indexes/{index_uid}/documents/delete-batch")] pub async fn delete_documents( data: web::Data, path: web::Path, body: web::Json>, ) -> aweb::Result { let index = data .db .open_index(path.index_uid.clone()) .ok_or(ResponseError::IndexNotFound(path.index_uid.clone()))?; let mut writer = data .db .update_write_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let mut documents_deletion = index.documents_deletion(); for document_id in body.into_inner() { if let Some(document_id) = meilisearch_core::serde::value_to_string(&document_id) { documents_deletion .delete_document_by_id(meilisearch_core::serde::compute_document_id(document_id)); } } let update_id = documents_deletion .finalize(&mut writer) .map_err(|e| ResponseError::Internal(e.to_string()))?; writer .commit() .map_err(|err| ResponseError::Internal(err.to_string()))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) } #[delete("/indexes/{index_uid}/documents")] pub async fn clear_all_documents( data: web::Data, path: web::Path, ) -> aweb::Result { let index = data .db .open_index(path.index_uid.clone()) .ok_or(ResponseError::IndexNotFound(path.index_uid.clone()))?; let mut writer = data .db .update_write_txn() .map_err(|err| ResponseError::Internal(err.to_string()))?; let update_id = index .clear_all(&mut writer) .map_err(|e| ResponseError::Internal(e.to_string()))?; writer .commit() .map_err(|err| ResponseError::Internal(err.to_string()))?; Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) }