diff --git a/crates/dump/src/lib.rs b/crates/dump/src/lib.rs index 5f2b63fae..34c85eeea 100644 --- a/crates/dump/src/lib.rs +++ b/crates/dump/src/lib.rs @@ -338,6 +338,7 @@ pub(crate) mod test { prefix_search: Setting::NotSet, chat: Setting::NotSet, vector_store: 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 8261746e0..7543f12a1 100644 --- a/crates/dump/src/reader/compat/v5_to_v6.rs +++ b/crates/dump/src/reader/compat/v5_to_v6.rs @@ -422,6 +422,7 @@ impl From> for v6::Settings { prefix_search: v6::Setting::NotSet, chat: v6::Setting::NotSet, vector_store: 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 902454b0b..8db4b2caa 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -320,6 +320,7 @@ InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQU 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 f73a66c4b..a43d8af73 100644 --- a/crates/meilisearch-types/src/settings.rs +++ b/crates/meilisearch-types/src/settings.rs @@ -326,6 +326,12 @@ pub struct Settings { #[schema(value_type = Option)] pub vector_store: 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, @@ -393,6 +399,7 @@ impl Settings { prefix_search: Setting::Reset, chat: Setting::Reset, vector_store: Setting::Reset, + execute_after_update: Setting::Reset, _kind: PhantomData, } } @@ -421,6 +428,7 @@ impl Settings { prefix_search, chat, vector_store, + execute_after_update, _kind, } = self; @@ -447,6 +455,7 @@ impl Settings { prefix_search, vector_store, chat, + execute_after_update, _kind: PhantomData, } } @@ -499,6 +508,7 @@ impl Settings { prefix_search: self.prefix_search, chat: self.chat, vector_store: self.vector_store, + execute_after_update: self.execute_after_update, _kind: PhantomData, } } @@ -580,6 +590,10 @@ impl Settings { prefix_search: other.prefix_search.or(self.prefix_search), chat: other.chat.clone().or(self.chat.clone()), vector_store: other.vector_store.or(self.vector_store), + execute_after_update: other + .execute_after_update + .clone() + .or(self.execute_after_update.clone()), _kind: PhantomData, } } @@ -620,6 +634,7 @@ pub fn apply_settings_to_builder( prefix_search, chat, vector_store, + execute_after_update, _kind, } = settings; @@ -843,6 +858,14 @@ pub fn apply_settings_to_builder( Setting::Reset => builder.reset_vector_store(), 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 { @@ -942,13 +965,13 @@ pub fn settings( .collect(); let vector_store = index.get_vector_store(rtxn)?; - 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 chat = index.chat_config(rtxn).map(ChatSettings::from)?; + let execute_after_update = index.execute_after_update(rtxn)?; let mut settings = Settings { displayed_attributes: match displayed_attributes { @@ -993,6 +1016,10 @@ pub fn settings( Some(vector_store) => Setting::Set(vector_store), None => Setting::Reset, }, + execute_after_update: match execute_after_update { + Some(function) => Setting::Set(function.to_string()), + None => Setting::NotSet, + }, _kind: PhantomData, }; @@ -1223,6 +1250,7 @@ pub(crate) mod test { prefix_search: Setting::NotSet, chat: Setting::NotSet, vector_store: Setting::NotSet, + execute_after_update: Setting::NotSet, _kind: PhantomData::, }; @@ -1256,7 +1284,7 @@ pub(crate) mod test { prefix_search: Setting::NotSet, chat: Setting::NotSet, vector_store: 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 cc825f893..171395dbd 100644 --- a/crates/meilisearch/src/routes/indexes/settings.rs +++ b/crates/meilisearch/src/routes/indexes/settings.rs @@ -498,6 +498,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, @@ -619,6 +630,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()), chat: ChatAnalytics::new(new_settings.chat.as_ref().set()), vector_store: VectorStoreAnalytics::new(new_settings.vector_store.as_ref().set()), diff --git a/crates/meilisearch/src/routes/indexes/settings_analytics.rs b/crates/meilisearch/src/routes/indexes/settings_analytics.rs index cd573099f..0f5f2160f 100644 --- a/crates/meilisearch/src/routes/indexes/settings_analytics.rs +++ b/crates/meilisearch/src/routes/indexes/settings_analytics.rs @@ -42,6 +42,7 @@ pub struct SettingsAnalytics { pub prefix_search: PrefixSearchAnalytics, pub chat: ChatAnalytics, pub vector_store: VectorStoreAnalytics, + pub execute_after_update: ExecuteAfterUpdateAnalytics, } impl Aggregate for SettingsAnalytics { @@ -197,6 +198,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), @@ -669,6 +673,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 d5f5a45dc..bd4d07e04 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -84,6 +84,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"; pub const DISABLED_TYPOS_TERMS: &str = "disabled_typos_terms"; @@ -1761,6 +1762,22 @@ impl Index { self.main.remap_key_type::().delete(txn, main_key::CHAT) } + 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 25982b18a..afa3a5a0e 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -202,6 +202,7 @@ pub struct Settings<'a, 't, 'i> { facet_search: Setting, chat: Setting, vector_store: Setting, + execute_after_update: Setting, } impl<'a, 't, 'i> Settings<'a, 't, 'i> { @@ -242,6 +243,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { facet_search: Setting::NotSet, chat: Setting::NotSet, vector_store: Setting::NotSet, + execute_after_update: Setting::NotSet, indexer_config, } } @@ -488,6 +490,14 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.vector_store = 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, embedder_stats), @@ -1059,6 +1069,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), @@ -1469,6 +1491,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()?; self.update_disabled_typos_terms()?; self.update_chat_config()?; diff --git a/crates/milli/src/update/test_settings.rs b/crates/milli/src/update/test_settings.rs index 9e4579667..0af5bcc9c 100644 --- a/crates/milli/src/update/test_settings.rs +++ b/crates/milli/src/update/test_settings.rs @@ -899,6 +899,7 @@ fn test_correct_settings_init() { disable_on_numbers, chat, vector_store, + execute_after_update, } = settings; assert!(matches!(searchable_fields, Setting::NotSet)); assert!(matches!(displayed_fields, Setting::NotSet)); @@ -929,6 +930,7 @@ fn test_correct_settings_init() { assert!(matches!(disable_on_numbers, Setting::NotSet)); assert!(matches!(chat, Setting::NotSet)); assert!(matches!(vector_store, Setting::NotSet)); + assert!(matches!(execute_after_update, Setting::NotSet)); }) .unwrap(); }