914: lazily create an index on documents push r=LegendreM a=qdequele

Create an index if it's possible when a user trying to send data to a non-existing index. https://github.com/meilisearch/MeiliSearch/issues/918

Co-authored-by: qdequele <quentin@meilisearch.com>
Co-authored-by: qdequele <quentin@dequelen.me>
This commit is contained in:
bors[bot] 2020-10-15 09:37:15 +00:00 committed by GitHub
commit 29b8810db8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 741 additions and 177 deletions

6
Cargo.lock generated
View File

@ -1914,7 +1914,8 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.1.3" version = "2.1.3"
source = "git+https://github.com/pest-parser/pest.git?rev=51fd1d49f1041f7839975664ef71fe15c7dcaf67#51fd1d49f1041f7839975664ef71fe15c7dcaf67" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [ dependencies = [
"ucd-trie", "ucd-trie",
] ]
@ -1922,8 +1923,7 @@ dependencies = [
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.1.3" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/pest-parser/pest.git?rev=51fd1d49f1041f7839975664ef71fe15c7dcaf67#51fd1d49f1041f7839975664ef71fe15c7dcaf67"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [ dependencies = [
"ucd-trie", "ucd-trie",
] ]

View File

@ -3,9 +3,10 @@ use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use meilisearch_core::{Database, DatabaseOptions}; use meilisearch_core::{Database, DatabaseOptions, Index};
use sha2::Digest; use sha2::Digest;
use crate::error::{Error as MSError, ResponseError};
use crate::index_update_callback; use crate::index_update_callback;
use crate::option::Opt; use crate::option::Opt;
@ -102,4 +103,60 @@ impl Data {
Ok(data) Ok(data)
} }
fn create_index(&self, uid: &str) -> Result<Index, ResponseError> {
if !uid
.chars()
.all(|x| x.is_ascii_alphanumeric() || x == '-' || x == '_')
{
return Err(MSError::InvalidIndexUid.into());
}
let created_index = self.db.create_index(&uid).map_err(|e| match e {
meilisearch_core::Error::IndexAlreadyExists => e.into(),
_ => ResponseError::from(MSError::create_index(e)),
})?;
self.db.main_write::<_, _, ResponseError>(|mut writer| {
created_index.main.put_name(&mut writer, uid)?;
created_index
.main
.created_at(&writer)?
.ok_or(MSError::internal("Impossible to read created at"))?;
created_index
.main
.updated_at(&writer)?
.ok_or(MSError::internal("Impossible to read updated at"))?;
Ok(())
})?;
Ok(created_index)
}
pub fn get_or_create_index<F, R>(&self, uid: &str, f: F) -> Result<R, ResponseError>
where
F: FnOnce(&Index) -> Result<R, ResponseError>,
{
let mut index_has_been_created = false;
let index = match self.db.open_index(&uid) {
Some(index) => index,
None => {
index_has_been_created = true;
self.create_index(&uid)?
}
};
match f(&index) {
Ok(r) => Ok(r),
Err(err) => {
if index_has_been_created {
let _ = self.db.delete_index(&uid);
}
Err(err)
}
}
}
} }

View File

