diff --git a/crates/dump/src/lib.rs b/crates/dump/src/lib.rs index 025e2e441..cacd94bb4 100644 --- a/crates/dump/src/lib.rs +++ b/crates/dump/src/lib.rs @@ -317,6 +317,7 @@ pub(crate) mod test { FilterableAttributesRule::Field(S("race")), FilterableAttributesRule::Field(S("age")), ]), + foreign_keys: Setting::NotSet, sortable_attributes: Setting::Set(btreeset! { S("age") }), ranking_rules: Setting::NotSet, stop_words: Setting::NotSet, diff --git a/crates/dump/src/reader/compat/v5_to_v6.rs b/crates/dump/src/reader/compat/v5_to_v6.rs index 01068a30a..1d01f4ffe 100644 --- a/crates/dump/src/reader/compat/v5_to_v6.rs +++ b/crates/dump/src/reader/compat/v5_to_v6.rs @@ -349,6 +349,7 @@ impl From> for v6::Settings { v5::settings::Setting::Reset => v6::Setting::Reset, v5::settings::Setting::NotSet => v6::Setting::NotSet, }, + foreign_keys: v6::Setting::NotSet, sortable_attributes: settings.sortable_attributes.into(), ranking_rules: { match settings.ranking_rules { diff --git a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap index 568635d05..5a0cb64e6 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, foreign_keys: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, foreign_keys: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: enqueued [0,] diff --git a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap index c9c8869a1..27a3291d1 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, foreign_keys: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, foreign_keys: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), indexing_fragments: NotSet, search_fragments: NotSet, request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, vector_store: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: enqueued [] diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 408359a01..7fe3c4f81 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -327,6 +327,7 @@ InvalidSettingsFacetSearch , InvalidRequest , BAD_REQU InvalidSettingsPrefixSearch , InvalidRequest , BAD_REQUEST ; InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ; InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ; +InvalidSettingsForeignKeys , InvalidRequest , BAD_REQUEST ; InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ; InvalidSettingsSearchCutoffMs , InvalidRequest , BAD_REQUEST ; InvalidSettingsEmbedders , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch-types/src/settings.rs b/crates/meilisearch-types/src/settings.rs index 5436d2fc6..72035c01a 100644 --- a/crates/meilisearch-types/src/settings.rs +++ b/crates/meilisearch-types/src/settings.rs @@ -15,7 +15,10 @@ pub use milli::update::ChatSettings; use milli::update::Setting; use milli::vector::db::IndexEmbeddingConfig; use milli::vector::VectorStoreBackend; -use milli::{Criterion, CriterionError, FilterableAttributesRule, Index, DEFAULT_VALUES_PER_FACET}; +use milli::{ + Criterion, CriterionError, FilterableAttributesRule, ForeignKey, Index, + DEFAULT_VALUES_PER_FACET, +}; use serde::{Deserialize, Serialize, Serializer}; use utoipa::ToSchema; @@ -221,6 +224,12 @@ pub struct Settings { #[schema(value_type = Option>, example = json!(["release_date"]))] pub sortable_attributes: Setting>, + /// Foreign keys to use for cross-index filtering search. + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option>, example = json!([{"foreignIndexUid": "products", "fieldName": "productId"}]))] + pub foreign_keys: Setting>, + /// List of ranking rules sorted by order of importance. The order is customizable. /// [A list of ordered built-in ranking rules](https://www.meilisearch.com/docs/learn/relevancy/relevancy). #[serde(default, skip_serializing_if = "Setting::is_not_set")] @@ -376,6 +385,7 @@ impl Settings { displayed_attributes: Setting::Reset.into(), searchable_attributes: Setting::Reset.into(), filterable_attributes: Setting::Reset, + foreign_keys: Setting::Reset, sortable_attributes: Setting::Reset, ranking_rules: Setting::Reset, stop_words: Setting::Reset, @@ -404,6 +414,7 @@ impl Settings { displayed_attributes, searchable_attributes, filterable_attributes, + foreign_keys, sortable_attributes, ranking_rules, stop_words, @@ -431,6 +442,7 @@ impl Settings { searchable_attributes, filterable_attributes, sortable_attributes, + foreign_keys, ranking_rules, stop_words, non_separator_tokens, @@ -482,6 +494,7 @@ impl Settings { displayed_attributes: displayed_attributes.into(), searchable_attributes: searchable_attributes.into(), filterable_attributes: self.filterable_attributes, + foreign_keys: self.foreign_keys, sortable_attributes: self.sortable_attributes, ranking_rules: self.ranking_rules, stop_words: self.stop_words, @@ -543,6 +556,7 @@ impl Settings { .sortable_attributes .clone() .or(self.sortable_attributes.clone()), + foreign_keys: other.foreign_keys.clone().or(self.foreign_keys.clone()), ranking_rules: other.ranking_rules.clone().or(self.ranking_rules.clone()), stop_words: other.stop_words.clone().or(self.stop_words.clone()), non_separator_tokens: other @@ -604,6 +618,7 @@ pub fn apply_settings_to_builder( searchable_attributes, filterable_attributes, sortable_attributes, + foreign_keys, ranking_rules, stop_words, non_separator_tokens, @@ -651,6 +666,12 @@ pub fn apply_settings_to_builder( Setting::NotSet => (), } + match foreign_keys { + Setting::Set(ref keys) => builder.set_foreign_keys(keys.clone().into_iter().collect()), + Setting::Reset => builder.reset_foreign_keys(), + Setting::NotSet => (), + } + match ranking_rules { Setting::Set(ref criteria) => { builder.set_criteria(criteria.iter().map(|c| c.clone().into()).collect()) @@ -868,6 +889,8 @@ pub fn settings( let sortable_attributes = index.sortable_fields(rtxn)?.into_iter().collect(); + let foreign_keys = index.foreign_keys(rtxn)?.into_iter().collect(); + let criteria = index.criteria(rtxn)?; let stop_words = index @@ -965,6 +988,7 @@ pub fn settings( .into(), filterable_attributes: Setting::Set(filterable_attributes), sortable_attributes: Setting::Set(sortable_attributes), + foreign_keys: Setting::Set(foreign_keys), ranking_rules: Setting::Set(criteria.iter().map(|c| c.clone().into()).collect()), stop_words: Setting::Set(stop_words), non_separator_tokens: Setting::Set(non_separator_tokens), @@ -1207,6 +1231,7 @@ pub(crate) mod test { searchable_attributes: Setting::Set(vec![String::from("hello")]).into(), filterable_attributes: Setting::NotSet, sortable_attributes: Setting::NotSet, + foreign_keys: Setting::NotSet, ranking_rules: Setting::NotSet, stop_words: Setting::NotSet, non_separator_tokens: Setting::NotSet, @@ -1240,6 +1265,7 @@ pub(crate) mod test { .into(), filterable_attributes: Setting::NotSet, sortable_attributes: Setting::NotSet, + foreign_keys: Setting::NotSet, ranking_rules: Setting::NotSet, stop_words: Setting::NotSet, non_separator_tokens: Setting::NotSet, diff --git a/crates/meilisearch/src/routes/indexes/settings.rs b/crates/meilisearch/src/routes/indexes/settings.rs index cc825f893..ca735bd5e 100644 --- a/crates/meilisearch/src/routes/indexes/settings.rs +++ b/crates/meilisearch/src/routes/indexes/settings.rs @@ -531,6 +531,17 @@ make_setting_routes!( camelcase_attr: "vectorStore", analytics: VectorStoreAnalytics }, + { + route: "/foreign-keys", + update_verb: put, + value_type: Vec, + err_type: meilisearch_types::deserr::DeserrJsonError< + meilisearch_types::error::deserr_codes::InvalidSettingsForeignKeys, + >, + attr: foreign_keys, + camelcase_attr: "foreignKeys", + analytics: ForeignKeysAnalytics + }, ); #[utoipa::path( @@ -595,6 +606,7 @@ pub async fn update_all( filterable_attributes: FilterableAttributesAnalytics::new( new_settings.filterable_attributes.as_ref().set(), ), + foreign_keys: ForeignKeysAnalytics::new(new_settings.foreign_keys.as_ref().set()), distinct_attribute: DistinctAttributeAnalytics::new( new_settings.distinct_attribute.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..149e05a31 100644 --- a/crates/meilisearch/src/routes/indexes/settings_analytics.rs +++ b/crates/meilisearch/src/routes/indexes/settings_analytics.rs @@ -9,7 +9,7 @@ use meilisearch_types::facet_values_sort::FacetValuesSort; use meilisearch_types::locales::{Locale, LocalizedAttributesRuleView}; use meilisearch_types::milli::update::Setting; use meilisearch_types::milli::vector::VectorStoreBackend; -use meilisearch_types::milli::FilterableAttributesRule; +use meilisearch_types::milli::{FilterableAttributesRule, ForeignKey}; use meilisearch_types::settings::{ ChatSettings, FacetingSettings, PaginationSettings, PrefixSearchSettings, ProximityPrecisionView, RankingRuleView, SettingEmbeddingSettings, TypoSettings, @@ -25,6 +25,7 @@ pub struct SettingsAnalytics { pub displayed_attributes: DisplayedAttributesAnalytics, pub sortable_attributes: SortableAttributesAnalytics, pub filterable_attributes: FilterableAttributesAnalytics, + pub foreign_keys: ForeignKeysAnalytics, pub distinct_attribute: DistinctAttributeAnalytics, pub proximity_precision: ProximityPrecisionAnalytics, pub typo_tolerance: TypoToleranceAnalytics, @@ -98,6 +99,10 @@ impl Aggregate for SettingsAnalytics { .has_patterns .or(self.filterable_attributes.has_patterns), }, + foreign_keys: ForeignKeysAnalytics { + set: new.foreign_keys.set | self.foreign_keys.set, + total: new.foreign_keys.total.or(self.foreign_keys.total), + }, distinct_attribute: DistinctAttributeAnalytics { set: self.distinct_attribute.set | new.distinct_attribute.set, }, @@ -362,6 +367,22 @@ impl FilterableAttributesAnalytics { } } +#[derive(Serialize, Default)] +pub struct ForeignKeysAnalytics { + pub set: bool, + pub total: Option, +} + +impl ForeignKeysAnalytics { + pub fn new(settings: Option<&Vec>) -> Self { + Self { set: settings.is_some(), total: settings.as_ref().map(|s| s.len()) } + } + + pub fn into_settings(self) -> SettingsAnalytics { + SettingsAnalytics { foreign_keys: self, ..Default::default() } + } +} + #[derive(Serialize, Default)] pub struct DistinctAttributeAnalytics { pub set: bool, diff --git a/crates/meilisearch/tests/dumps/mod.rs b/crates/meilisearch/tests/dumps/mod.rs index a1db8efcd..19056e213 100644 --- a/crates/meilisearch/tests/dumps/mod.rs +++ b/crates/meilisearch/tests/dumps/mod.rs @@ -237,6 +237,7 @@ async fn import_dump_v1_movie_with_settings() { "sortableAttributes": [ "genres" ], + "foreignKeys": [], "rankingRules": [ "typo", "words", @@ -411,6 +412,7 @@ async fn import_dump_v1_rubygems_with_settings() { "sortableAttributes": [ "version" ], + "foreignKeys": [], "rankingRules": [ "typo", "words", @@ -740,6 +742,7 @@ async fn import_dump_v2_movie_with_settings() { "genres" ], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "words", "typo", @@ -911,6 +914,7 @@ async fn import_dump_v2_rubygems_with_settings() { "version" ], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "typo", "words", @@ -1240,6 +1244,7 @@ async fn import_dump_v3_movie_with_settings() { "genres" ], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "words", "typo", @@ -1411,6 +1416,7 @@ async fn import_dump_v3_rubygems_with_settings() { "version" ], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "typo", "words", @@ -1740,6 +1746,7 @@ async fn import_dump_v4_movie_with_settings() { "genres" ], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "words", "typo", @@ -1911,6 +1918,7 @@ async fn import_dump_v4_rubygems_with_settings() { "version" ], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "typo", "words", diff --git a/crates/meilisearch/tests/settings/get_settings.rs b/crates/meilisearch/tests/settings/get_settings.rs index ae0c37048..0f0302bbc 100644 --- a/crates/meilisearch/tests/settings/get_settings.rs +++ b/crates/meilisearch/tests/settings/get_settings.rs @@ -318,6 +318,7 @@ async fn secrets_are_hidden_in_settings() { ], "filterableAttributes": [], "sortableAttributes": [], + "foreignKeys": [], "rankingRules": [ "words", "typo", diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index ba43cae38..f8f9aa265 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -53,6 +53,7 @@ pub mod main_key { pub const HIDDEN_FACETED_FIELDS_KEY: &str = "hidden-faceted-fields"; pub const FILTERABLE_FIELDS_KEY: &str = "filterable-fields"; pub const SORTABLE_FIELDS_KEY: &str = "sortable-fields"; + pub const FOREIGN_KEYS_KEY: &str = "foreign-keys"; pub const FIELD_DISTRIBUTION_KEY: &str = "fields-distribution"; pub const FIELDS_IDS_MAP_KEY: &str = "fields-ids-map"; pub const FIELDIDS_WEIGHTS_MAP_KEY: &str = "fieldids-weights-map"; diff --git a/crates/milli/src/lib.rs b/crates/milli/src/lib.rs index 6e4d833f7..7dcf05217 100644 --- a/crates/milli/src/lib.rs +++ b/crates/milli/src/lib.rs @@ -19,6 +19,7 @@ mod external_documents_ids; pub mod facet; mod fields_ids_map; mod filterable_attributes_rules; +mod foreign_key; pub mod heed_codec; pub mod index; mod localized_attributes_rules; @@ -71,6 +72,7 @@ pub use self::filterable_attributes_rules::{ FilterFeatures, FilterableAttributesFeatures, FilterableAttributesPatterns, FilterableAttributesRule, }; +pub use self::foreign_key::ForeignKey; pub use self::heed_codec::{ BEU16StrCodec, BEU32StrCodec, BoRoaringBitmapCodec, BoRoaringBitmapLenCodec, CboRoaringBitmapCodec, CboRoaringBitmapLenCodec, FieldIdWordCountCodec, ObkvCodec, diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index 96fee7809..d69c9002d 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -45,7 +45,8 @@ use crate::vector::{ VectorStoreBackend, }; use crate::{ - ChannelCongestion, FieldId, FilterableAttributesRule, Index, LocalizedAttributesRule, Result, + ChannelCongestion, FieldId, FilterableAttributesRule, ForeignKey, Index, + LocalizedAttributesRule, Result, }; #[derive(Debug, Clone, PartialEq, Eq, Copy)] @@ -176,6 +177,7 @@ pub struct Settings<'a, 't, 'i> { displayed_fields: Setting>, filterable_fields: Setting>, sortable_fields: Setting>, + foreign_keys: Setting>, criteria: Setting>, stop_words: Setting>, non_separator_tokens: Setting>, @@ -217,6 +219,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { displayed_fields: Setting::NotSet, filterable_fields: Setting::NotSet, sortable_fields: Setting::NotSet, + foreign_keys: Setting::NotSet, criteria: Setting::NotSet, stop_words: Setting::NotSet, non_separator_tokens: Setting::NotSet, @@ -278,6 +281,14 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.sortable_fields = Setting::Reset; } + pub fn set_foreign_keys(&mut self, keys: Vec) { + self.foreign_keys = Setting::Set(keys); + } + + pub fn reset_foreign_keys(&mut self) { + self.foreign_keys = Setting::Reset; + } + pub fn reset_criteria(&mut self) { self.criteria = Setting::Reset; } @@ -822,6 +833,19 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Ok(()) } + fn update_foreign_keys(&mut self) -> Result<()> { + match self.foreign_keys { + Setting::Set(ref keys) => { + self.index.put_foreign_keys(self.wtxn, keys)?; + } + Setting::Reset => { + self.index.delete_foreign_keys(self.wtxn)?; + } + Setting::NotSet => (), + } + Ok(()) + } + fn update_criteria(&mut self) -> Result<()> { match &self.criteria { Setting::Set(criteria) => { @@ -1455,6 +1479,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.update_sort_facet_values_by()?; self.update_pagination_max_total_hits()?; self.update_search_cutoff()?; + self.update_foreign_keys()?; // could trigger re-indexing self.update_filterable()?; @@ -1593,6 +1618,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { displayed_fields: Setting::NotSet, filterable_fields: Setting::NotSet, sortable_fields: Setting::NotSet, + foreign_keys: Setting::NotSet, criteria: Setting::NotSet, stop_words: Setting::NotSet, non_separator_tokens: Setting::NotSet, diff --git a/crates/milli/src/update/test_settings.rs b/crates/milli/src/update/test_settings.rs index 9e4579667..c92685e7f 100644 --- a/crates/milli/src/update/test_settings.rs +++ b/crates/milli/src/update/test_settings.rs @@ -874,6 +874,7 @@ fn test_correct_settings_init() { displayed_fields, filterable_fields, sortable_fields, + foreign_keys, criteria, stop_words, non_separator_tokens, @@ -904,6 +905,7 @@ fn test_correct_settings_init() { assert!(matches!(displayed_fields, Setting::NotSet)); assert!(matches!(filterable_fields, Setting::NotSet)); assert!(matches!(sortable_fields, Setting::NotSet)); + assert!(matches!(foreign_keys, Setting::NotSet)); assert!(matches!(criteria, Setting::NotSet)); assert!(matches!(stop_words, Setting::NotSet)); assert!(matches!(non_separator_tokens, Setting::NotSet));