diff --git a/crates/dump/src/lib.rs b/crates/dump/src/lib.rs index ee63f7048..042a0a1b9 100644 --- a/crates/dump/src/lib.rs +++ b/crates/dump/src/lib.rs @@ -305,6 +305,7 @@ pub(crate) mod test { localized_attributes: Setting::NotSet, facet_search: Setting::NotSet, prefix_search: Setting::NotSet, + execute_after_update: Setting::NotSet, _kind: std::marker::PhantomData, }; settings.check() diff --git a/crates/dump/src/reader/compat/v5_to_v6.rs b/crates/dump/src/reader/compat/v5_to_v6.rs index 6b63e7c6b..f4938414f 100644 --- a/crates/dump/src/reader/compat/v5_to_v6.rs +++ b/crates/dump/src/reader/compat/v5_to_v6.rs @@ -397,6 +397,7 @@ impl From> for v6::Settings { search_cutoff_ms: v6::Setting::NotSet, facet_search: v6::Setting::NotSet, prefix_search: v6::Setting::NotSet, + execute_after_update: v6::Setting::NotSet, _kind: std::marker::PhantomData, } } diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 6c547d51e..a92d7a11f 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -312,6 +312,7 @@ InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ; InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ; InvalidSettingsProximityPrecision , InvalidRequest , BAD_REQUEST ; InvalidSettingsFacetSearch , InvalidRequest , BAD_REQUEST ; +InvalidSettingsexecuteAfterUpdate , InvalidRequest , BAD_REQUEST ; InvalidSettingsPrefixSearch , InvalidRequest , BAD_REQUEST ; InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ; InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch-types/src/settings.rs b/crates/meilisearch-types/src/settings.rs index 6ace0f4ee..4c1e4c1ac 100644 --- a/crates/meilisearch-types/src/settings.rs +++ b/crates/meilisearch-types/src/settings.rs @@ -289,6 +289,12 @@ pub struct Settings { #[schema(value_type = Option, example = json!("Hemlo"))] pub prefix_search: Setting, + /// Function to execute after an update + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("doc.likes += 1"))] + pub execute_after_update: Setting, + #[serde(skip)] #[deserr(skip)] pub _kind: PhantomData, @@ -354,6 +360,7 @@ impl Settings { localized_attributes: Setting::Reset, facet_search: Setting::Reset, prefix_search: Setting::Reset, + execute_after_update: Setting::Reset, _kind: PhantomData, } } @@ -380,6 +387,7 @@ impl Settings { localized_attributes: localized_attributes_rules, facet_search, prefix_search, + execute_after_update, _kind, } = self; @@ -404,6 +412,7 @@ impl Settings { localized_attributes: localized_attributes_rules, facet_search, prefix_search, + execute_after_update, _kind: PhantomData, } } @@ -454,6 +463,7 @@ impl Settings { localized_attributes: self.localized_attributes, facet_search: self.facet_search, prefix_search: self.prefix_search, + execute_after_update: self.execute_after_update, _kind: PhantomData, } } @@ -530,6 +540,10 @@ impl Settings { }, prefix_search: other.prefix_search.or(self.prefix_search), facet_search: other.facet_search.or(self.facet_search), + execute_after_update: other + .execute_after_update + .clone() + .or(self.execute_after_update.clone()), _kind: PhantomData, } } @@ -568,6 +582,7 @@ pub fn apply_settings_to_builder( localized_attributes: localized_attributes_rules, facet_search, prefix_search, + execute_after_update, _kind, } = settings; @@ -772,6 +787,14 @@ pub fn apply_settings_to_builder( Setting::Reset => builder.reset_facet_search(), Setting::NotSet => (), } + + match execute_after_update { + Setting::Set(execute_after_update) => { + builder.set_execute_after_update(execute_after_update.clone()) + } + Setting::Reset => builder.reset_execute_after_update(), + Setting::NotSet => (), + } } pub enum SecretPolicy { @@ -867,14 +890,11 @@ pub fn settings( }) .collect(); let embedders = Setting::Set(embedders); - let search_cutoff_ms = index.search_cutoff(rtxn)?; - let localized_attributes_rules = index.localized_attributes_rules(rtxn)?; - let prefix_search = index.prefix_search(rtxn)?.map(PrefixSearchSettings::from); - let facet_search = index.facet_search(rtxn)?; + let execute_after_update = index.execute_after_update(rtxn)?; let mut settings = Settings { displayed_attributes: match displayed_attributes { @@ -914,6 +934,10 @@ pub fn settings( }, prefix_search: Setting::Set(prefix_search.unwrap_or_default()), facet_search: Setting::Set(facet_search), + execute_after_update: match execute_after_update { + Some(function) => Setting::Set(function.to_string()), + None => Setting::NotSet, + }, _kind: PhantomData, }; @@ -1141,6 +1165,7 @@ pub(crate) mod test { search_cutoff_ms: Setting::NotSet, facet_search: Setting::NotSet, prefix_search: Setting::NotSet, + execute_after_update: Setting::NotSet, _kind: PhantomData::, }; @@ -1172,6 +1197,7 @@ pub(crate) mod test { search_cutoff_ms: Setting::NotSet, facet_search: Setting::NotSet, prefix_search: Setting::NotSet, + execute_after_update: Setting::NotSet, _kind: PhantomData::, }; diff --git a/crates/meilisearch/src/routes/indexes/settings.rs b/crates/meilisearch/src/routes/indexes/settings.rs index 92b018c8c..3fbd662ae 100644 --- a/crates/meilisearch/src/routes/indexes/settings.rs +++ b/crates/meilisearch/src/routes/indexes/settings.rs @@ -497,6 +497,17 @@ make_setting_routes!( camelcase_attr: "facetSearch", analytics: FacetSearchAnalytics }, + { + route: "/execute-after-update", + update_verb: put, + value_type: String, + err_type: meilisearch_types::deserr::DeserrJsonError< + meilisearch_types::error::deserr_codes::InvalidSettingsexecuteAfterUpdate, + >, + attr: execute_after_update, + camelcase_attr: "executeAfterUpdate", + analytics: ExecuteAfterUpdateAnalytics + }, { route: "/prefix-search", update_verb: put, @@ -596,6 +607,9 @@ pub async fn update_all( new_settings.non_separator_tokens.as_ref().set(), ), facet_search: FacetSearchAnalytics::new(new_settings.facet_search.as_ref().set()), + execute_after_update: ExecuteAfterUpdateAnalytics::new( + new_settings.execute_after_update.as_ref().set(), + ), prefix_search: PrefixSearchAnalytics::new(new_settings.prefix_search.as_ref().set()), }, &req, diff --git a/crates/meilisearch/src/routes/indexes/settings_analytics.rs b/crates/meilisearch/src/routes/indexes/settings_analytics.rs index cb5983f02..3378e99aa 100644 --- a/crates/meilisearch/src/routes/indexes/settings_analytics.rs +++ b/crates/meilisearch/src/routes/indexes/settings_analytics.rs @@ -39,6 +39,7 @@ pub struct SettingsAnalytics { pub non_separator_tokens: NonSeparatorTokensAnalytics, pub facet_search: FacetSearchAnalytics, pub prefix_search: PrefixSearchAnalytics, + pub execute_after_update: ExecuteAfterUpdateAnalytics, } impl Aggregate for SettingsAnalytics { @@ -194,6 +195,9 @@ impl Aggregate for SettingsAnalytics { set: new.facet_search.set | self.facet_search.set, value: new.facet_search.value.or(self.facet_search.value), }, + execute_after_update: ExecuteAfterUpdateAnalytics { + set: new.execute_after_update.set | self.execute_after_update.set, + }, prefix_search: PrefixSearchAnalytics { set: new.prefix_search.set | self.prefix_search.set, value: new.prefix_search.value.or(self.prefix_search.value), @@ -659,6 +663,21 @@ impl FacetSearchAnalytics { } } +#[derive(Serialize, Default)] +pub struct ExecuteAfterUpdateAnalytics { + pub set: bool, +} + +impl ExecuteAfterUpdateAnalytics { + pub fn new(distinct: Option<&String>) -> Self { + Self { set: distinct.is_some() } + } + + pub fn into_settings(self) -> SettingsAnalytics { + SettingsAnalytics { execute_after_update: self, ..Default::default() } + } +} + #[derive(Serialize, Default)] pub struct PrefixSearchAnalytics { pub set: bool, diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index 1f006b316..f83eb1382 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -76,6 +76,7 @@ pub mod main_key { pub const SEARCH_CUTOFF: &str = "search_cutoff"; pub const LOCALIZED_ATTRIBUTES_RULES: &str = "localized_attributes_rules"; pub const FACET_SEARCH: &str = "facet_search"; + pub const EXECUTE_AFTER_UPDATE: &str = "execute-after-update"; pub const PREFIX_SEARCH: &str = "prefix_search"; pub const DOCUMENTS_STATS: &str = "documents_stats"; } @@ -1623,6 +1624,22 @@ impl Index { self.main.remap_key_type::().delete(txn, main_key::FACET_SEARCH) } + pub fn execute_after_update<'t>(&self, txn: &'t RoTxn<'_>) -> heed::Result> { + self.main.remap_types::().get(txn, main_key::EXECUTE_AFTER_UPDATE) + } + + pub(crate) fn put_execute_after_update( + &self, + txn: &mut RwTxn<'_>, + val: &str, + ) -> heed::Result<()> { + self.main.remap_types::().put(txn, main_key::EXECUTE_AFTER_UPDATE, &val) + } + + pub(crate) fn delete_execute_after_update(&self, txn: &mut RwTxn<'_>) -> heed::Result { + self.main.remap_key_type::().delete(txn, main_key::EXECUTE_AFTER_UPDATE) + } + pub fn localized_attributes_rules( &self, rtxn: &RoTxn<'_>, diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index 317be1968..a0cae324d 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -183,6 +183,7 @@ pub struct Settings<'a, 't, 'i> { localized_attributes_rules: Setting>, prefix_search: Setting, facet_search: Setting, + execute_after_update: Setting, } impl<'a, 't, 'i> Settings<'a, 't, 'i> { @@ -220,6 +221,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { localized_attributes_rules: Setting::NotSet, prefix_search: Setting::NotSet, facet_search: Setting::NotSet, + execute_after_update: Setting::NotSet, indexer_config, } } @@ -442,6 +444,14 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.facet_search = Setting::Reset; } + pub fn set_execute_after_update(&mut self, value: String) { + self.execute_after_update = Setting::Set(value); + } + + pub fn reset_execute_after_update(&mut self) { + self.execute_after_update = Setting::Reset; + } + #[tracing::instrument( level = "trace" skip(self, progress_callback, should_abort, settings_diff), @@ -994,6 +1004,18 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Ok(changed) } + fn update_execute_after_update(&mut self) -> Result<()> { + match self.execute_after_update.as_ref() { + Setting::Set(new) => { + self.index.put_execute_after_update(self.wtxn, &new).map_err(Into::into) + } + Setting::Reset => { + self.index.delete_execute_after_update(self.wtxn).map(drop).map_err(Into::into) + } + Setting::NotSet => Ok(()), + } + } + fn update_embedding_configs(&mut self) -> Result> { match std::mem::take(&mut self.embedder_settings) { Setting::Set(configs) => self.update_embedding_configs_set(configs), @@ -1245,6 +1267,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.update_proximity_precision()?; self.update_prefix_search()?; self.update_facet_search()?; + self.update_execute_after_update()?; self.update_localized_attributes_rules()?; let embedding_config_updates = self.update_embedding_configs()?; diff --git a/crates/milli/src/update/test_settings.rs b/crates/milli/src/update/test_settings.rs index 00be0476a..f98166d8e 100644 --- a/crates/milli/src/update/test_settings.rs +++ b/crates/milli/src/update/test_settings.rs @@ -896,6 +896,7 @@ fn test_correct_settings_init() { localized_attributes_rules, prefix_search, facet_search, + execute_after_update, } = settings; assert!(matches!(searchable_fields, Setting::NotSet)); assert!(matches!(displayed_fields, Setting::NotSet)); @@ -923,6 +924,7 @@ fn test_correct_settings_init() { assert!(matches!(localized_attributes_rules, Setting::NotSet)); assert!(matches!(prefix_search, Setting::NotSet)); assert!(matches!(facet_search, Setting::NotSet)); + assert!(matches!(execute_after_update, Setting::NotSet)); }) .unwrap(); }