@ -45,7 +45,8 @@ async fn get_document(
let reader = data.db.main_read_txn()?; let reader = data.db.main_read_txn()?;
let internal_id = index.main let internal_id = index
.main
.external_to_internal_docid(&reader, &path.document_id)? .external_to_internal_docid(&reader, &path.document_id)?
.ok_or(Error::document_not_found(&path.document_id))?; .ok_or(Error::document_not_found(&path.document_id))?;
@ -166,11 +167,7 @@ async fn update_multiple_documents(
body: web::Json<Vec<Document>>, body: web::Json<Vec<Document>>,
is_partial: bool, is_partial: bool,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let reader = data.db.main_read_txn()?; let reader = data.db.main_read_txn()?;
let mut schema = index let mut schema = index
@ -184,12 +181,10 @@ async fn update_multiple_documents(
None => body None => body
.first() .first()
.and_then(find_primary_key) .and_then(find_primary_key)
.ok_or(meilisearch_core::Error::MissingPrimaryKey)? .ok_or(meilisearch_core::Error::MissingPrimaryKey)?,
}; };
schema schema.set_primary_key(&id).map_err(Error::bad_request)?;
.set_primary_key(&id)
.map_err(Error::bad_request)?;
data.db.main_write(|w| index.main.put_schema(w, &schema))?; data.db.main_write(|w| index.main.put_schema(w, &schema))?;
} }
@ -204,9 +199,9 @@ async fn update_multiple_documents(
document_addition.update_document(document); document_addition.update_document(document);
} }
let update_id = data.db.update_write(|w| document_addition.finalize(w))?; Ok(data.db.update_write(|w| document_addition.finalize(w))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) return Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)));
} }
#[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")] #[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")]
@ -243,7 +238,6 @@ async fn delete_documents(
.open_index(&path.index_uid) .open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?; .ok_or(Error::index_not_found(&path.index_uid))?;
let mut documents_deletion = index.documents_deletion(); let mut documents_deletion = index.documents_deletion();
for document_id in body.into_inner() { for document_id in body.into_inner() {

View File

@ -53,13 +53,12 @@ async fn update_all(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<Settings>, body: web::Json<Settings>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let settings = body let update_id = data.get_or_create_index(&path.index_uid, |index| {
.into_inner() Ok(data.db.update_write::<_, _, ResponseError>(|writer| {
.to_update() let settings = body.into_inner().to_update().map_err(Error::bad_request)?;
.map_err(Error::bad_request)?; let update_id = index.settings_update(writer, settings)?;
Ok(update_id)
let update_id = data.db.update_write::<_, _, Error>(|writer| { })?)
update_all_settings_txn(&data, settings, &path.index_uid, writer)
})?; })?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
@ -71,11 +70,7 @@ pub fn get_all_sync(data: &web::Data<Data>, reader: &MainReader, index_uid: &str
.open_index(index_uid) .open_index(index_uid)
.ok_or(Error::index_not_found(index_uid))?; .ok_or(Error::index_not_found(index_uid))?;
let stop_words: BTreeSet<String> = index let stop_words: BTreeSet<String> = index.main.stop_words(&reader)?.into_iter().collect();
.main
.stop_words(reader)?
.into_iter()
.collect();
let synonyms_list = index.main.synonyms(reader)?; let synonyms_list = index.main.synonyms(reader)?;
@ -94,22 +89,19 @@ pub fn get_all_sync(data: &web::Data<Data>, reader: &MainReader, index_uid: &str
.map(|r| r.to_string()) .map(|r| r.to_string())
.collect(); .collect();
let schema = index.main.schema(&reader)?;
let schema = index.main.schema(reader)?;
let distinct_attribute = match (index.main.distinct_attribute(reader)?, &schema) { let distinct_attribute = match (index.main.distinct_attribute(reader)?, &schema) {
(Some(id), Some(schema)) => schema.name(id).map(str::to_string), (Some(id), Some(schema)) => schema.name(id).map(str::to_string),
_ => None, _ => None,
}; };
let attributes_for_faceting = match (&schema, &index.main.attributes_for_faceting(reader)?) { let attributes_for_faceting = match (&schema, &index.main.attributes_for_faceting(&reader)?) {
(Some(schema), Some(attrs)) => { (Some(schema), Some(attrs)) => attrs
attrs
.iter() .iter()
.filter_map(|&id| schema.name(id)) .filter_map(|&id| schema.name(id))
.map(str::to_string) .map(str::to_string)
.collect() .collect(),
}
_ => vec![], _ => vec![],
}; };
@ -159,7 +151,9 @@ async fn delete_all(
attributes_for_faceting: UpdateState::Clear, attributes_for_faceting: UpdateState::Clear,
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -198,18 +192,17 @@ async fn update_rules(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<Option<Vec<String>>>, body: web::Json<Option<Vec<String>>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = Settings { let settings = Settings {
ranking_rules: Some(body.into_inner()), ranking_rules: Some(body.into_inner()),
..Settings::default() ..Settings::default()
}; };
let settings = settings.to_update().map_err(Error::bad_request)?; let settings = settings.to_update().map_err(Error::bad_request)?;
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -232,7 +225,9 @@ async fn delete_rules(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -269,18 +264,17 @@ async fn update_distinct(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<Option<String>>, body: web::Json<Option<String>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = Settings { let settings = Settings {
distinct_attribute: Some(body.into_inner()), distinct_attribute: Some(body.into_inner()),
..Settings::default() ..Settings::default()
}; };
let settings = settings.to_update().map_err(Error::bad_request)?; let settings = settings.to_update().map_err(Error::bad_request)?;
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -303,7 +297,9 @@ async fn delete_distinct(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -322,8 +318,7 @@ async fn get_searchable(
.ok_or(Error::index_not_found(&path.index_uid))?; .ok_or(Error::index_not_found(&path.index_uid))?;
let reader = data.db.main_read_txn()?; let reader = data.db.main_read_txn()?;
let schema = index.main.schema(&reader)?; let schema = index.main.schema(&reader)?;
let searchable_attributes: Option<Vec<String>> = let searchable_attributes: Option<Vec<String>> = schema.as_ref().map(get_indexed_attributes);
schema.as_ref().map(get_indexed_attributes);
Ok(HttpResponse::Ok().json(searchable_attributes)) Ok(HttpResponse::Ok().json(searchable_attributes))
} }
@ -337,11 +332,7 @@ async fn update_searchable(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<Option<Vec<String>>>, body: web::Json<Option<Vec<String>>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = Settings { let settings = Settings {
searchable_attributes: Some(body.into_inner()), searchable_attributes: Some(body.into_inner()),
..Settings::default() ..Settings::default()
@ -349,7 +340,10 @@ async fn update_searchable(
let settings = settings.to_update().map_err(Error::bad_request)?; let settings = settings.to_update().map_err(Error::bad_request)?;
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -372,7 +366,9 @@ async fn delete_searchable(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -407,18 +403,17 @@ async fn update_displayed(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<Option<BTreeSet<String>>>, body: web::Json<Option<BTreeSet<String>>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = Settings { let settings = Settings {
displayed_attributes: Some(body.into_inner()), displayed_attributes: Some(body.into_inner()),
..Settings::default() ..Settings::default()
}; };
let settings = settings.to_update().map_err(Error::bad_request)?; let settings = settings.to_update().map_err(Error::bad_request)?;
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -441,7 +436,9 @@ async fn delete_displayed(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -459,20 +456,16 @@ async fn get_attributes_for_faceting(
.open_index(&path.index_uid) .open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?; .ok_or(Error::index_not_found(&path.index_uid))?;
let attributes_for_faceting = data let attributes_for_faceting = data.db.main_read::<_, _, ResponseError>(|reader| {
.db
.main_read::<_, _, ResponseError>(|reader| {
let schema = index.main.schema(reader)?; let schema = index.main.schema(reader)?;
let attrs = index.main.attributes_for_faceting(reader)?; let attrs = index.main.attributes_for_faceting(reader)?;
let attr_names = match (&schema, &attrs) { let attr_names = match (&schema, &attrs) {
(Some(schema), Some(attrs)) => { (Some(schema), Some(attrs)) => attrs
attrs
.iter() .iter()
.filter_map(|&id| schema.name(id)) .filter_map(|&id| schema.name(id))
.map(str::to_string) .map(str::to_string)
.collect() .collect(),
} _ => vec![],
_ => vec![]
}; };
Ok(attr_names) Ok(attr_names)
})?; })?;
@ -489,18 +482,17 @@ async fn update_attributes_for_faceting(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<Option<Vec<String>>>, body: web::Json<Option<Vec<String>>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = Settings { let settings = Settings {
attributes_for_faceting: Some(body.into_inner()), attributes_for_faceting: Some(body.into_inner()),
..Settings::default() ..Settings::default()
}; };
let settings = settings.to_update().map_err(Error::bad_request)?; let settings = settings.to_update().map_err(Error::bad_request)?;
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -523,7 +515,9 @@ async fn delete_attributes_for_faceting(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -532,7 +526,8 @@ fn get_indexed_attributes(schema: &Schema) -> Vec<String> {
if schema.is_indexed_all() { if schema.is_indexed_all() {
["*"].iter().map(|s| s.to_string()).collect() ["*"].iter().map(|s| s.to_string()).collect()
} else { } else {
schema.indexed_name() schema
.indexed_name()
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect() .collect()
@ -543,7 +538,8 @@ fn get_displayed_attributes(schema: &Schema) -> BTreeSet<String> {
if schema.is_displayed_all() { if schema.is_displayed_all() {
["*"].iter().map(|s| s.to_string()).collect() ["*"].iter().map(|s| s.to_string()).collect()
} else { } else {
schema.displayed_name() schema
.displayed_name()
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect() .collect()

View File

@ -39,17 +39,16 @@ async fn update(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<BTreeSet<String>>, body: web::Json<BTreeSet<String>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = SettingsUpdate { let settings = SettingsUpdate {
stop_words: UpdateState::Update(body.into_inner()), stop_words: UpdateState::Update(body.into_inner()),
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -72,7 +71,9 @@ async fn delete(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }

View File

@ -50,17 +50,16 @@ async fn update(
path: web::Path<IndexParam>, path: web::Path<IndexParam>,
body: web::Json<BTreeMap<String, Vec<String>>>, body: web::Json<BTreeMap<String, Vec<String>>>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
let index = data let update_id = data.get_or_create_index(&path.index_uid, |index| {
.db
.open_index(&path.index_uid)
.ok_or(Error::index_not_found(&path.index_uid))?;
let settings = SettingsUpdate { let settings = SettingsUpdate {
synonyms: UpdateState::Update(body.into_inner()), synonyms: UpdateState::Update(body.into_inner()),
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; Ok(data
.db
.update_write(|w| index.settings_update(w, settings))?)
})?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }
@ -83,7 +82,9 @@ async fn delete(
..SettingsUpdate::default() ..SettingsUpdate::default()
}; };
let update_id = data.db.update_write(|w| index.settings_update(w, settings))?; let update_id = data
.db
.update_write(|w| index.settings_update(w, settings))?;
Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id))) Ok(HttpResponse::Accepted().json(IndexUpdateResponse::with_id(update_id)))
} }

View File

@ -15,15 +15,24 @@ use meilisearch_http::option::Opt;
#[macro_export] #[macro_export]
macro_rules! test_post_get_search { macro_rules! test_post_get_search {
($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => { ($server:expr, $query:expr, |$response:ident, $status_code:ident | $block:expr) => {
let post_query: meilisearch_http::routes::search::SearchQueryPost = serde_json::from_str(&$query.clone().to_string()).unwrap(); let post_query: meilisearch_http::routes::search::SearchQueryPost =
serde_json::from_str(&$query.clone().to_string()).unwrap();
let get_query: meilisearch_http::routes::search::SearchQuery = post_query.into(); let get_query: meilisearch_http::routes::search::SearchQuery = post_query.into();
let get_query = ::serde_url_params::to_string(&get_query).unwrap(); let get_query = ::serde_url_params::to_string(&get_query).unwrap();
let ($response, $status_code) = $server.search_get(&get_query).await; let ($response, $status_code) = $server.search_get(&get_query).await;
let _ =::std::panic::catch_unwind(|| $block) let _ = ::std::panic::catch_unwind(|| $block).map_err(|e| {
.map_err(|e| panic!("panic in get route: {:?}", e.downcast_ref::<&str>().unwrap())); panic!(
"panic in get route: {:?}",
e.downcast_ref::<&str>().unwrap()
)
});
let ($response, $status_code) = $server.search_post($query).await; let ($response, $status_code) = $server.search_post($query).await;
let _ = ::std::panic::catch_unwind(|| $block) let _ = ::std::panic::catch_unwind(|| $block).map_err(|e| {
.map_err(|e| panic!("panic in post route: {:?}", e.downcast_ref::<&str>().unwrap())); panic!(
"panic in post route: {:?}",
e.downcast_ref::<&str>().unwrap()
)
});
}; };
} }
@ -61,7 +70,6 @@ impl Server {
} }
pub async fn test_server() -> Self { pub async fn test_server() -> Self {
let mut server = Self::with_uid("test"); let mut server = Self::with_uid("test");
let body = json!({ let body = json!({
@ -151,7 +159,8 @@ impl Server {
pub async fn get_request(&mut self, url: &str) -> (Value, StatusCode) { pub async fn get_request(&mut self, url: &str) -> (Value, StatusCode) {
eprintln!("get_request: {}", url); eprintln!("get_request: {}", url);
let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let mut app =
test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await;
let req = test::TestRequest::get().uri(url).to_request(); let req = test::TestRequest::get().uri(url).to_request();
let res = test::call_service(&mut app, req).await; let res = test::call_service(&mut app, req).await;
@ -165,7 +174,8 @@ impl Server {
pub async fn post_request(&self, url: &str, body: Value) -> (Value, StatusCode) { pub async fn post_request(&self, url: &str, body: Value) -> (Value, StatusCode) {
eprintln!("post_request: {}", url); eprintln!("post_request: {}", url);
let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let mut app =
test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(url) .uri(url)
@ -183,8 +193,7 @@ impl Server {
eprintln!("post_request_async: {}", url); eprintln!("post_request_async: {}", url);
let (response, status_code) = self.post_request(url, body).await; let (response, status_code) = self.post_request(url, body).await;
// eprintln!("response: {}", response); eprintln!("response: {}", response);
assert_eq!(status_code, 202);
assert!(response["updateId"].as_u64().is_some()); assert!(response["updateId"].as_u64().is_some());
self.wait_update_id(response["updateId"].as_u64().unwrap()) self.wait_update_id(response["updateId"].as_u64().unwrap())
.await; .await;
@ -194,7 +203,8 @@ impl Server {
pub async fn put_request(&mut self, url: &str, body: Value) -> (Value, StatusCode) { pub async fn put_request(&mut self, url: &str, body: Value) -> (Value, StatusCode) {
eprintln!("put_request: {}", url); eprintln!("put_request: {}", url);
let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let mut app =
test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await;
let req = test::TestRequest::put() let req = test::TestRequest::put()
.uri(url) .uri(url)
@ -222,7 +232,8 @@ impl Server {
pub async fn delete_request(&mut self, url: &str) -> (Value, StatusCode) { pub async fn delete_request(&mut self, url: &str) -> (Value, StatusCode) {
eprintln!("delete_request: {}", url); eprintln!("delete_request: {}", url);
let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let mut app =
test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await;
let req = test::TestRequest::delete().uri(url).to_request(); let req = test::TestRequest::delete().uri(url).to_request();
let res = test::call_service(&mut app, req).await; let res = test::call_service(&mut app, req).await;
@ -340,9 +351,9 @@ impl Server {
self.delete_request_async(&url).await self.delete_request_async(&url).await
} }
pub async fn delete_multiple_documents(&mut self, body: Value) { pub async fn delete_multiple_documents(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/documents/delete-batch", self.uid); let url = format!("/indexes/{}/documents/delete-batch", self.uid);
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await
} }
pub async fn get_all_settings(&mut self) -> (Value, StatusCode) { pub async fn get_all_settings(&mut self) -> (Value, StatusCode) {
@ -355,6 +366,11 @@ impl Server {
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await;
} }
pub async fn update_all_settings_sync(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_all_settings(&mut self) -> (Value, StatusCode) { pub async fn delete_all_settings(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings", self.uid); let url = format!("/indexes/{}/settings", self.uid);
self.delete_request_async(&url).await self.delete_request_async(&url).await
@ -390,6 +406,11 @@ impl Server {
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await;
} }
pub async fn update_distinct_attribute_sync(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/distinct-attribute", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_distinct_attribute(&mut self) -> (Value, StatusCode) { pub async fn delete_distinct_attribute(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/distinct-attribute", self.uid); let url = format!("/indexes/{}/settings/distinct-attribute", self.uid);
self.delete_request_async(&url).await self.delete_request_async(&url).await
@ -410,6 +431,11 @@ impl Server {
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await;
} }
pub async fn update_searchable_attributes_sync(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/searchable-attributes", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_searchable_attributes(&mut self) -> (Value, StatusCode) { pub async fn delete_searchable_attributes(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/searchable-attributes", self.uid); let url = format!("/indexes/{}/settings/searchable-attributes", self.uid);
self.delete_request_async(&url).await self.delete_request_async(&url).await
@ -425,11 +451,39 @@ impl Server {
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await;
} }
pub async fn update_displayed_attributes_sync(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/displayed-attributes", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_displayed_attributes(&mut self) -> (Value, StatusCode) { pub async fn delete_displayed_attributes(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/displayed-attributes", self.uid); let url = format!("/indexes/{}/settings/displayed-attributes", self.uid);
self.delete_request_async(&url).await self.delete_request_async(&url).await
} }
pub async fn get_attributes_for_faceting(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid);
self.get_request(&url).await
}
pub async fn update_attributes_for_faceting(&mut self, body: Value) {
let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid);
self.post_request_async(&url, body).await;
}
pub async fn update_attributes_for_faceting_sync(
&mut self,
body: Value,
) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_attributes_for_faceting(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/attributes-for-faceting", self.uid);
self.delete_request_async(&url).await
}
pub async fn get_synonyms(&mut self) -> (Value, StatusCode) { pub async fn get_synonyms(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/synonyms", self.uid); let url = format!("/indexes/{}/settings/synonyms", self.uid);
self.get_request(&url).await self.get_request(&url).await
@ -440,6 +494,11 @@ impl Server {
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await;
} }
pub async fn update_synonyms_sync(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/synonyms", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_synonyms(&mut self) -> (Value, StatusCode) { pub async fn delete_synonyms(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/synonyms", self.uid); let url = format!("/indexes/{}/settings/synonyms", self.uid);
self.delete_request_async(&url).await self.delete_request_async(&url).await
@ -455,6 +514,11 @@ impl Server {
self.post_request_async(&url, body).await; self.post_request_async(&url, body).await;
} }
pub async fn update_stop_words_sync(&mut self, body: Value) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/stop-words", self.uid);
self.post_request(&url, body).await
}
pub async fn delete_stop_words(&mut self) -> (Value, StatusCode) { pub async fn delete_stop_words(&mut self) -> (Value, StatusCode) {
let url = format!("/indexes/{}/settings/stop-words", self.uid); let url = format!("/indexes/{}/settings/stop-words", self.uid);
self.delete_request_async(&url).await self.delete_request_async(&url).await

View File

@ -192,7 +192,9 @@ async fn add_document_with_long_field() {
"url":"/configuration/app/web.html#locations" "url":"/configuration/app/web.html#locations"
}]); }]);
server.add_or_replace_multiple_documents(body).await; server.add_or_replace_multiple_documents(body).await;
let (response, _status) = server.search_post(json!({ "q": "request_buffering" })).await; let (response, _status) = server
.search_post(json!({ "q": "request_buffering" }))
.await;
assert!(!response["hits"].as_array().unwrap().is_empty()); assert!(!response["hits"].as_array().unwrap().is_empty());
} }
@ -213,5 +215,8 @@ async fn documents_with_same_id_are_overwritten() {
server.add_or_replace_multiple_documents(documents).await; server.add_or_replace_multiple_documents(documents).await;
let (response, _status) = server.get_all_documents().await; let (response, _status) = server.get_all_documents().await;
assert_eq!(response.as_array().unwrap().len(), 1); assert_eq!(response.as_array().unwrap().len(), 1);
assert_eq!(response.as_array().unwrap()[0].as_object().unwrap()["content"], "test2"); assert_eq!(
response.as_array().unwrap()[0].as_object().unwrap()["content"],
"test2"
);
} }

View File

@ -0,0 +1,446 @@
use serde_json::json;
mod common;
#[actix_rt::test]
async fn create_index_lazy_by_pushing_documents() {
let mut server = common::Server::with_uid("movies");
// 1 - Add documents
let body = json!([{
"title": "Test",
"comment": "comment test"
}]);
let url = "/indexes/movies/documents?primaryKey=title";
let (response, status_code) = server.post_request(&url, body).await;
assert_eq!(status_code, 202);
let update_id = response["updateId"].as_u64().unwrap();
server.wait_update_id(update_id).await;
// 3 - Check update success
let (response, status_code) = server.get_update_status(update_id).await;
assert_eq!(status_code, 200);
assert_eq!(response["status"], "processed");
}
#[actix_rt::test]
async fn create_index_lazy_by_pushing_documents_and_discover_pk() {
let mut server = common::Server::with_uid("movies");
// 1 - Add documents
let body = json!([{
"id": 1,
"title": "Test",
"comment": "comment test"
}]);
let url = "/indexes/movies/documents";
let (response, status_code) = server.post_request(&url, body).await;
assert_eq!(status_code, 202);
let update_id = response["updateId"].as_u64().unwrap();
server.wait_update_id(update_id).await;
// 3 - Check update success
let (response, status_code) = server.get_update_status(update_id).await;
assert_eq!(status_code, 200);
assert_eq!(response["status"], "processed");
}
#[actix_rt::test]
async fn create_index_lazy_by_pushing_documents_with_wrong_name() {
let server = common::Server::with_uid("wrong&name");
let body = json!([{
"title": "Test",
"comment": "comment test"
}]);
let url = "/indexes/wrong&name/documents?primaryKey=title";
let (response, status_code) = server.post_request(&url, body).await;
assert_eq!(status_code, 400);
assert_eq!(response["errorCode"], "invalid_index_uid");
}
#[actix_rt::test]
async fn create_index_lazy_add_documents_failed() {
let mut server = common::Server::with_uid("wrong&name");
let body = json!([{
"title": "Test",
"comment": "comment test"
}]);
let url = "/indexes/wrong&name/documents";
let (response, status_code) = server.post_request(&url, body).await;
assert_eq!(status_code, 400);
assert_eq!(response["errorCode"], "invalid_index_uid");
let (_, status_code) = server.get_index().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_settings() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!({
"rankingRules": [
"typo",
"words",
"proximity",
"attribute",
"wordsPosition",
"exactness",
"desc(registered)",
"desc(age)",
],
"distinctAttribute": "id",
"searchableAttributes": [
"id",
"name",
"color",
"gender",
"email",
"phone",
"address",
"registered",
"about"
],
"displayedAttributes": [
"name",
"gender",
"email",
"registered",
"age",
],
"stopWords": [
"ad",
"in",
"ut",
],
"synonyms": {
"road": ["street", "avenue"],
"street": ["avenue"],
},
"attributesForFaceting": ["name"],
});
server.update_all_settings(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_settings_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!({
"rankingRules": [
"other",
"words",
"proximity",
"attribute",
"wordsPosition",
"exactness",
"desc(registered)",
"desc(age)",
],
"distinctAttribute": "id",
"searchableAttributes": [
"id",
"name",
"color",
"gender",
"email",
"phone",
"address",
"registered",
"about"
],
"displayedAttributes": [
"name",
"gender",
"email",
"registered",
"age",
],
"stopWords": [
"ad",
"in",
"ut",
],
"synonyms": {
"road": ["street", "avenue"],
"street": ["avenue"],
},
"anotherSettings": ["name"],
});
let (_, status_code) = server.update_all_settings_sync(body.clone()).await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_ranking_rules() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!([
"typo",
"words",
"proximity",
"attribute",
"wordsPosition",
"exactness",
"desc(registered)",
"desc(age)",
]);
server.update_ranking_rules(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_ranking_rules_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!({
"rankingRules": 123,
});
let (_, status_code) = server.update_ranking_rules_sync(body.clone()).await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_distinct_attribute() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!("type");
server.update_distinct_attribute(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_distinct_attribute_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(123);
let (resp, status_code) = server.update_distinct_attribute_sync(body.clone()).await;
eprintln!("resp: {:?}", resp);
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (resp, status_code) = server.get_all_settings().await;
eprintln!("resp: {:?}", resp);
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_searchable_attributes() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(["title", "description"]);
server.update_searchable_attributes(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_searchable_attributes_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(123);
let (_, status_code) = server.update_searchable_attributes_sync(body.clone()).await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_displayed_attributes() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(["title", "description"]);
server.update_displayed_attributes(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_displayed_attributes_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(123);
let (_, status_code) = server.update_displayed_attributes_sync(body.clone()).await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_attributes_for_faceting() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(["title", "description"]);
server.update_attributes_for_faceting(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_attributes_for_faceting_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(123);
let (_, status_code) = server
.update_attributes_for_faceting_sync(body.clone())
.await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_synonyms() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!({
"road": ["street", "avenue"],
"street": ["avenue"],
});
server.update_synonyms(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_synonyms_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(123);
let (_, status_code) = server.update_synonyms_sync(body.clone()).await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_stop_words() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(["le", "la", "les"]);
server.update_stop_words(body.clone()).await;
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 200);
}
#[actix_rt::test]
async fn create_index_lazy_by_sending_stop_words_with_error() {
let mut server = common::Server::with_uid("movies");
// 2 - Send the settings
let body = json!(123);
let (_, status_code) = server.update_stop_words_sync(body.clone()).await;
assert_eq!(status_code, 400);
// 3 - Get all settings and compare to the previous one
let (_, status_code) = server.get_all_settings().await;
assert_eq!(status_code, 404);
}