2021-01-28 21:12:34 +08:00
|
|
|
mod local_index_controller;
|
2021-02-02 02:51:47 +08:00
|
|
|
mod updates;
|
2021-01-14 00:50:36 +08:00
|
|
|
|
2021-01-28 21:12:34 +08:00
|
|
|
pub use local_index_controller::LocalIndexController;
|
2021-01-14 00:50:36 +08:00
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
use std::collections::HashMap;
|
2021-01-28 21:12:34 +08:00
|
|
|
use std::num::NonZeroUsize;
|
|
|
|
use std::sync::Arc;
|
2021-01-14 00:50:36 +08:00
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
use anyhow::Result;
|
2021-02-04 00:44:20 +08:00
|
|
|
use chrono::{DateTime, Utc};
|
2021-01-28 21:12:34 +08:00
|
|
|
use milli::Index;
|
2021-02-09 23:08:13 +08:00
|
|
|
use milli::update::{IndexDocumentsMethod, UpdateFormat, DocumentAdditionResult};
|
|
|
|
use serde::{Serialize, Deserialize, de::Deserializer};
|
|
|
|
use uuid::Uuid;
|
2021-01-16 22:09:48 +08:00
|
|
|
|
2021-02-02 02:51:47 +08:00
|
|
|
pub use updates::{Processed, Processing, Failed};
|
|
|
|
|
|
|
|
pub type UpdateStatus = updates::UpdateStatus<UpdateMeta, UpdateResult, String>;
|
2021-01-28 21:12:34 +08:00
|
|
|
|
2021-02-04 00:44:20 +08:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct IndexMetadata {
|
|
|
|
pub name: String,
|
|
|
|
uuid: Uuid,
|
|
|
|
created_at: DateTime<Utc>,
|
|
|
|
updated_at: DateTime<Utc>,
|
2021-02-04 22:28:52 +08:00
|
|
|
primary_key: Option<String>,
|
2021-02-04 00:44:20 +08:00
|
|
|
}
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
pub enum UpdateMeta {
|
|
|
|
DocumentsAddition { method: IndexDocumentsMethod, format: UpdateFormat },
|
|
|
|
ClearDocuments,
|
|
|
|
Settings(Settings),
|
|
|
|
Facets(Facets),
|
2021-01-14 00:50:36 +08:00
|
|
|
}
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct Facets {
|
|
|
|
pub level_group_size: Option<NonZeroUsize>,
|
|
|
|
pub min_level_size: Option<NonZeroUsize>,
|
2021-01-14 00:50:36 +08:00
|
|
|
}
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
fn deserialize_some<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
|
|
|
|
where T: Deserialize<'de>,
|
|
|
|
D: Deserializer<'de>
|
|
|
|
{
|
|
|
|
Deserialize::deserialize(deserializer).map(Some)
|
2021-01-14 00:50:36 +08:00
|
|
|
}
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct Settings {
|
|
|
|
#[serde(
|
|
|
|
default,
|
|
|
|
deserialize_with = "deserialize_some",
|
|
|
|
skip_serializing_if = "Option::is_none",
|
|
|
|
)]
|
|
|
|
pub displayed_attributes: Option<Option<Vec<String>>>,
|
|
|
|
|
|
|
|
#[serde(
|
|
|
|
default,
|
|
|
|
deserialize_with = "deserialize_some",
|
|
|
|
skip_serializing_if = "Option::is_none",
|
|
|
|
)]
|
|
|
|
pub searchable_attributes: Option<Option<Vec<String>>>,
|
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub faceted_attributes: Option<Option<HashMap<String, String>>>,
|
|
|
|
|
|
|
|
#[serde(
|
|
|
|
default,
|
|
|
|
deserialize_with = "deserialize_some",
|
|
|
|
skip_serializing_if = "Option::is_none",
|
|
|
|
)]
|
|
|
|
pub criteria: Option<Option<Vec<String>>>,
|
2021-01-14 00:50:36 +08:00
|
|
|
}
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
impl Settings {
|
|
|
|
pub fn cleared() -> Self {
|
|
|
|
Self {
|
|
|
|
displayed_attributes: Some(None),
|
|
|
|
searchable_attributes: Some(None),
|
|
|
|
faceted_attributes: Some(None),
|
|
|
|
criteria: Some(None),
|
2021-01-14 00:50:36 +08:00
|
|
|
}
|
|
|
|
}
|
2021-01-16 22:09:48 +08:00
|
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub enum UpdateResult {
|
2021-01-28 21:12:34 +08:00
|
|
|
DocumentsAddition(DocumentAdditionResult),
|
2021-01-16 22:09:48 +08:00
|
|
|
Other,
|
|
|
|
}
|
2021-01-14 00:50:36 +08:00
|
|
|
|
2021-02-09 23:08:13 +08:00
|
|
|
#[derive(Clone, Debug)]
|
2021-02-09 18:41:26 +08:00
|
|
|
pub struct IndexSettings {
|
|
|
|
pub name: Option<String>,
|
|
|
|
pub primary_key: Option<String>,
|
|
|
|
}
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
/// The `IndexController` is in charge of the access to the underlying indices. It splits the logic
|
2021-02-02 02:51:47 +08:00
|
|
|
/// for read access which is provided thanks to an handle to the index, and write access which must
|
|
|
|
/// be provided. This allows the implementer to define the behaviour of write accesses to the
|
|
|
|
/// indices, and abstract the scheduling of the updates. The implementer must be able to provide an
|
|
|
|
/// instance of `IndexStore`
|
2021-01-28 21:12:34 +08:00
|
|
|
pub trait IndexController {
|
2021-01-16 22:09:48 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Write operations
|
|
|
|
*
|
|
|
|
* Logic for the write operation need to be provided by the implementer, since they can be made
|
|
|
|
* asynchronous thanks to an update_store for example.
|
|
|
|
*
|
|
|
|
* */
|
|
|
|
|
|
|
|
/// Perform document addition on the database. If the provided index does not exist, it will be
|
|
|
|
/// created when the addition is applied to the index.
|
|
|
|
fn add_documents<S: AsRef<str>>(
|
|
|
|
&self,
|
|
|
|
index: S,
|
|
|
|
method: IndexDocumentsMethod,
|
|
|
|
format: UpdateFormat,
|
|
|
|
data: &[u8],
|
2021-02-02 02:51:47 +08:00
|
|
|
) -> anyhow::Result<UpdateStatus>;
|
2021-01-16 22:09:48 +08:00
|
|
|
|
2021-02-11 19:03:00 +08:00
|
|
|
/// Clear all documents in the given index.
|
|
|
|
fn clear_documents(&self, index: impl AsRef<str>) -> anyhow::Result<UpdateStatus>;
|
|
|
|
|
2021-01-16 22:09:48 +08:00
|
|
|
/// Updates an index settings. If the index does not exist, it will be created when the update
|
|
|
|
/// is applied to the index.
|
2021-02-02 02:51:47 +08:00
|
|
|
fn update_settings<S: AsRef<str>>(&self, index_uid: S, settings: Settings) -> anyhow::Result<UpdateStatus>;
|
2021-01-16 22:09:48 +08:00
|
|
|
|
|
|
|
/// Create an index with the given `index_uid`.
|
2021-02-09 18:41:26 +08:00
|
|
|
fn create_index(&self, index_settings: IndexSettings) -> Result<IndexMetadata>;
|
2021-01-16 22:09:48 +08:00
|
|
|
|
|
|
|
/// Delete index with the given `index_uid`, attempting to close it beforehand.
|
|
|
|
fn delete_index<S: AsRef<str>>(&self, index_uid: S) -> Result<()>;
|
|
|
|
|
|
|
|
/// Swap two indexes, concretely, it simply swaps the index the names point to.
|
|
|
|
fn swap_indices<S1: AsRef<str>, S2: AsRef<str>>(&self, index1_uid: S1, index2_uid: S2) -> Result<()>;
|
|
|
|
|
|
|
|
/// Apply an update to the given index. This method can be called when an update is ready to be
|
|
|
|
/// processed
|
|
|
|
fn handle_update<S: AsRef<str>>(
|
|
|
|
&self,
|
|
|
|
_index: S,
|
|
|
|
_update_id: u64,
|
|
|
|
_meta: Processing<UpdateMeta>,
|
|
|
|
_content: &[u8]
|
|
|
|
) -> Result<Processed<UpdateMeta, UpdateResult>, Failed<UpdateMeta, String>> {
|
2021-01-14 00:50:36 +08:00
|
|
|
todo!()
|
|
|
|
}
|
2021-01-16 22:09:48 +08:00
|
|
|
|
2021-02-02 02:51:47 +08:00
|
|
|
/// Returns, if it exists, the `Index` with the povided name.
|
|
|
|
fn index(&self, name: impl AsRef<str>) -> anyhow::Result<Option<Arc<Index>>>;
|
2021-01-29 00:20:51 +08:00
|
|
|
|
2021-02-09 18:26:52 +08:00
|
|
|
/// Returns the udpate status an update
|
2021-02-02 02:51:47 +08:00
|
|
|
fn update_status(&self, index: impl AsRef<str>, id: u64) -> anyhow::Result<Option<UpdateStatus>>;
|
2021-02-09 18:26:52 +08:00
|
|
|
|
|
|
|
/// Returns all the udpate status for an index
|
2021-02-02 02:51:47 +08:00
|
|
|
fn all_update_status(&self, index: impl AsRef<str>) -> anyhow::Result<Vec<UpdateStatus>>;
|
2021-02-04 00:44:20 +08:00
|
|
|
|
2021-02-09 18:26:52 +08:00
|
|
|
/// List all the indexes
|
2021-02-04 00:44:20 +08:00
|
|
|
fn list_indexes(&self) -> anyhow::Result<Vec<IndexMetadata>>;
|
2021-02-09 18:26:52 +08:00
|
|
|
|
|
|
|
fn update_index(&self, name: impl AsRef<str>, index_settings: IndexSettings) -> anyhow::Result<IndexMetadata>;
|
2021-01-28 21:12:34 +08:00
|
|
|
}
|
2021-02-04 03:20:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
#[macro_use]
|
|
|
|
pub(crate) mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! make_index_controller_tests {
|
|
|
|
($controller_buider:block) => {
|
|
|
|
#[test]
|
|
|
|
fn test_create_and_list_indexes() {
|
|
|
|
crate::index_controller::test::create_and_list_indexes($controller_buider);
|
|
|
|
}
|
2021-02-09 18:41:26 +08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_create_index_with_no_name_is_error() {
|
|
|
|
crate::index_controller::test::create_index_with_no_name_is_error($controller_buider);
|
|
|
|
}
|
2021-02-09 23:08:13 +08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_index() {
|
|
|
|
crate::index_controller::test::update_index($controller_buider);
|
|
|
|
}
|
2021-02-04 03:20:51 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-02-09 23:08:13 +08:00
|
|
|
pub(crate) fn create_and_list_indexes(controller: impl IndexController) {
|
2021-02-09 18:41:26 +08:00
|
|
|
let settings1 = IndexSettings {
|
|
|
|
name: Some(String::from("test_index")),
|
|
|
|
primary_key: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let settings2 = IndexSettings {
|
|
|
|
name: Some(String::from("test_index2")),
|
|
|
|
primary_key: Some(String::from("foo")),
|
|
|
|
};
|
|
|
|
|
|
|
|
controller.create_index(settings1).unwrap();
|
|
|
|
controller.create_index(settings2).unwrap();
|
2021-02-04 03:20:51 +08:00
|
|
|
|
|
|
|
let indexes = controller.list_indexes().unwrap();
|
|
|
|
assert_eq!(indexes.len(), 2);
|
|
|
|
assert_eq!(indexes[0].name, "test_index");
|
|
|
|
assert_eq!(indexes[1].name, "test_index2");
|
2021-02-09 18:41:26 +08:00
|
|
|
assert_eq!(indexes[1].primary_key.clone().unwrap(), "foo");
|
|
|
|
}
|
|
|
|
|
2021-02-09 23:08:13 +08:00
|
|
|
pub(crate) fn create_index_with_no_name_is_error(controller: impl IndexController) {
|
2021-02-09 18:41:26 +08:00
|
|
|
let settings = IndexSettings {
|
|
|
|
name: None,
|
|
|
|
primary_key: None,
|
|
|
|
};
|
|
|
|
assert!(controller.create_index(settings).is_err());
|
2021-02-04 03:20:51 +08:00
|
|
|
}
|
2021-02-09 23:08:13 +08:00
|
|
|
|
|
|
|
pub(crate) fn update_index(controller: impl IndexController) {
|
|
|
|
|
|
|
|
let settings = IndexSettings {
|
|
|
|
name: Some(String::from("test")),
|
|
|
|
primary_key: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert!(controller.create_index(settings).is_ok());
|
|
|
|
|
|
|
|
// perform empty update returns index meta unchanged
|
|
|
|
let settings = IndexSettings {
|
|
|
|
name: None,
|
|
|
|
primary_key: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = controller.update_index("test", settings).unwrap();
|
|
|
|
assert_eq!(result.name, "test");
|
|
|
|
assert_eq!(result.created_at, result.updated_at);
|
|
|
|
assert!(result.primary_key.is_none());
|
|
|
|
|
|
|
|
// Changing the name trigger an error
|
|
|
|
let settings = IndexSettings {
|
|
|
|
name: Some(String::from("bar")),
|
|
|
|
primary_key: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert!(controller.update_index("test", settings).is_err());
|
|
|
|
|
|
|
|
// Update primary key
|
|
|
|
let settings = IndexSettings {
|
|
|
|
name: None,
|
|
|
|
primary_key: Some(String::from("foo")),
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = controller.update_index("test", settings.clone()).unwrap();
|
|
|
|
assert_eq!(result.name, "test");
|
|
|
|
assert!(result.created_at < result.updated_at);
|
|
|
|
assert_eq!(result.primary_key.unwrap(), "foo");
|
|
|
|
|
|
|
|
// setting the primary key again is an error
|
|
|
|
assert!(controller.update_index("test", settings).is_err());
|
|
|
|
}
|
2021-02-04 03:20:51 +08:00
|
|
|
}
|