mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-01-31 15:31:53 +08:00
Draft implementation of filter support for /delete-by-batch route
This commit is contained in:
parent
1afde4fea5
commit
05cc463fbc
@ -17,6 +17,7 @@ use meilisearch_types::error::{Code, ResponseError};
|
|||||||
use meilisearch_types::heed::RoTxn;
|
use meilisearch_types::heed::RoTxn;
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use meilisearch_types::milli::update::IndexDocumentsMethod;
|
use meilisearch_types::milli::update::IndexDocumentsMethod;
|
||||||
|
use meilisearch_types::milli::InternalError;
|
||||||
use meilisearch_types::star_or::OptionStarOrList;
|
use meilisearch_types::star_or::OptionStarOrList;
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use meilisearch_types::{milli, Document, Index};
|
use meilisearch_types::{milli, Document, Index};
|
||||||
@ -373,22 +374,96 @@ async fn document_addition(
|
|||||||
Ok(task.into())
|
Ok(task.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum DocumentDeletionQuery {
|
||||||
|
Ids(Vec<Value>),
|
||||||
|
Object { filter: Option<Value> },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a Json encoded document id and validate it, returning a user error when it is one.
|
||||||
|
/// FIXME: stolen from milli
|
||||||
|
fn validate_document_id_value(document_id: Value) -> String {
|
||||||
|
match document_id {
|
||||||
|
Value::String(string) => match validate_document_id(&string) {
|
||||||
|
Some(s) if s.len() == string.len() => string,
|
||||||
|
Some(s) => s.to_string(),
|
||||||
|
None => panic!(),
|
||||||
|
},
|
||||||
|
Value::Number(number) if number.is_i64() => number.to_string(),
|
||||||
|
_content => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: stolen from milli
|
||||||
|
fn validate_document_id(document_id: &str) -> Option<&str> {
|
||||||
|
if !document_id.is_empty()
|
||||||
|
&& document_id.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_'))
|
||||||
|
{
|
||||||
|
Some(document_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_documents(
|
pub async fn delete_documents(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
body: web::Json<Vec<Value>>,
|
body: web::Json<DocumentDeletionQuery>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", body);
|
debug!("called with params: {:?}", body);
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
let index_uid = index_uid.into_inner();
|
||||||
|
|
||||||
analytics.delete_documents(DocumentDeletionKind::PerBatch, &req);
|
analytics.delete_documents(DocumentDeletionKind::PerBatch, &req);
|
||||||
|
|
||||||
let ids = body
|
let ids = match body.into_inner() {
|
||||||
.iter()
|
DocumentDeletionQuery::Ids(body) => body
|
||||||
.map(|v| v.as_str().map(String::from).unwrap_or_else(|| v.to_string()))
|
.iter()
|
||||||
.collect();
|
.map(|v| v.as_str().map(String::from).unwrap_or_else(|| v.to_string()))
|
||||||
|
.collect(),
|
||||||
|
DocumentDeletionQuery::Object { filter } => {
|
||||||
|
debug!("filter: {:?}", filter);
|
||||||
|
|
||||||
|
// FIXME: spawn_blocking
|
||||||
|
if let Some(ref filter) = filter {
|
||||||
|
if let Some(facets) = crate::search::parse_filter(filter)? {
|
||||||
|
debug!("facets: {:?}", facets);
|
||||||
|
|
||||||
|
let index = index_scheduler.index(&index_uid)?;
|
||||||
|
let rtxn = index.read_txn()?;
|
||||||
|
let filtered_candidates = facets.evaluate(&rtxn, &index)?;
|
||||||
|
debug!("filtered_candidates.len(): {:?}", filtered_candidates.len());
|
||||||
|
|
||||||
|
// FIXME: unwraps
|
||||||
|
let primary_key = index.primary_key(&rtxn)?.unwrap();
|
||||||
|
let primary_key = index.fields_ids_map(&rtxn)?.id(primary_key).unwrap();
|
||||||
|
|
||||||
|
let documents = index.documents(&rtxn, filtered_candidates.into_iter())?;
|
||||||
|
debug!("documents.len(): {:?}", documents.len());
|
||||||
|
let documents: Vec<String> = documents
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, document)| {
|
||||||
|
let value = document.get(primary_key).unwrap();
|
||||||
|
let value: Value = serde_json::from_slice(value)
|
||||||
|
.map_err(InternalError::SerdeJson)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
validate_document_id_value(value)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
debug!("documents: {:?}", documents);
|
||||||
|
documents
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let task =
|
let task =
|
||||||
KindWithContent::DocumentDeletion { index_uid: index_uid.to_string(), documents_ids: ids };
|
KindWithContent::DocumentDeletion { index_uid: index_uid.to_string(), documents_ids: ids };
|
||||||
|
@ -745,7 +745,7 @@ fn format_value<A: AsRef<[u8]>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_filter(facets: &Value) -> Result<Option<Filter>, MeilisearchHttpError> {
|
pub fn parse_filter(facets: &Value) -> Result<Option<Filter>, MeilisearchHttpError> {
|
||||||
match facets {
|
match facets {
|
||||||
Value::String(expr) => {
|
Value::String(expr) => {
|
||||||
let condition = Filter::from_str(expr)?;
|
let condition = Filter::from_str(expr)?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user