Draft implementation of filter support for /delete-by-batch route

This commit is contained in:
Louis Dureuil 2023-03-01 15:20:53 +01:00
parent 1afde4fea5
commit 05cc463fbc
No known key found for this signature in database
2 changed files with 81 additions and 6 deletions

View File

@ -17,6 +17,7 @@ use meilisearch_types::error::{Code, ResponseError};
use meilisearch_types::heed::RoTxn;
use meilisearch_types::index_uid::IndexUid;
use meilisearch_types::milli::update::IndexDocumentsMethod;
use meilisearch_types::milli::InternalError;
use meilisearch_types::star_or::OptionStarOrList;
use meilisearch_types::tasks::KindWithContent;
use meilisearch_types::{milli, Document, Index};
@ -373,22 +374,96 @@ async fn document_addition(
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(
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
index_uid: web::Path<String>,
body: web::Json<Vec<Value>>,
body: web::Json<DocumentDeletionQuery>,
req: HttpRequest,
analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> {
debug!("called with params: {:?}", body);
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
let index_uid = index_uid.into_inner();
analytics.delete_documents(DocumentDeletionKind::PerBatch, &req);
let ids = body
.iter()
.map(|v| v.as_str().map(String::from).unwrap_or_else(|| v.to_string()))
.collect();
let ids = match body.into_inner() {
DocumentDeletionQuery::Ids(body) => body
.iter()
.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 =
KindWithContent::DocumentDeletion { index_uid: index_uid.to_string(), documents_ids: ids };

View File

@ -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 {
Value::String(expr) => {
let condition = Filter::from_str(expr)?;