mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-26 20:15:07 +08:00
Merge pull request #148 from shekhirin/shekhirin/setting-enum
refactor(http, update): introduce setting enum
This commit is contained in:
commit
0d09c64dde
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -911,6 +911,7 @@ dependencies = [
|
|||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_test",
|
||||||
"stderrlog",
|
"stderrlog",
|
||||||
"structopt",
|
"structopt",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@ -1520,7 +1521,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",
|
||||||
]
|
]
|
||||||
@ -1528,8 +1530,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",
|
||||||
]
|
]
|
||||||
@ -2079,6 +2080,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_test"
|
||||||
|
version = "1.0.125"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4bb5fef7eaf5a97917567183607ac4224c5b451c15023930f23b937cce879fe"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -37,3 +37,6 @@ fst = "0.4.5"
|
|||||||
|
|
||||||
# Temporary fix for bitvec, remove once fixed. (https://github.com/bitvecto-rs/bitvec/issues/105)
|
# Temporary fix for bitvec, remove once fixed. (https://github.com/bitvecto-rs/bitvec/issues/105)
|
||||||
funty = "=1.1"
|
funty = "=1.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_test = "1.0.125"
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
|
use std::{io, mem};
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::{File, create_dir_all};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{mem, io};
|
|
||||||
|
|
||||||
use askama_warp::Template;
|
use askama_warp::Template;
|
||||||
use byte_unit::Byte;
|
use byte_unit::Byte;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use futures::stream;
|
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use futures::stream;
|
||||||
use grenad::CompressionType;
|
use grenad::CompressionType;
|
||||||
use heed::EnvOpenOptions;
|
use heed::EnvOpenOptions;
|
||||||
|
use meilisearch_tokenizer::{Analyzer, AnalyzerConfig};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use rayon::ThreadPool;
|
use rayon::ThreadPool;
|
||||||
use serde::{Serialize, Deserialize, Deserializer};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio::fs::File as TFile;
|
use tokio::fs::File as TFile;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use warp::filters::ws::Message;
|
|
||||||
use warp::{Filter, http::Response};
|
use warp::{Filter, http::Response};
|
||||||
use meilisearch_tokenizer::{Analyzer, AnalyzerConfig};
|
use warp::filters::ws::Message;
|
||||||
|
|
||||||
|
use milli::{FacetCondition, Index, MatchingWords, obkv_to_json, SearchResult, UpdateStore};
|
||||||
use milli::facet::FacetValue;
|
use milli::facet::FacetValue;
|
||||||
|
use milli::update::{IndexDocumentsMethod, Setting, UpdateBuilder, UpdateFormat};
|
||||||
use milli::update::UpdateIndexingStep::*;
|
use milli::update::UpdateIndexingStep::*;
|
||||||
use milli::update::{UpdateBuilder, IndexDocumentsMethod, UpdateFormat};
|
|
||||||
use milli::{obkv_to_json, Index, UpdateStore, SearchResult, MatchingWords, FacetCondition};
|
|
||||||
|
|
||||||
static GLOBAL_THREAD_POOL: OnceCell<ThreadPool> = OnceCell::new();
|
static GLOBAL_THREAD_POOL: OnceCell<ThreadPool> = OnceCell::new();
|
||||||
|
|
||||||
@ -154,17 +154,17 @@ impl<'a, A: AsRef<[u8]>> Highlighter<'a, A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::String(string)
|
Value::String(string)
|
||||||
},
|
}
|
||||||
Value::Array(values) => {
|
Value::Array(values) => {
|
||||||
Value::Array(values.into_iter()
|
Value::Array(values.into_iter()
|
||||||
.map(|v| self.highlight_value(v, matching_words))
|
.map(|v| self.highlight_value(v, matching_words))
|
||||||
.collect())
|
.collect())
|
||||||
},
|
}
|
||||||
Value::Object(object) => {
|
Value::Object(object) => {
|
||||||
Value::Object(object.into_iter()
|
Value::Object(object.into_iter()
|
||||||
.map(|(k, v)| (k, self.highlight_value(v, matching_words)))
|
.map(|(k, v)| (k, self.highlight_value(v, matching_words)))
|
||||||
.collect())
|
.collect())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,40 +242,24 @@ enum UpdateMetaProgress {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct Settings {
|
struct Settings {
|
||||||
#[serde(
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
default,
|
displayed_attributes: Setting<Vec<String>>,
|
||||||
deserialize_with = "deserialize_some",
|
|
||||||
skip_serializing_if = "Option::is_none",
|
|
||||||
)]
|
|
||||||
displayed_attributes: Option<Option<Vec<String>>>,
|
|
||||||
|
|
||||||
#[serde(
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
default,
|
searchable_attributes: Setting<Vec<String>>,
|
||||||
deserialize_with = "deserialize_some",
|
|
||||||
skip_serializing_if = "Option::is_none",
|
|
||||||
)]
|
|
||||||
searchable_attributes: Option<Option<Vec<String>>>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
faceted_attributes: Option<HashMap<String, String>>,
|
faceted_attributes: Setting<HashMap<String, String>>,
|
||||||
|
|
||||||
#[serde(
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
default,
|
criteria: Setting<Vec<String>>,
|
||||||
deserialize_with = "deserialize_some",
|
|
||||||
skip_serializing_if = "Option::is_none",
|
|
||||||
)]
|
|
||||||
criteria: Option<Option<Vec<String>>>,
|
|
||||||
|
|
||||||
#[serde(
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
default,
|
stop_words: Setting<BTreeSet<String>>,
|
||||||
deserialize_with = "deserialize_some",
|
|
||||||
skip_serializing_if = "Option::is_none",
|
|
||||||
)]
|
|
||||||
stop_words: Option<Option<BTreeSet<String>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -294,14 +278,6 @@ struct WordsPrefixes {
|
|||||||
max_prefix_length: Option<usize>,
|
max_prefix_length: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any value that is present is considered Some value, including null.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
@ -396,7 +372,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
total_steps: indexing_step.number_of_steps(),
|
total_steps: indexing_step.number_of_steps(),
|
||||||
current,
|
current,
|
||||||
total,
|
total,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -404,7 +380,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Ok(_) => wtxn.commit().map_err(Into::into),
|
Ok(_) => wtxn.commit().map_err(Into::into),
|
||||||
Err(e) => Err(e.into())
|
Err(e) => Err(e.into())
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
UpdateMeta::ClearDocuments => {
|
UpdateMeta::ClearDocuments => {
|
||||||
// We must use the write transaction of the update here.
|
// We must use the write transaction of the update here.
|
||||||
let mut wtxn = index_cloned.write_txn()?;
|
let mut wtxn = index_cloned.write_txn()?;
|
||||||
@ -414,47 +390,45 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Ok(_count) => wtxn.commit().map_err(Into::into),
|
Ok(_count) => wtxn.commit().map_err(Into::into),
|
||||||
Err(e) => Err(e.into())
|
Err(e) => Err(e.into())
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
UpdateMeta::Settings(settings) => {
|
UpdateMeta::Settings(settings) => {
|
||||||
// We must use the write transaction of the update here.
|
// We must use the write transaction of the update here.
|
||||||
let mut wtxn = index_cloned.write_txn()?;
|
let mut wtxn = index_cloned.write_txn()?;
|
||||||
let mut builder = update_builder.settings(&mut wtxn, &index_cloned);
|
let mut builder = update_builder.settings(&mut wtxn, &index_cloned);
|
||||||
|
|
||||||
// We transpose the settings JSON struct into a real setting update.
|
// We transpose the settings JSON struct into a real setting update.
|
||||||
if let Some(names) = settings.searchable_attributes {
|
match settings.searchable_attributes {
|
||||||
match names {
|
Setting::Set(searchable_attributes) => builder.set_searchable_fields(searchable_attributes),
|
||||||
Some(names) => builder.set_searchable_fields(names),
|
Setting::Reset => builder.reset_searchable_fields(),
|
||||||
None => builder.reset_searchable_fields(),
|
Setting::NotSet => ()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We transpose the settings JSON struct into a real setting update.
|
// We transpose the settings JSON struct into a real setting update.
|
||||||
if let Some(names) = settings.displayed_attributes {
|
match settings.displayed_attributes {
|
||||||
match names {
|
Setting::Set(displayed_attributes) => builder.set_displayed_fields(displayed_attributes),
|
||||||
Some(names) => builder.set_displayed_fields(names),
|
Setting::Reset => builder.reset_displayed_fields(),
|
||||||
None => builder.reset_displayed_fields(),
|
Setting::NotSet => ()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We transpose the settings JSON struct into a real setting update.
|
// We transpose the settings JSON struct into a real setting update.
|
||||||
if let Some(facet_types) = settings.faceted_attributes {
|
match settings.faceted_attributes {
|
||||||
builder.set_faceted_fields(facet_types);
|
Setting::Set(faceted_attributes) => builder.set_faceted_fields(faceted_attributes),
|
||||||
|
Setting::Reset => builder.reset_faceted_fields(),
|
||||||
|
Setting::NotSet => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// We transpose the settings JSON struct into a real setting update.
|
// We transpose the settings JSON struct into a real setting update.
|
||||||
if let Some(criteria) = settings.criteria {
|
match settings.criteria {
|
||||||
match criteria {
|
Setting::Set(criteria) => builder.set_criteria(criteria),
|
||||||
Some(criteria) => builder.set_criteria(criteria),
|
Setting::Reset => builder.reset_criteria(),
|
||||||
None => builder.reset_criteria(),
|
Setting::NotSet => ()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We transpose the settings JSON struct into a real setting update.
|
// We transpose the settings JSON struct into a real setting update.
|
||||||
if let Some(stop_words) = settings.stop_words {
|
match settings.stop_words {
|
||||||
match stop_words {
|
Setting::Set(stop_words) => builder.set_stop_words(stop_words),
|
||||||
Some(stop_words) => builder.set_stop_words(stop_words),
|
Setting::Reset => builder.reset_stop_words(),
|
||||||
None => builder.reset_stop_words(),
|
Setting::NotSet => ()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = builder.execute(|indexing_step, update_id| {
|
let result = builder.execute(|indexing_step, update_id| {
|
||||||
@ -471,7 +445,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
total_steps: indexing_step.number_of_steps(),
|
total_steps: indexing_step.number_of_steps(),
|
||||||
current,
|
current,
|
||||||
total,
|
total,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -479,7 +453,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Ok(_count) => wtxn.commit().map_err(Into::into),
|
Ok(_count) => wtxn.commit().map_err(Into::into),
|
||||||
Err(e) => Err(e.into())
|
Err(e) => Err(e.into())
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
UpdateMeta::Facets(levels) => {
|
UpdateMeta::Facets(levels) => {
|
||||||
// We must use the write transaction of the update here.
|
// We must use the write transaction of the update here.
|
||||||
let mut wtxn = index_cloned.write_txn()?;
|
let mut wtxn = index_cloned.write_txn()?;
|
||||||
@ -494,7 +468,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Ok(()) => wtxn.commit().map_err(Into::into),
|
Ok(()) => wtxn.commit().map_err(Into::into),
|
||||||
Err(e) => Err(e.into())
|
Err(e) => Err(e.into())
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
UpdateMeta::WordsPrefixes(settings) => {
|
UpdateMeta::WordsPrefixes(settings) => {
|
||||||
// We must use the write transaction of the update here.
|
// We must use the write transaction of the update here.
|
||||||
let mut wtxn = index_cloned.write_txn()?;
|
let mut wtxn = index_cloned.write_txn()?;
|
||||||
@ -716,7 +690,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let filters = match query.filters {
|
let filters = match query.filters {
|
||||||
Some(condition) if !condition.trim().is_empty() => {
|
Some(condition) if !condition.trim().is_empty() => {
|
||||||
Some(FacetCondition::from_str(&rtxn, &index, &condition).unwrap())
|
Some(FacetCondition::from_str(&rtxn, &index, &condition).unwrap())
|
||||||
},
|
}
|
||||||
_otherwise => None,
|
_otherwise => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -724,14 +698,14 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Some(array) => {
|
Some(array) => {
|
||||||
let eithers = array.into_iter().map(Into::into);
|
let eithers = array.into_iter().map(Into::into);
|
||||||
FacetCondition::from_array(&rtxn, &index, eithers).unwrap()
|
FacetCondition::from_array(&rtxn, &index, eithers).unwrap()
|
||||||
},
|
}
|
||||||
_otherwise => None,
|
_otherwise => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let condition = match (filters, facet_filters) {
|
let condition = match (filters, facet_filters) {
|
||||||
(Some(filters), Some(facet_filters)) => {
|
(Some(filters), Some(facet_filters)) => {
|
||||||
Some(FacetCondition::And(Box::new(filters), Box::new(facet_filters)))
|
Some(FacetCondition::And(Box::new(filters), Box::new(facet_filters)))
|
||||||
},
|
}
|
||||||
(Some(condition), None) | (None, Some(condition)) => Some(condition),
|
(Some(condition), None) | (None, Some(condition)) => Some(condition),
|
||||||
_otherwise => None,
|
_otherwise => None,
|
||||||
};
|
};
|
||||||
@ -807,12 +781,12 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Response::builder()
|
Response::builder()
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(serde_json::to_string(&document).unwrap())
|
.body(serde_json::to_string(&document).unwrap())
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(404)
|
.status(404)
|
||||||
.body(format!("Document with id {:?} not found.", id))
|
.body(format!("Document with id {:?} not found.", id))
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -978,11 +952,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
let msg = serde_json::to_string(&status).unwrap();
|
let msg = serde_json::to_string(&status).unwrap();
|
||||||
stream::iter(Some(Ok(Message::text(msg))))
|
stream::iter(Some(Ok(Message::text(msg))))
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("channel error: {:?}", e);
|
eprintln!("channel error: {:?}", e);
|
||||||
stream::iter(None)
|
stream::iter(None)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.forward(websocket)
|
.forward(websocket)
|
||||||
@ -1019,3 +993,62 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let addr = SocketAddr::from_str(&opt.http_listen_addr)?;
|
let addr = SocketAddr::from_str(&opt.http_listen_addr)?;
|
||||||
Ok(warp::serve(routes).run(addr).await)
|
Ok(warp::serve(routes).run(addr).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_test::{assert_de_tokens, assert_ser_tokens, Token};
|
||||||
|
|
||||||
|
use milli::update::Setting;
|
||||||
|
|
||||||
|
use crate::Settings;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_settings() {
|
||||||
|
let settings = Settings {
|
||||||
|
displayed_attributes: Setting::Set(vec!["name".to_string()]),
|
||||||
|
searchable_attributes: Setting::Reset,
|
||||||
|
faceted_attributes: Setting::NotSet,
|
||||||
|
criteria: Setting::NotSet,
|
||||||
|
stop_words: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_ser_tokens(&settings, &[
|
||||||
|
Token::Struct { name: "Settings", len: 3 },
|
||||||
|
Token::Str("displayedAttributes"),
|
||||||
|
Token::Some,
|
||||||
|
Token::Seq { len: Some(1) },
|
||||||
|
Token::Str("name"),
|
||||||
|
Token::SeqEnd,
|
||||||
|
Token::Str("searchableAttributes"),
|
||||||
|
Token::None,
|
||||||
|
Token::Str("facetedAttributes"),
|
||||||
|
Token::None,
|
||||||
|
Token::StructEnd,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_settings() {
|
||||||
|
let settings = Settings {
|
||||||
|
displayed_attributes: Setting::Set(vec!["name".to_string()]),
|
||||||
|
searchable_attributes: Setting::Reset,
|
||||||
|
faceted_attributes: Setting::Reset,
|
||||||
|
criteria: Setting::NotSet,
|
||||||
|
stop_words: Setting::NotSet,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_de_tokens(&settings, &[
|
||||||
|
Token::Struct { name: "Settings", len: 3 },
|
||||||
|
Token::Str("displayedAttributes"),
|
||||||
|
Token::Some,
|
||||||
|
Token::Seq { len: Some(1) },
|
||||||
|
Token::Str("name"),
|
||||||
|
Token::SeqEnd,
|
||||||
|
Token::Str("searchableAttributes"),
|
||||||
|
Token::None,
|
||||||
|
Token::Str("facetedAttributes"),
|
||||||
|
Token::None,
|
||||||
|
Token::StructEnd,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
pub use self::available_documents_ids::AvailableDocumentsIds;
|
||||||
|
pub use self::clear_documents::ClearDocuments;
|
||||||
|
pub use self::delete_documents::DeleteDocuments;
|
||||||
|
pub use self::facets::Facets;
|
||||||
|
pub use self::index_documents::{DocumentAdditionResult, IndexDocuments, IndexDocumentsMethod, UpdateFormat};
|
||||||
|
pub use self::settings::{Setting, Settings};
|
||||||
|
pub use self::update_builder::UpdateBuilder;
|
||||||
|
pub use self::update_step::UpdateIndexingStep;
|
||||||
|
pub use self::words_prefixes::WordsPrefixes;
|
||||||
|
|
||||||
mod available_documents_ids;
|
mod available_documents_ids;
|
||||||
mod clear_documents;
|
mod clear_documents;
|
||||||
mod delete_documents;
|
mod delete_documents;
|
||||||
@ -8,12 +18,3 @@ mod update_builder;
|
|||||||
mod update_step;
|
mod update_step;
|
||||||
mod words_prefixes;
|
mod words_prefixes;
|
||||||
|
|
||||||
pub use self::available_documents_ids::AvailableDocumentsIds;
|
|
||||||
pub use self::clear_documents::ClearDocuments;
|
|
||||||
pub use self::delete_documents::DeleteDocuments;
|
|
||||||
pub use self::facets::Facets;
|
|
||||||
pub use self::index_documents::{IndexDocuments, IndexDocumentsMethod, UpdateFormat, DocumentAdditionResult};
|
|
||||||
pub use self::settings::Settings;
|
|
||||||
pub use self::update_builder::UpdateBuilder;
|
|
||||||
pub use self::update_step::UpdateIndexingStep;
|
|
||||||
pub use self::words_prefixes::WordsPrefixes;
|
|
||||||
|
@ -6,12 +6,51 @@ use chrono::Utc;
|
|||||||
use grenad::CompressionType;
|
use grenad::CompressionType;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rayon::ThreadPool;
|
use rayon::ThreadPool;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::{FieldsIdsMap, Index};
|
||||||
use crate::criterion::Criterion;
|
use crate::criterion::Criterion;
|
||||||
use crate::facet::FacetType;
|
use crate::facet::FacetType;
|
||||||
use crate::update::index_documents::{Transform, IndexDocumentsMethod};
|
|
||||||
use crate::update::{ClearDocuments, IndexDocuments, UpdateIndexingStep};
|
use crate::update::{ClearDocuments, IndexDocuments, UpdateIndexingStep};
|
||||||
use crate::{Index, FieldsIdsMap};
|
use crate::update::index_documents::{IndexDocumentsMethod, Transform};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Setting<T> {
|
||||||
|
Set(T),
|
||||||
|
Reset,
|
||||||
|
NotSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Setting<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::NotSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Setting<T> {
|
||||||
|
pub const fn is_not_set(&self) -> bool {
|
||||||
|
matches!(self, Self::NotSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Serialize> Serialize for Setting<T> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||||
|
match self {
|
||||||
|
Self::Set(value) => Some(value),
|
||||||
|
// Usually not_set isn't serialized by setting skip_serializing_if field attribute
|
||||||
|
Self::NotSet | Self::Reset => None,
|
||||||
|
}.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Setting<T> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
||||||
|
Deserialize::deserialize(deserializer).map(|x| match x {
|
||||||
|
Some(x) => Self::Set(x),
|
||||||
|
None => Self::Reset, // Reset is forced by sending null value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Settings<'a, 't, 'u, 'i> {
|
pub struct Settings<'a, 't, 'u, 'i> {
|
||||||
wtxn: &'t mut heed::RwTxn<'i, 'u>,
|
wtxn: &'t mut heed::RwTxn<'i, 'u>,
|
||||||
@ -26,13 +65,11 @@ pub struct Settings<'a, 't, 'u, 'i> {
|
|||||||
pub(crate) thread_pool: Option<&'a ThreadPool>,
|
pub(crate) thread_pool: Option<&'a ThreadPool>,
|
||||||
update_id: u64,
|
update_id: u64,
|
||||||
|
|
||||||
// If a struct field is set to `None` it means that it hasn't been set by the user,
|
searchable_fields: Setting<Vec<String>>,
|
||||||
// however if it is `Some(None)` it means that the user forced a reset of the setting.
|
displayed_fields: Setting<Vec<String>>,
|
||||||
searchable_fields: Option<Option<Vec<String>>>,
|
faceted_fields: Setting<HashMap<String, String>>,
|
||||||
displayed_fields: Option<Option<Vec<String>>>,
|
criteria: Setting<Vec<String>>,
|
||||||
faceted_fields: Option<Option<HashMap<String, String>>>,
|
stop_words: Setting<BTreeSet<String>>,
|
||||||
criteria: Option<Option<Vec<String>>>,
|
|
||||||
stop_words: Option<Option<BTreeSet<String>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
||||||
@ -52,56 +89,56 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
chunk_compression_level: None,
|
chunk_compression_level: None,
|
||||||
chunk_fusing_shrink_size: None,
|
chunk_fusing_shrink_size: None,
|
||||||
thread_pool: None,
|
thread_pool: None,
|
||||||
searchable_fields: None,
|
searchable_fields: Setting::NotSet,
|
||||||
displayed_fields: None,
|
displayed_fields: Setting::NotSet,
|
||||||
faceted_fields: None,
|
faceted_fields: Setting::NotSet,
|
||||||
criteria: None,
|
criteria: Setting::NotSet,
|
||||||
stop_words: None,
|
stop_words: Setting::NotSet,
|
||||||
update_id,
|
update_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_searchable_fields(&mut self) {
|
pub fn reset_searchable_fields(&mut self) {
|
||||||
self.searchable_fields = Some(None);
|
self.searchable_fields = Setting::Reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_searchable_fields(&mut self, names: Vec<String>) {
|
pub fn set_searchable_fields(&mut self, names: Vec<String>) {
|
||||||
self.searchable_fields = Some(Some(names));
|
self.searchable_fields = Setting::Set(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_displayed_fields(&mut self) {
|
pub fn reset_displayed_fields(&mut self) {
|
||||||
self.displayed_fields = Some(None);
|
self.displayed_fields = Setting::Reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_displayed_fields(&mut self, names: Vec<String>) {
|
pub fn set_displayed_fields(&mut self, names: Vec<String>) {
|
||||||
self.displayed_fields = Some(Some(names));
|
self.displayed_fields = Setting::Set(names);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_faceted_fields(&mut self, names_facet_types: HashMap<String, String>) {
|
|
||||||
self.faceted_fields = Some(Some(names_facet_types));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_faceted_fields(&mut self) {
|
pub fn reset_faceted_fields(&mut self) {
|
||||||
self.faceted_fields = Some(None);
|
self.faceted_fields = Setting::Reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_faceted_fields(&mut self, names_facet_types: HashMap<String, String>) {
|
||||||
|
self.faceted_fields = Setting::Set(names_facet_types);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_criteria(&mut self) {
|
pub fn reset_criteria(&mut self) {
|
||||||
self.criteria = Some(None);
|
self.criteria = Setting::Reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_criteria(&mut self, criteria: Vec<String>) {
|
pub fn set_criteria(&mut self, criteria: Vec<String>) {
|
||||||
self.criteria = Some(Some(criteria));
|
self.criteria = Setting::Set(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_stop_words(&mut self) {
|
pub fn reset_stop_words(&mut self) {
|
||||||
self.stop_words = Some(None);
|
self.stop_words = Setting::Reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stop_words(&mut self, stop_words: BTreeSet<String>) {
|
pub fn set_stop_words(&mut self, stop_words: BTreeSet<String>) {
|
||||||
self.stop_words = if stop_words.is_empty() {
|
self.stop_words = if stop_words.is_empty() {
|
||||||
Some(None)
|
Setting::Reset
|
||||||
} else {
|
} else {
|
||||||
Some(Some(stop_words))
|
Setting::Set(stop_words)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +152,7 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
// if the settings are set before any document update, we don't need to do anything, and
|
// if the settings are set before any document update, we don't need to do anything, and
|
||||||
// will set the primary key during the first document addition.
|
// will set the primary key during the first document addition.
|
||||||
if self.index.number_of_documents(&self.wtxn)? == 0 {
|
if self.index.number_of_documents(&self.wtxn)? == 0 {
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform = Transform {
|
let transform = Transform {
|
||||||
@ -160,7 +197,7 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
|
|
||||||
fn update_displayed(&mut self) -> anyhow::Result<bool> {
|
fn update_displayed(&mut self) -> anyhow::Result<bool> {
|
||||||
match self.displayed_fields {
|
match self.displayed_fields {
|
||||||
Some(Some(ref fields)) => {
|
Setting::Set(ref fields) => {
|
||||||
let mut fields_ids_map = self.index.fields_ids_map(self.wtxn)?;
|
let mut fields_ids_map = self.index.fields_ids_map(self.wtxn)?;
|
||||||
// fields are deduplicated, only the first occurrence is taken into account
|
// fields are deduplicated, only the first occurrence is taken into account
|
||||||
let names: Vec<_> = fields
|
let names: Vec<_> = fields
|
||||||
@ -177,8 +214,8 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
self.index.put_displayed_fields(self.wtxn, &names)?;
|
self.index.put_displayed_fields(self.wtxn, &names)?;
|
||||||
self.index.put_fields_ids_map(self.wtxn, &fields_ids_map)?;
|
self.index.put_fields_ids_map(self.wtxn, &fields_ids_map)?;
|
||||||
}
|
}
|
||||||
Some(None) => { self.index.delete_displayed_fields(self.wtxn)?; },
|
Setting::Reset => { self.index.delete_displayed_fields(self.wtxn)?; }
|
||||||
None => return Ok(false),
|
Setting::NotSet => return Ok(false),
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@ -187,7 +224,7 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
/// reflect the order of the searchable attributes.
|
/// reflect the order of the searchable attributes.
|
||||||
fn update_searchable(&mut self) -> anyhow::Result<bool> {
|
fn update_searchable(&mut self) -> anyhow::Result<bool> {
|
||||||
match self.searchable_fields {
|
match self.searchable_fields {
|
||||||
Some(Some(ref fields)) => {
|
Setting::Set(ref fields) => {
|
||||||
// every time the searchable attributes are updated, we need to update the
|
// every time the searchable attributes are updated, we need to update the
|
||||||
// ids for any settings that uses the facets. (displayed_fields,
|
// ids for any settings that uses the facets. (displayed_fields,
|
||||||
// faceted_fields)
|
// faceted_fields)
|
||||||
@ -218,15 +255,15 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
self.index.put_searchable_fields(self.wtxn, &names)?;
|
self.index.put_searchable_fields(self.wtxn, &names)?;
|
||||||
self.index.put_fields_ids_map(self.wtxn, &new_fields_ids_map)?;
|
self.index.put_fields_ids_map(self.wtxn, &new_fields_ids_map)?;
|
||||||
}
|
}
|
||||||
Some(None) => { self.index.delete_searchable_fields(self.wtxn)?; },
|
Setting::Reset => { self.index.delete_searchable_fields(self.wtxn)?; }
|
||||||
None => return Ok(false),
|
Setting::NotSet => return Ok(false),
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_stop_words(&mut self) -> anyhow::Result<bool> {
|
fn update_stop_words(&mut self) -> anyhow::Result<bool> {
|
||||||
match self.stop_words {
|
match self.stop_words {
|
||||||
Some(Some(ref stop_words)) => {
|
Setting::Set(ref stop_words) => {
|
||||||
let current = self.index.stop_words(self.wtxn)?;
|
let current = self.index.stop_words(self.wtxn)?;
|
||||||
// since we can't compare a BTreeSet with an FST we are going to convert the
|
// since we can't compare a BTreeSet with an FST we are going to convert the
|
||||||
// BTreeSet to an FST and then compare bytes per bytes the two FSTs.
|
// BTreeSet to an FST and then compare bytes per bytes the two FSTs.
|
||||||
@ -241,14 +278,14 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(None) => Ok(self.index.delete_stop_words(self.wtxn)?),
|
Setting::Reset => Ok(self.index.delete_stop_words(self.wtxn)?),
|
||||||
None => Ok(false),
|
Setting::NotSet => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_facets(&mut self) -> anyhow::Result<bool> {
|
fn update_facets(&mut self) -> anyhow::Result<bool> {
|
||||||
match self.faceted_fields {
|
match self.faceted_fields {
|
||||||
Some(Some(ref fields)) => {
|
Setting::Set(ref fields) => {
|
||||||
let mut fields_ids_map = self.index.fields_ids_map(self.wtxn)?;
|
let mut fields_ids_map = self.index.fields_ids_map(self.wtxn)?;
|
||||||
let mut new_facets = HashMap::new();
|
let mut new_facets = HashMap::new();
|
||||||
for (name, ty) in fields {
|
for (name, ty) in fields {
|
||||||
@ -259,15 +296,15 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
self.index.put_faceted_fields(self.wtxn, &new_facets)?;
|
self.index.put_faceted_fields(self.wtxn, &new_facets)?;
|
||||||
self.index.put_fields_ids_map(self.wtxn, &fields_ids_map)?;
|
self.index.put_fields_ids_map(self.wtxn, &fields_ids_map)?;
|
||||||
}
|
}
|
||||||
Some(None) => { self.index.delete_faceted_fields(self.wtxn)?; },
|
Setting::Reset => { self.index.delete_faceted_fields(self.wtxn)?; }
|
||||||
None => return Ok(false)
|
Setting::NotSet => return Ok(false)
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_criteria(&mut self) -> anyhow::Result<()> {
|
fn update_criteria(&mut self) -> anyhow::Result<()> {
|
||||||
match self.criteria {
|
match self.criteria {
|
||||||
Some(Some(ref fields)) => {
|
Setting::Set(ref fields) => {
|
||||||
let faceted_fields = self.index.faceted_fields(&self.wtxn)?;
|
let faceted_fields = self.index.faceted_fields(&self.wtxn)?;
|
||||||
let mut new_criteria = Vec::new();
|
let mut new_criteria = Vec::new();
|
||||||
for name in fields {
|
for name in fields {
|
||||||
@ -276,8 +313,8 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
}
|
}
|
||||||
self.index.put_criteria(self.wtxn, &new_criteria)?;
|
self.index.put_criteria(self.wtxn, &new_criteria)?;
|
||||||
}
|
}
|
||||||
Some(None) => { self.index.delete_criteria(self.wtxn)?; }
|
Setting::Reset => { self.index.delete_criteria(self.wtxn)?; }
|
||||||
None => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -305,14 +342,14 @@ impl<'a, 't, 'u, 'i> Settings<'a, 't, 'u, 'i> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use heed::EnvOpenOptions;
|
use heed::EnvOpenOptions;
|
||||||
use maplit::{hashmap, btreeset};
|
use maplit::{btreeset, hashmap};
|
||||||
|
|
||||||
use crate::facet::FacetType;
|
use crate::facet::FacetType;
|
||||||
use crate::update::{IndexDocuments, UpdateFormat};
|
use crate::update::{IndexDocuments, UpdateFormat};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_and_reset_searchable_fields() {
|
fn set_and_reset_searchable_fields() {
|
||||||
let path = tempfile::tempdir().unwrap();
|
let path = tempfile::tempdir().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user