mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-25 22:34:28 +01:00
Merge #2453
2453: test index resolver r=MarinPostma a=MarinPostma add some tests to the `IndexResolver` implementation of `BatchHandler` Co-authored-by: ad hoc <postma.marin@protonmail.com>
This commit is contained in:
commit
0928f3d41c
@ -121,7 +121,6 @@ mod real {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -137,12 +136,12 @@ mod test {
|
|||||||
|
|
||||||
pub enum MockDumpHandler<U, I> {
|
pub enum MockDumpHandler<U, I> {
|
||||||
Real(super::real::DumpHandler<U, I>),
|
Real(super::real::DumpHandler<U, I>),
|
||||||
Mock(Mocker, PhantomData<(U, I)>),
|
Mock(Mocker),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<U, I> MockDumpHandler<U, I> {
|
impl<U, I> MockDumpHandler<U, I> {
|
||||||
pub fn mock(mocker: Mocker) -> Self {
|
pub fn mock(mocker: Mocker) -> Self {
|
||||||
Self::Mock(mocker, PhantomData)
|
Self::Mock(mocker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +172,7 @@ mod test {
|
|||||||
pub async fn run(&self, uid: String) -> Result<()> {
|
pub async fn run(&self, uid: String) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
DumpHandler::Real(real) => real.run(uid).await,
|
DumpHandler::Real(real) => real.run(uid).await,
|
||||||
DumpHandler::Mock(mocker, _) => unsafe { mocker.get("run").call(uid) },
|
DumpHandler::Mock(mocker) => unsafe { mocker.get("run").call(uid) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use tokio::sync::mpsc::error::SendError as MpscSendError;
|
|||||||
use tokio::sync::oneshot::error::RecvError as OneshotRecvError;
|
use tokio::sync::oneshot::error::RecvError as OneshotRecvError;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{error::MilliError, index::error::IndexError};
|
use crate::{error::MilliError, index::error::IndexError, update_file_store::UpdateFileStoreError};
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, IndexResolverError>;
|
pub type Result<T> = std::result::Result<T, IndexResolverError>;
|
||||||
|
|
||||||
@ -49,7 +49,8 @@ internal_error!(
|
|||||||
uuid::Error,
|
uuid::Error,
|
||||||
std::io::Error,
|
std::io::Error,
|
||||||
tokio::task::JoinError,
|
tokio::task::JoinError,
|
||||||
serde_json::Error
|
serde_json::Error,
|
||||||
|
UpdateFileStoreError
|
||||||
);
|
);
|
||||||
|
|
||||||
impl ErrorCode for IndexResolverError {
|
impl ErrorCode for IndexResolverError {
|
||||||
|
@ -27,6 +27,12 @@ use self::meta_store::IndexMeta;
|
|||||||
|
|
||||||
pub type HardStateIndexResolver = IndexResolver<HeedMetaStore, MapIndexStore>;
|
pub type HardStateIndexResolver = IndexResolver<HeedMetaStore, MapIndexStore>;
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
pub use real::IndexResolver;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub use test::MockIndexResolver as IndexResolver;
|
||||||
|
|
||||||
/// An index uid is composed of only ascii alphanumeric characters, - and _, between 1 and 400
|
/// An index uid is composed of only ascii alphanumeric characters, - and _, between 1 and 400
|
||||||
/// bytes long
|
/// bytes long
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
@ -96,349 +102,448 @@ impl FromStr for IndexUid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IndexResolver<U, I> {
|
mod real {
|
||||||
index_uuid_store: U,
|
use super::*;
|
||||||
index_store: I,
|
|
||||||
pub file_store: UpdateFileStore,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IndexResolver<HeedMetaStore, MapIndexStore> {
|
pub struct IndexResolver<U, I> {
|
||||||
pub fn load_dump(
|
pub(super) index_uuid_store: U,
|
||||||
src: impl AsRef<Path>,
|
pub(super) index_store: I,
|
||||||
dst: impl AsRef<Path>,
|
pub(super) file_store: UpdateFileStore,
|
||||||
index_db_size: usize,
|
|
||||||
env: Arc<Env>,
|
|
||||||
indexer_opts: &IndexerOpts,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
HeedMetaStore::load_dump(&src, env)?;
|
|
||||||
let indexes_path = src.as_ref().join("indexes");
|
|
||||||
let indexes = indexes_path.read_dir()?;
|
|
||||||
let indexer_config = IndexerConfig::try_from(indexer_opts)?;
|
|
||||||
for index in indexes {
|
|
||||||
Index::load_dump(&index?.path(), &dst, index_db_size, &indexer_config)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, I> IndexResolver<U, I>
|
impl IndexResolver<HeedMetaStore, MapIndexStore> {
|
||||||
where
|
pub fn load_dump(
|
||||||
U: IndexMetaStore,
|
src: impl AsRef<Path>,
|
||||||
I: IndexStore,
|
dst: impl AsRef<Path>,
|
||||||
{
|
index_db_size: usize,
|
||||||
pub fn new(index_uuid_store: U, index_store: I, file_store: UpdateFileStore) -> Self {
|
env: Arc<Env>,
|
||||||
Self {
|
indexer_opts: &IndexerOpts,
|
||||||
index_uuid_store,
|
) -> anyhow::Result<()> {
|
||||||
index_store,
|
HeedMetaStore::load_dump(&src, env)?;
|
||||||
file_store,
|
let indexes_path = src.as_ref().join("indexes");
|
||||||
|
let indexes = indexes_path.read_dir()?;
|
||||||
|
let indexer_config = IndexerConfig::try_from(indexer_opts)?;
|
||||||
|
for index in indexes {
|
||||||
|
Index::load_dump(&index?.path(), &dst, index_db_size, &indexer_config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_document_addition_batch(&self, mut tasks: Vec<Task>) -> Vec<Task> {
|
impl<U, I> IndexResolver<U, I>
|
||||||
fn get_content_uuid(task: &Task) -> Uuid {
|
where
|
||||||
match task {
|
U: IndexMetaStore,
|
||||||
Task {
|
I: IndexStore,
|
||||||
content: TaskContent::DocumentAddition { content_uuid, .. },
|
{
|
||||||
..
|
pub fn new(index_uuid_store: U, index_store: I, file_store: UpdateFileStore) -> Self {
|
||||||
} => *content_uuid,
|
Self {
|
||||||
_ => panic!("unexpected task in the document addition batch"),
|
index_uuid_store,
|
||||||
|
index_store,
|
||||||
|
file_store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_uuids = tasks.iter().map(get_content_uuid).collect::<Vec<_>>();
|
pub async fn process_document_addition_batch(&self, mut tasks: Vec<Task>) -> Vec<Task> {
|
||||||
|
fn get_content_uuid(task: &Task) -> Uuid {
|
||||||
match tasks.first() {
|
match task {
|
||||||
Some(Task {
|
Task {
|
||||||
id,
|
content: TaskContent::DocumentAddition { content_uuid, .. },
|
||||||
content:
|
|
||||||
TaskContent::DocumentAddition {
|
|
||||||
merge_strategy,
|
|
||||||
primary_key,
|
|
||||||
allow_index_creation,
|
|
||||||
index_uid,
|
|
||||||
..
|
..
|
||||||
},
|
} => *content_uuid,
|
||||||
..
|
_ => panic!("unexpected task in the document addition batch"),
|
||||||
}) => {
|
}
|
||||||
let primary_key = primary_key.clone();
|
}
|
||||||
let method = *merge_strategy;
|
|
||||||
|
|
||||||
let index = if *allow_index_creation {
|
let content_uuids = tasks.iter().map(get_content_uuid).collect::<Vec<_>>();
|
||||||
self.get_or_create_index(index_uid.clone(), *id).await
|
|
||||||
} else {
|
|
||||||
self.get_index(index_uid.as_str().to_string()).await
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the index doesn't exist and we are not allowed to create it with the first
|
match tasks.first() {
|
||||||
// task, we must fails the whole batch.
|
Some(Task {
|
||||||
let now = OffsetDateTime::now_utc();
|
id,
|
||||||
let index = match index {
|
content:
|
||||||
Ok(index) => index,
|
TaskContent::DocumentAddition {
|
||||||
Err(e) => {
|
merge_strategy,
|
||||||
let error = ResponseError::from(e);
|
primary_key,
|
||||||
for task in tasks.iter_mut() {
|
allow_index_creation,
|
||||||
task.events.push(TaskEvent::Failed {
|
index_uid,
|
||||||
error: error.clone(),
|
..
|
||||||
timestamp: now,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_store = self.file_store.clone();
|
|
||||||
let result = spawn_blocking(move || {
|
|
||||||
index.update_documents(
|
|
||||||
method,
|
|
||||||
primary_key,
|
|
||||||
file_store,
|
|
||||||
content_uuids.into_iter(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let event = match result {
|
|
||||||
Ok(Ok(result)) => TaskEvent::Succeeded {
|
|
||||||
timestamp: OffsetDateTime::now_utc(),
|
|
||||||
result: TaskResult::DocumentAddition {
|
|
||||||
indexed_documents: result.indexed_documents,
|
|
||||||
},
|
},
|
||||||
},
|
..
|
||||||
Ok(Err(e)) => TaskEvent::Failed {
|
}) => {
|
||||||
timestamp: OffsetDateTime::now_utc(),
|
|
||||||
error: e.into(),
|
|
||||||
},
|
|
||||||
Err(e) => TaskEvent::Failed {
|
|
||||||
timestamp: OffsetDateTime::now_utc(),
|
|
||||||
error: IndexResolverError::from(e).into(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for task in tasks.iter_mut() {
|
|
||||||
task.events.push(event.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks
|
|
||||||
}
|
|
||||||
_ => panic!("invalid batch!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn process_task(&self, task: &Task) -> Result<TaskResult> {
|
|
||||||
match &task.content {
|
|
||||||
TaskContent::DocumentAddition { .. } => panic!("updates should be handled by batch"),
|
|
||||||
TaskContent::DocumentDeletion {
|
|
||||||
deletion: DocumentDeletion::Ids(ids),
|
|
||||||
index_uid,
|
|
||||||
} => {
|
|
||||||
let ids = ids.clone();
|
|
||||||
let index = self.get_index(index_uid.clone().into_inner()).await?;
|
|
||||||
|
|
||||||
let DocumentDeletionResult {
|
|
||||||
deleted_documents, ..
|
|
||||||
} = spawn_blocking(move || index.delete_documents(&ids)).await??;
|
|
||||||
|
|
||||||
Ok(TaskResult::DocumentDeletion { deleted_documents })
|
|
||||||
}
|
|
||||||
TaskContent::DocumentDeletion {
|
|
||||||
deletion: DocumentDeletion::Clear,
|
|
||||||
index_uid,
|
|
||||||
} => {
|
|
||||||
let index = self.get_index(index_uid.clone().into_inner()).await?;
|
|
||||||
let deleted_documents = spawn_blocking(move || -> IndexResult<u64> {
|
|
||||||
let number_documents = index.stats()?.number_of_documents;
|
|
||||||
index.clear_documents()?;
|
|
||||||
Ok(number_documents)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(TaskResult::ClearAll { deleted_documents })
|
|
||||||
}
|
|
||||||
TaskContent::SettingsUpdate {
|
|
||||||
settings,
|
|
||||||
is_deletion,
|
|
||||||
allow_index_creation,
|
|
||||||
index_uid,
|
|
||||||
} => {
|
|
||||||
let index = if *is_deletion || !*allow_index_creation {
|
|
||||||
self.get_index(index_uid.clone().into_inner()).await?
|
|
||||||
} else {
|
|
||||||
self.get_or_create_index(index_uid.clone(), task.id).await?
|
|
||||||
};
|
|
||||||
|
|
||||||
let settings = settings.clone();
|
|
||||||
spawn_blocking(move || index.update_settings(&settings.check())).await??;
|
|
||||||
|
|
||||||
Ok(TaskResult::Other)
|
|
||||||
}
|
|
||||||
TaskContent::IndexDeletion { index_uid } => {
|
|
||||||
let index = self.delete_index(index_uid.clone().into_inner()).await?;
|
|
||||||
|
|
||||||
let deleted_documents = spawn_blocking(move || -> IndexResult<u64> {
|
|
||||||
Ok(index.stats()?.number_of_documents)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(TaskResult::ClearAll { deleted_documents })
|
|
||||||
}
|
|
||||||
TaskContent::IndexCreation {
|
|
||||||
primary_key,
|
|
||||||
index_uid,
|
|
||||||
} => {
|
|
||||||
let index = self.create_index(index_uid.clone(), task.id).await?;
|
|
||||||
|
|
||||||
if let Some(primary_key) = primary_key {
|
|
||||||
let primary_key = primary_key.clone();
|
let primary_key = primary_key.clone();
|
||||||
spawn_blocking(move || index.update_primary_key(primary_key)).await??;
|
let method = *merge_strategy;
|
||||||
}
|
|
||||||
|
|
||||||
Ok(TaskResult::Other)
|
let index = if *allow_index_creation {
|
||||||
}
|
self.get_or_create_index(index_uid.clone(), *id).await
|
||||||
TaskContent::IndexUpdate {
|
} else {
|
||||||
primary_key,
|
self.get_index(index_uid.as_str().to_string()).await
|
||||||
index_uid,
|
};
|
||||||
} => {
|
|
||||||
let index = self.get_index(index_uid.clone().into_inner()).await?;
|
|
||||||
|
|
||||||
if let Some(primary_key) = primary_key {
|
// If the index doesn't exist and we are not allowed to create it with the first
|
||||||
let primary_key = primary_key.clone();
|
// task, we must fails the whole batch.
|
||||||
spawn_blocking(move || index.update_primary_key(primary_key)).await??;
|
let now = OffsetDateTime::now_utc();
|
||||||
}
|
let index = match index {
|
||||||
|
Ok(index) => index,
|
||||||
Ok(TaskResult::Other)
|
Err(e) => {
|
||||||
}
|
let error = ResponseError::from(e);
|
||||||
_ => unreachable!("Invalid task for index resolver"),
|
for task in tasks.iter_mut() {
|
||||||
}
|
task.events.push(TaskEvent::Failed {
|
||||||
}
|
error: error.clone(),
|
||||||
|
timestamp: now,
|
||||||
pub async fn dump(&self, path: impl AsRef<Path>) -> Result<()> {
|
});
|
||||||
for (_, index) in self.list().await? {
|
|
||||||
index.dump(&path)?;
|
|
||||||
}
|
|
||||||
self.index_uuid_store.dump(path.as_ref().to_owned()).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_index(&self, uid: IndexUid, creation_task_id: TaskId) -> Result<Index> {
|
|
||||||
match self.index_uuid_store.get(uid.into_inner()).await? {
|
|
||||||
(uid, Some(_)) => Err(IndexResolverError::IndexAlreadyExists(uid)),
|
|
||||||
(uid, None) => {
|
|
||||||
let uuid = Uuid::new_v4();
|
|
||||||
let index = self.index_store.create(uuid).await?;
|
|
||||||
match self
|
|
||||||
.index_uuid_store
|
|
||||||
.insert(
|
|
||||||
uid,
|
|
||||||
IndexMeta {
|
|
||||||
uuid,
|
|
||||||
creation_task_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Err(e) => {
|
|
||||||
match self.index_store.delete(uuid).await {
|
|
||||||
Ok(Some(index)) => {
|
|
||||||
index.close();
|
|
||||||
}
|
}
|
||||||
Ok(None) => (),
|
return tasks;
|
||||||
Err(e) => log::error!("Error while deleting index: {:?}", e),
|
|
||||||
}
|
}
|
||||||
Err(e)
|
};
|
||||||
|
|
||||||
|
let file_store = self.file_store.clone();
|
||||||
|
let result = spawn_blocking(move || {
|
||||||
|
index.update_documents(
|
||||||
|
method,
|
||||||
|
primary_key,
|
||||||
|
file_store,
|
||||||
|
content_uuids.into_iter(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let event = match result {
|
||||||
|
Ok(Ok(result)) => TaskEvent::Succeeded {
|
||||||
|
timestamp: OffsetDateTime::now_utc(),
|
||||||
|
result: TaskResult::DocumentAddition {
|
||||||
|
indexed_documents: result.indexed_documents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ok(Err(e)) => TaskEvent::Failed {
|
||||||
|
timestamp: OffsetDateTime::now_utc(),
|
||||||
|
error: e.into(),
|
||||||
|
},
|
||||||
|
Err(e) => TaskEvent::Failed {
|
||||||
|
timestamp: OffsetDateTime::now_utc(),
|
||||||
|
error: IndexResolverError::from(e).into(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for task in tasks.iter_mut() {
|
||||||
|
task.events.push(event.clone());
|
||||||
}
|
}
|
||||||
Ok(()) => Ok(index),
|
|
||||||
|
tasks
|
||||||
}
|
}
|
||||||
|
_ => panic!("invalid batch!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Get or create an index with name `uid`.
|
pub async fn delete_content_file(&self, content_uuid: Uuid) -> Result<()> {
|
||||||
pub async fn get_or_create_index(&self, uid: IndexUid, task_id: TaskId) -> Result<Index> {
|
self.file_store.delete(content_uuid).await?;
|
||||||
match self.create_index(uid, task_id).await {
|
Ok(())
|
||||||
Ok(index) => Ok(index),
|
|
||||||
Err(IndexResolverError::IndexAlreadyExists(uid)) => self.get_index(uid).await,
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list(&self) -> Result<Vec<(String, Index)>> {
|
pub async fn process_task(&self, task: &Task) -> Result<TaskResult> {
|
||||||
let uuids = self.index_uuid_store.list().await?;
|
match &task.content {
|
||||||
let mut indexes = Vec::new();
|
TaskContent::DocumentAddition { .. } => {
|
||||||
for (name, IndexMeta { uuid, .. }) in uuids {
|
panic!("updates should be handled by batch")
|
||||||
match self.index_store.get(uuid).await? {
|
}
|
||||||
Some(index) => indexes.push((name, index)),
|
TaskContent::DocumentDeletion {
|
||||||
None => {
|
deletion: DocumentDeletion::Ids(ids),
|
||||||
// we found an unexisting index, we remove it from the uuid store
|
index_uid,
|
||||||
let _ = self.index_uuid_store.delete(name).await;
|
} => {
|
||||||
|
let ids = ids.clone();
|
||||||
|
let index = self.get_index(index_uid.clone().into_inner()).await?;
|
||||||
|
|
||||||
|
let DocumentDeletionResult {
|
||||||
|
deleted_documents, ..
|
||||||
|
} = spawn_blocking(move || index.delete_documents(&ids)).await??;
|
||||||
|
|
||||||
|
Ok(TaskResult::DocumentDeletion { deleted_documents })
|
||||||
|
}
|
||||||
|
TaskContent::DocumentDeletion {
|
||||||
|
deletion: DocumentDeletion::Clear,
|
||||||
|
index_uid,
|
||||||
|
} => {
|
||||||
|
let index = self.get_index(index_uid.clone().into_inner()).await?;
|
||||||
|
let deleted_documents = spawn_blocking(move || -> IndexResult<u64> {
|
||||||
|
let number_documents = index.stats()?.number_of_documents;
|
||||||
|
index.clear_documents()?;
|
||||||
|
Ok(number_documents)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(TaskResult::ClearAll { deleted_documents })
|
||||||
|
}
|
||||||
|
TaskContent::SettingsUpdate {
|
||||||
|
settings,
|
||||||
|
is_deletion,
|
||||||
|
allow_index_creation,
|
||||||
|
index_uid,
|
||||||
|
} => {
|
||||||
|
let index = if *is_deletion || !*allow_index_creation {
|
||||||
|
self.get_index(index_uid.clone().into_inner()).await?
|
||||||
|
} else {
|
||||||
|
self.get_or_create_index(index_uid.clone(), task.id).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = settings.clone();
|
||||||
|
spawn_blocking(move || index.update_settings(&settings.check())).await??;
|
||||||
|
|
||||||
|
Ok(TaskResult::Other)
|
||||||
|
}
|
||||||
|
TaskContent::IndexDeletion { index_uid } => {
|
||||||
|
let index = self.delete_index(index_uid.clone().into_inner()).await?;
|
||||||
|
|
||||||
|
let deleted_documents = spawn_blocking(move || -> IndexResult<u64> {
|
||||||
|
Ok(index.stats()?.number_of_documents)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(TaskResult::ClearAll { deleted_documents })
|
||||||
|
}
|
||||||
|
TaskContent::IndexCreation {
|
||||||
|
primary_key,
|
||||||
|
index_uid,
|
||||||
|
} => {
|
||||||
|
let index = self.create_index(index_uid.clone(), task.id).await?;
|
||||||
|
|
||||||
|
if let Some(primary_key) = primary_key {
|
||||||
|
let primary_key = primary_key.clone();
|
||||||
|
spawn_blocking(move || index.update_primary_key(primary_key)).await??;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(TaskResult::Other)
|
||||||
|
}
|
||||||
|
TaskContent::IndexUpdate {
|
||||||
|
primary_key,
|
||||||
|
index_uid,
|
||||||
|
} => {
|
||||||
|
let index = self.get_index(index_uid.clone().into_inner()).await?;
|
||||||
|
|
||||||
|
if let Some(primary_key) = primary_key {
|
||||||
|
let primary_key = primary_key.clone();
|
||||||
|
spawn_blocking(move || index.update_primary_key(primary_key)).await??;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(TaskResult::Other)
|
||||||
|
}
|
||||||
|
_ => unreachable!("Invalid task for index resolver"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn dump(&self, path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
for (_, index) in self.list().await? {
|
||||||
|
index.dump(&path)?;
|
||||||
|
}
|
||||||
|
self.index_uuid_store.dump(path.as_ref().to_owned()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_index(&self, uid: IndexUid, creation_task_id: TaskId) -> Result<Index> {
|
||||||
|
match self.index_uuid_store.get(uid.into_inner()).await? {
|
||||||
|
(uid, Some(_)) => Err(IndexResolverError::IndexAlreadyExists(uid)),
|
||||||
|
(uid, None) => {
|
||||||
|
let uuid = Uuid::new_v4();
|
||||||
|
let index = self.index_store.create(uuid).await?;
|
||||||
|
match self
|
||||||
|
.index_uuid_store
|
||||||
|
.insert(
|
||||||
|
uid,
|
||||||
|
IndexMeta {
|
||||||
|
uuid,
|
||||||
|
creation_task_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(e) => {
|
||||||
|
match self.index_store.delete(uuid).await {
|
||||||
|
Ok(Some(index)) => {
|
||||||
|
index.close();
|
||||||
|
}
|
||||||
|
Ok(None) => (),
|
||||||
|
Err(e) => log::error!("Error while deleting index: {:?}", e),
|
||||||
|
}
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
Ok(()) => Ok(index),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(indexes)
|
/// Get or create an index with name `uid`.
|
||||||
}
|
pub async fn get_or_create_index(&self, uid: IndexUid, task_id: TaskId) -> Result<Index> {
|
||||||
|
match self.create_index(uid, task_id).await {
|
||||||
pub async fn delete_index(&self, uid: String) -> Result<Index> {
|
Ok(index) => Ok(index),
|
||||||
match self.index_uuid_store.delete(uid.clone()).await? {
|
Err(IndexResolverError::IndexAlreadyExists(uid)) => self.get_index(uid).await,
|
||||||
Some(IndexMeta { uuid, .. }) => match self.index_store.delete(uuid).await? {
|
Err(e) => Err(e),
|
||||||
Some(index) => {
|
}
|
||||||
index.clone().close();
|
|
||||||
Ok(index)
|
|
||||||
}
|
|
||||||
None => Err(IndexResolverError::UnexistingIndex(uid)),
|
|
||||||
},
|
|
||||||
None => Err(IndexResolverError::UnexistingIndex(uid)),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_index(&self, uid: String) -> Result<Index> {
|
pub async fn list(&self) -> Result<Vec<(String, Index)>> {
|
||||||
match self.index_uuid_store.get(uid).await? {
|
let uuids = self.index_uuid_store.list().await?;
|
||||||
(name, Some(IndexMeta { uuid, .. })) => {
|
let mut indexes = Vec::new();
|
||||||
|
for (name, IndexMeta { uuid, .. }) in uuids {
|
||||||
match self.index_store.get(uuid).await? {
|
match self.index_store.get(uuid).await? {
|
||||||
Some(index) => Ok(index),
|
Some(index) => indexes.push((name, index)),
|
||||||
None => {
|
None => {
|
||||||
// For some reason we got a uuid to an unexisting index, we return an error,
|
// we found an unexisting index, we remove it from the uuid store
|
||||||
// and remove the uuid from the uuid store.
|
let _ = self.index_uuid_store.delete(name).await;
|
||||||
let _ = self.index_uuid_store.delete(name.clone()).await;
|
|
||||||
Err(IndexResolverError::UnexistingIndex(name))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(name, _) => Err(IndexResolverError::UnexistingIndex(name)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_index_creation_task_id(&self, index_uid: String) -> Result<TaskId> {
|
Ok(indexes)
|
||||||
let (uid, meta) = self.index_uuid_store.get(index_uid).await?;
|
}
|
||||||
meta.map(
|
|
||||||
|IndexMeta {
|
pub async fn delete_index(&self, uid: String) -> Result<Index> {
|
||||||
creation_task_id, ..
|
match self.index_uuid_store.delete(uid.clone()).await? {
|
||||||
}| creation_task_id,
|
Some(IndexMeta { uuid, .. }) => match self.index_store.delete(uuid).await? {
|
||||||
)
|
Some(index) => {
|
||||||
.ok_or(IndexResolverError::UnexistingIndex(uid))
|
index.clone().close();
|
||||||
|
Ok(index)
|
||||||
|
}
|
||||||
|
None => Err(IndexResolverError::UnexistingIndex(uid)),
|
||||||
|
},
|
||||||
|
None => Err(IndexResolverError::UnexistingIndex(uid)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_index(&self, uid: String) -> Result<Index> {
|
||||||
|
match self.index_uuid_store.get(uid).await? {
|
||||||
|
(name, Some(IndexMeta { uuid, .. })) => {
|
||||||
|
match self.index_store.get(uuid).await? {
|
||||||
|
Some(index) => Ok(index),
|
||||||
|
None => {
|
||||||
|
// For some reason we got a uuid to an unexisting index, we return an error,
|
||||||
|
// and remove the uuid from the uuid store.
|
||||||
|
let _ = self.index_uuid_store.delete(name.clone()).await;
|
||||||
|
Err(IndexResolverError::UnexistingIndex(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(name, _) => Err(IndexResolverError::UnexistingIndex(name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_index_creation_task_id(&self, index_uid: String) -> Result<TaskId> {
|
||||||
|
let (uid, meta) = self.index_uuid_store.get(index_uid).await?;
|
||||||
|
meta.map(
|
||||||
|
|IndexMeta {
|
||||||
|
creation_task_id, ..
|
||||||
|
}| creation_task_id,
|
||||||
|
)
|
||||||
|
.ok_or(IndexResolverError::UnexistingIndex(uid))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
// use std::{collections::BTreeMap, vec::IntoIter};
|
use super::*;
|
||||||
//
|
|
||||||
// use super::*;
|
use nelson::Mocker;
|
||||||
//
|
|
||||||
// use futures::future::ok;
|
pub enum MockIndexResolver<U, I> {
|
||||||
// use milli::update::{DocumentAdditionResult, IndexDocumentsMethod};
|
Real(super::real::IndexResolver<U, I>),
|
||||||
// use nelson::Mocker;
|
Mock(Mocker),
|
||||||
// use proptest::prelude::*;
|
}
|
||||||
//
|
|
||||||
// use crate::{
|
impl MockIndexResolver<HeedMetaStore, MapIndexStore> {
|
||||||
// index::{
|
pub fn load_dump(
|
||||||
// error::{IndexError, Result as IndexResult},
|
src: impl AsRef<Path>,
|
||||||
// Checked, IndexMeta, IndexStats, Settings,
|
dst: impl AsRef<Path>,
|
||||||
// },
|
index_db_size: usize,
|
||||||
// tasks::{batch::Batch, BatchHandler},
|
env: Arc<Env>,
|
||||||
// };
|
indexer_opts: &IndexerOpts,
|
||||||
// use index_store::MockIndexStore;
|
) -> anyhow::Result<()> {
|
||||||
// use meta_store::MockIndexMetaStore;
|
super::real::IndexResolver::load_dump(src, dst, index_db_size, env, indexer_opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, I> MockIndexResolver<U, I>
|
||||||
|
where
|
||||||
|
U: IndexMetaStore,
|
||||||
|
I: IndexStore,
|
||||||
|
{
|
||||||
|
pub fn new(index_uuid_store: U, index_store: I, file_store: UpdateFileStore) -> Self {
|
||||||
|
Self::Real(super::real::IndexResolver {
|
||||||
|
index_uuid_store,
|
||||||
|
index_store,
|
||||||
|
file_store,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mock(mocker: Mocker) -> Self {
|
||||||
|
Self::Mock(mocker)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_document_addition_batch(&self, tasks: Vec<Task>) -> Vec<Task> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.process_document_addition_batch(tasks).await,
|
||||||
|
IndexResolver::Mock(m) => unsafe {
|
||||||
|
m.get("process_document_addition_batch").call(tasks)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_task(&self, task: &Task) -> Result<TaskResult> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.process_task(task).await,
|
||||||
|
IndexResolver::Mock(m) => unsafe { m.get("process_task").call(task) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn dump(&self, path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.dump(path).await,
|
||||||
|
IndexResolver::Mock(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get or create an index with name `uid`.
|
||||||
|
pub async fn get_or_create_index(&self, uid: IndexUid, task_id: TaskId) -> Result<Index> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.get_or_create_index(uid, task_id).await,
|
||||||
|
IndexResolver::Mock(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list(&self) -> Result<Vec<(String, Index)>> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.list().await,
|
||||||
|
IndexResolver::Mock(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_index(&self, uid: String) -> Result<Index> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.delete_index(uid).await,
|
||||||
|
IndexResolver::Mock(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_index(&self, uid: String) -> Result<Index> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.get_index(uid).await,
|
||||||
|
IndexResolver::Mock(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_index_creation_task_id(&self, index_uid: String) -> Result<TaskId> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.get_index_creation_task_id(index_uid).await,
|
||||||
|
IndexResolver::Mock(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_content_file(&self, content_uuid: Uuid) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
IndexResolver::Real(r) => r.delete_content_file(content_uuid).await,
|
||||||
|
IndexResolver::Mock(m) => unsafe {
|
||||||
|
m.get("delete_content_file").call(content_uuid)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: ignoring this test, it has become too complex to maintain, and rather implement
|
// TODO: ignoring this test, it has become too complex to maintain, and rather implement
|
||||||
// handler logic test.
|
// handler logic test.
|
||||||
|
@ -38,7 +38,7 @@ where
|
|||||||
if let BatchContent::DocumentsAdditionBatch(ref tasks) = batch.content {
|
if let BatchContent::DocumentsAdditionBatch(ref tasks) = batch.content {
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
if let Some(content_uuid) = task.get_content_uuid() {
|
if let Some(content_uuid) = task.get_content_uuid() {
|
||||||
if let Err(e) = self.file_store.delete(content_uuid).await {
|
if let Err(e) = self.delete_content_file(content_uuid).await {
|
||||||
log::error!("error deleting update file: {}", e);
|
log::error!("error deleting update file: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,7 +49,12 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::index_resolver::{index_store::MockIndexStore, meta_store::MockIndexMetaStore};
|
use crate::index_resolver::index_store::MapIndexStore;
|
||||||
|
use crate::index_resolver::meta_store::HeedMetaStore;
|
||||||
|
use crate::index_resolver::{
|
||||||
|
error::Result as IndexResult, index_store::MockIndexStore, meta_store::MockIndexMetaStore,
|
||||||
|
};
|
||||||
|
use crate::tasks::task::TaskResult;
|
||||||
use crate::tasks::{
|
use crate::tasks::{
|
||||||
handlers::test::task_to_batch,
|
handlers::test::task_to_batch,
|
||||||
task::{Task, TaskContent},
|
task::{Task, TaskContent},
|
||||||
@ -142,5 +147,58 @@ mod test {
|
|||||||
index_resolver.process_batch(batch).await;
|
index_resolver.process_batch(batch).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test perform_batch. We need a Mocker for IndexResolver.
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn index_document_task_deletes_update_file(
|
||||||
|
task in any::<Task>(),
|
||||||
|
) {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
let handle = rt.spawn(async {
|
||||||
|
let mocker = Mocker::default();
|
||||||
|
|
||||||
|
if let TaskContent::DocumentAddition{ .. } = task.content {
|
||||||
|
mocker.when::<Uuid, IndexResult<()>>("delete_content_file").then(|_| Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let index_resolver: IndexResolver<HeedMetaStore, MapIndexStore> = IndexResolver::mock(mocker);
|
||||||
|
|
||||||
|
let batch = task_to_batch(task);
|
||||||
|
|
||||||
|
index_resolver.finish(&batch).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
rt.block_on(handle).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handle_batch(task in any::<Task>()) {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
let handle = rt.spawn(async {
|
||||||
|
let mocker = Mocker::default();
|
||||||
|
match task.content {
|
||||||
|
TaskContent::DocumentAddition { .. } => {
|
||||||
|
mocker.when::<Vec<Task>, Vec<Task>>("process_document_addition_batch").then(|tasks| tasks);
|
||||||
|
}
|
||||||
|
TaskContent::Dump { .. } => (),
|
||||||
|
_ => {
|
||||||
|
mocker.when::<&Task, IndexResult<TaskResult>>("process_task").then(|_| Ok(TaskResult::Other));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let index_resolver: IndexResolver<HeedMetaStore, MapIndexStore> = IndexResolver::mock(mocker);
|
||||||
|
|
||||||
|
|
||||||
|
let batch = task_to_batch(task);
|
||||||
|
|
||||||
|
if index_resolver.accept(&batch) {
|
||||||
|
index_resolver.process_batch(batch).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(e) = rt.block_on(handle) {
|
||||||
|
if e.is_panic() {
|
||||||
|
std::panic::resume_unwind(e.into_panic());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user