From e22da044e40fab0bc6744eb4056f6b68cc89db30 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 25 Aug 2025 15:22:34 +0100 Subject: [PATCH 01/32] embedded document --- etc/gen_atlas_search/src/main.rs | 33 ++++----- src/atlas_search.rs | 25 +++++++ src/atlas_search/gen.rs | 121 ++++++++++++++++++------------- 3 files changed, 112 insertions(+), 67 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 4a1346918..d7de83dff 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::format_ident; @@ -54,10 +56,9 @@ impl Operator { let init_expr = rust_type.bson_expr(&ident); if arg.optional.unwrap_or(false) { - let tvars = rust_type.variables(); setters.push(parse_quote! { #[allow(missing_docs)] - pub fn #ident<#tvars>(mut self, #ident: #type_) -> Self { + pub fn #ident(mut self, #ident: #type_) -> Self { self.stage.insert(#arg_name, #init_expr); self } @@ -136,7 +137,8 @@ impl Argument { [ArgumentType::Object] => ArgumentRustType::Document, [ArgumentType::SearchScore] => ArgumentRustType::Document, [ArgumentType::SearchPath] => ArgumentRustType::StringOrArray, - [ArgumentType::SearchOperator, ArgumentType::Array] => ArgumentRustType::Operator, + [ArgumentType::SearchOperator] => ArgumentRustType::Operator, + [ArgumentType::SearchOperator, ArgumentType::Array] => ArgumentRustType::OperatorIter, [ArgumentType::Int] => ArgumentRustType::I32, _ => panic!("Unexpected argument types: {:?}", self.type_), } @@ -150,6 +152,7 @@ enum ArgumentRustType { TokenOrder, MatchCriteria, Operator, + OperatorIter, I32, } @@ -161,26 +164,21 @@ impl ArgumentRustType { Self::StringOrArray => parse_quote! { impl StringOrArray }, Self::TokenOrder => parse_quote! { TokenOrder }, Self::MatchCriteria => parse_quote! { MatchCriteria }, - Self::Operator => parse_quote! { impl IntoIterator> }, + Self::Operator => parse_quote! { impl Into }, + Self::OperatorIter => parse_quote! { impl IntoIterator> }, Self::I32 => parse_quote! { i32 }, } } - fn variables(&self) -> TokenStream { - match self { - Self::Operator => parse_quote! { T }, - _ => parse_quote! {}, - } - } - fn bson_expr(&self, ident: &syn::Ident) -> syn::Expr { match self { Self::String => parse_quote! { #ident.as_ref() }, Self::StringOrArray => parse_quote! { #ident.to_bson() }, Self::TokenOrder | Self::MatchCriteria => parse_quote! { #ident.name() }, Self::Document | Self::I32 => parse_quote! { #ident }, - Self::Operator => { - parse_quote! { #ident.into_iter().map(Document::from).collect::>() } + Self::Operator => parse_quote! { #ident.into() }, + Self::OperatorIter => { + parse_quote! { #ident.into_iter().map(Into::into).collect::>() } } } } @@ -200,11 +198,10 @@ impl TokenStreamExt for TokenStream { fn main() { let mut operators = TokenStream::new(); - for path in [ - "yaml/search/autocomplete.yaml", - "yaml/search/text.yaml", - "yaml/search/compound.yaml", - ] { + for name in ["autocomplete", "compound", "embeddedDocument", "text"] { + let mut path = PathBuf::from("yaml/search"); + path.push(name); + path.set_extension("yaml"); let contents = std::fs::read_to_string(path).unwrap(); let parsed = serde_yaml::from_str::(&contents) .unwrap() diff --git a/src/atlas_search.rs b/src/atlas_search.rs index c82645190..2d3b159a9 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -281,5 +281,30 @@ async fn api_flow() { }, ]) .await; + let _ = coll + .aggregate(vec![ + AtlasSearch::embedded_document( + "items", + AtlasSearch::compound() + .must(AtlasSearch::text("items.name", "school")) + .should(AtlasSearch::text("items.name", "backpack")), + ) + .score(doc! { + "embedded": { + "aggregate": "mean" + } + }) + .into(), + doc! { "$limit": 5 }, + doc! { + "$project": { + "_id": 0, + "items.name": 1, + "items.tags": 1, + "score": { "$meta": "searchScore" }, + } + }, + ]) + .await; } } diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 31111c43b..8ecb6770c 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -36,44 +36,6 @@ impl AtlasSearch { } } #[allow(missing_docs)] -pub struct Text; -impl AtlasSearch { - /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. - If you omit an analyzer, the text operator uses the default standard analyzer. - */ - /// - ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). - pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> Self { - AtlasSearch { - name: "text", - stage: doc! { - "path" : path.to_bson(), "query" : query.to_bson(), - }, - _t: PhantomData, - } - } - #[allow(missing_docs)] - pub fn fuzzy(mut self, fuzzy: Document) -> Self { - self.stage.insert("fuzzy", fuzzy); - self - } - #[allow(missing_docs)] - pub fn match_criteria(mut self, match_criteria: MatchCriteria) -> Self { - self.stage.insert("matchCriteria", match_criteria.name()); - self - } - #[allow(missing_docs)] - pub fn synonyms(mut self, synonyms: impl AsRef) -> Self { - self.stage.insert("synonyms", synonyms.as_ref()); - self - } - #[allow(missing_docs)] - pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); - self - } -} -#[allow(missing_docs)] pub struct Compound; impl AtlasSearch { /**The compound operator combines two or more operators into a single query. @@ -90,34 +52,32 @@ impl AtlasSearch { } } #[allow(missing_docs)] - pub fn must(mut self, must: impl IntoIterator>) -> Self { - self.stage.insert( - "must", - must.into_iter().map(Document::from).collect::>(), - ); + pub fn must(mut self, must: impl IntoIterator>) -> Self { + self.stage + .insert("must", must.into_iter().map(Into::into).collect::>()); self } #[allow(missing_docs)] - pub fn must_not(mut self, must_not: impl IntoIterator>) -> Self { + pub fn must_not(mut self, must_not: impl IntoIterator>) -> Self { self.stage.insert( "mustNot", - must_not.into_iter().map(Document::from).collect::>(), + must_not.into_iter().map(Into::into).collect::>(), ); self } #[allow(missing_docs)] - pub fn should(mut self, should: impl IntoIterator>) -> Self { + pub fn should(mut self, should: impl IntoIterator>) -> Self { self.stage.insert( "should", - should.into_iter().map(Document::from).collect::>(), + should.into_iter().map(Into::into).collect::>(), ); self } #[allow(missing_docs)] - pub fn filter(mut self, filter: impl IntoIterator>) -> Self { + pub fn filter(mut self, filter: impl IntoIterator>) -> Self { self.stage.insert( "filter", - filter.into_iter().map(Document::from).collect::>(), + filter.into_iter().map(Into::into).collect::>(), ); self } @@ -133,3 +93,66 @@ impl AtlasSearch { self } } +#[allow(missing_docs)] +pub struct EmbeddedDocument; +impl AtlasSearch { + /**The embeddedDocument operator is similar to $elemMatch operator. + It constrains multiple query predicates to be satisfied from a single + element of an array of embedded documents. embeddedDocument can be used only + for queries over fields of the embeddedDocuments + */ + /// + ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). + pub fn embedded_document(path: impl StringOrArray, operator: impl Into) -> Self { + AtlasSearch { + name: "embeddedDocument", + stage: doc! { + "path" : path.to_bson(), "operator" : operator.into(), + }, + _t: PhantomData, + } + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] +pub struct Text; +impl AtlasSearch { + /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. + If you omit an analyzer, the text operator uses the default standard analyzer. + */ + /// + ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). + pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> Self { + AtlasSearch { + name: "text", + stage: doc! { + "path" : path.to_bson(), "query" : query.to_bson(), + }, + _t: PhantomData, + } + } + #[allow(missing_docs)] + pub fn fuzzy(mut self, fuzzy: Document) -> Self { + self.stage.insert("fuzzy", fuzzy); + self + } + #[allow(missing_docs)] + pub fn match_criteria(mut self, match_criteria: MatchCriteria) -> Self { + self.stage.insert("matchCriteria", match_criteria.name()); + self + } + #[allow(missing_docs)] + pub fn synonyms(mut self, synonyms: impl AsRef) -> Self { + self.stage.insert("synonyms", synonyms.as_ref()); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} From fc124be40b5850fbc90738ca5e4cedfd3e1121fa Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 25 Aug 2025 15:35:13 +0100 Subject: [PATCH 02/32] short --- etc/gen_atlas_search/src/main.rs | 35 ++++++++++++++++++++--- src/atlas_search.rs | 9 +++--- src/atlas_search/gen.rs | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 8 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index d7de83dff..eb92928af 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -39,12 +39,13 @@ impl Operator { self } - fn gen_helper(&self) -> TokenStream { + fn gen_helper(&self) -> Helper { let name_text = &self.name; let name_ident = format_ident!("{}", name_text.to_case(Case::Pascal)); let constr_ident = format_ident!("{}", name_text.to_case(Case::Snake)); let mut required_args = TokenStream::new(); + let mut required_arg_names = TokenStream::new(); let mut init_doc = TokenStream::new(); let mut setters = TokenStream::new(); @@ -65,6 +66,7 @@ impl Operator { }); } else { required_args.push(parse_quote! { #ident : #type_, }); + required_arg_names.push(parse_quote! { #ident, }); init_doc.push(parse_quote! { #arg_name : #init_expr, }); } } @@ -74,7 +76,7 @@ impl Operator { "For more details, see the [{name_text} operator reference]({}).", self.link ); - parse_quote! { + let toplevel = parse_quote! { #[allow(missing_docs)] pub struct #name_ident; @@ -91,10 +93,24 @@ impl Operator { } #setters } - } + }; + let short = parse_quote! { + #[doc = #desc] + #[doc = ""] + #[doc = #link] + pub fn #constr_ident(#required_args) -> AtlasSearch<#name_ident> { + AtlasSearch::#constr_ident(#required_arg_names) + } + }; + Helper { toplevel, short } } } +struct Helper { + toplevel: TokenStream, + short: TokenStream, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] struct Argument { @@ -198,6 +214,7 @@ impl TokenStreamExt for TokenStream { fn main() { let mut operators = TokenStream::new(); + let mut short = TokenStream::new(); for name in ["autocomplete", "compound", "embeddedDocument", "text"] { let mut path = PathBuf::from("yaml/search"); path.push(name); @@ -206,7 +223,9 @@ fn main() { let parsed = serde_yaml::from_str::(&contents) .unwrap() .clear_tests(); - operators.push(parsed.gen_helper()); + let helper = parsed.gen_helper(); + operators.push(helper.toplevel); + short.push(helper.short); } let file = parse_quote! { @@ -214,6 +233,14 @@ fn main() { use super::*; #operators + + /// Atlas Search constructor functions without the `AtlasSearch::` prefix; can be useful to + /// improve readability when constructing deeply nested searches. + pub mod short { + use super::*; + + #short + } }; let text = prettyplease::unparse(&file); println!("{text}"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 2d3b159a9..7758b36f1 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -281,13 +281,14 @@ async fn api_flow() { }, ]) .await; + use short::*; let _ = coll .aggregate(vec![ - AtlasSearch::embedded_document( + embedded_document( "items", - AtlasSearch::compound() - .must(AtlasSearch::text("items.name", "school")) - .should(AtlasSearch::text("items.name", "backpack")), + compound() + .must(text("items.name", "school")) + .should(text("items.name", "backpack")), ) .score(doc! { "embedded": { diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 8ecb6770c..622cb3770 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -156,3 +156,51 @@ impl AtlasSearch { self } } +/// Atlas Search constructor functions without the `AtlasSearch::` prefix; can be useful to +/// improve readability when constructing deeply nested searches. +pub mod short { + use super::*; + /**The autocomplete operator performs a search for a word or phrase that + contains a sequence of characters from an incomplete input string. The + fields that you intend to query with the autocomplete operator must be + indexed with the autocomplete data type in the collection's index definition. + */ + /// + ///For more details, see the [autocomplete operator reference](https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/). + pub fn autocomplete( + path: impl StringOrArray, + query: impl StringOrArray, + ) -> AtlasSearch { + AtlasSearch::autocomplete(path, query) + } + /**The compound operator combines two or more operators into a single query. + Each element of a compound query is called a clause, and each clause + consists of one or more sub-queries. + */ + /// + ///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). + pub fn compound() -> AtlasSearch { + AtlasSearch::compound() + } + /**The embeddedDocument operator is similar to $elemMatch operator. + It constrains multiple query predicates to be satisfied from a single + element of an array of embedded documents. embeddedDocument can be used only + for queries over fields of the embeddedDocuments + */ + /// + ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). + pub fn embedded_document( + path: impl StringOrArray, + operator: impl Into, + ) -> AtlasSearch { + AtlasSearch::embedded_document(path, operator) + } + /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. + If you omit an analyzer, the text operator uses the default standard analyzer. + */ + /// + ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). + pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { + AtlasSearch::text(path, query) + } +} From 8d49673ab557d785c8dddc31b25001fe2aff3989 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 25 Aug 2025 16:48:50 +0100 Subject: [PATCH 03/32] equals --- etc/gen_atlas_search/src/main.rs | 95 ++++++++++++++++++-------------- src/atlas_search.rs | 31 +++++++++++ src/atlas_search/gen.rs | 29 ++++++++++ 3 files changed, 113 insertions(+), 42 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index eb92928af..c8ede7f5e 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -51,7 +51,7 @@ impl Operator { for arg in &self.arguments { let ident = format_ident!("{}", arg.name.to_case(Case::Snake)); - let rust_type = arg.rust_type(); + let rust_type = arg.rust_type(&self.name); let type_ = rust_type.tokens(); let arg_name = &arg.name; let init_expr = rust_type.bson_expr(&ident); @@ -124,78 +124,83 @@ struct Argument { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] enum ArgumentType { - String, - Object, - SearchScore, - SearchPath, - SearchOperator, Array, + BinData, + Bool, + Date, Int, + Null, + Number, + Object, + ObjectId, + SearchOperator, + SearchPath, + SearchScore, + String, } -static QUERY: &str = "query"; -static TOKEN_ORDER: &str = "tokenOrder"; -static MATCH_CRITERIA: &str = "matchCriteria"; - impl Argument { - fn rust_type(&self) -> ArgumentRustType { - if self.name == QUERY { - return ArgumentRustType::StringOrArray; - } - if self.name == TOKEN_ORDER { - return ArgumentRustType::TokenOrder; - } - if self.name == MATCH_CRITERIA { - return ArgumentRustType::MatchCriteria; + fn rust_type(&self, operator: &str) -> ArgumentRustType { + match (operator, self.name.as_str()) { + ("autocomplete" | "text", "query") => return ArgumentRustType::StringOrArray, + ("autocomplete", "tokenOrder") => return ArgumentRustType::TokenOrder, + ("text", "matchCriteria") => return ArgumentRustType::MatchCriteria, + ("equals", "value") => return ArgumentRustType::IntoBson, + _ => (), } + use ArgumentType::*; match self.type_.as_slice() { - [ArgumentType::String] => ArgumentRustType::String, - [ArgumentType::Object] => ArgumentRustType::Document, - [ArgumentType::SearchScore] => ArgumentRustType::Document, - [ArgumentType::SearchPath] => ArgumentRustType::StringOrArray, - [ArgumentType::SearchOperator] => ArgumentRustType::Operator, - [ArgumentType::SearchOperator, ArgumentType::Array] => ArgumentRustType::OperatorIter, - [ArgumentType::Int] => ArgumentRustType::I32, + [String] => ArgumentRustType::String, + [Object] => ArgumentRustType::Document, + [SearchScore] => ArgumentRustType::Document, + [SearchPath] => ArgumentRustType::StringOrArray, + [SearchOperator] => ArgumentRustType::IntoDocument, + [SearchOperator, Array] => ArgumentRustType::IntoDocumentIter, + [Int] => ArgumentRustType::I32, _ => panic!("Unexpected argument types: {:?}", self.type_), } } } enum ArgumentRustType { - String, Document, + I32, + IntoBson, + IntoDocument, + IntoDocumentIter, + MatchCriteria, + String, StringOrArray, TokenOrder, - MatchCriteria, - Operator, - OperatorIter, - I32, } impl ArgumentRustType { fn tokens(&self) -> syn::Type { match self { - Self::String => parse_quote! { impl AsRef }, Self::Document => parse_quote! { Document }, + Self::I32 => parse_quote! { i32 }, + Self::IntoBson => parse_quote! { impl Into }, + Self::IntoDocument => parse_quote! { impl Into }, + Self::IntoDocumentIter => { + parse_quote! { impl IntoIterator> } + } + Self::MatchCriteria => parse_quote! { MatchCriteria }, + Self::String => parse_quote! { impl AsRef }, Self::StringOrArray => parse_quote! { impl StringOrArray }, Self::TokenOrder => parse_quote! { TokenOrder }, - Self::MatchCriteria => parse_quote! { MatchCriteria }, - Self::Operator => parse_quote! { impl Into }, - Self::OperatorIter => parse_quote! { impl IntoIterator> }, - Self::I32 => parse_quote! { i32 }, } } fn bson_expr(&self, ident: &syn::Ident) -> syn::Expr { match self { - Self::String => parse_quote! { #ident.as_ref() }, - Self::StringOrArray => parse_quote! { #ident.to_bson() }, - Self::TokenOrder | Self::MatchCriteria => parse_quote! { #ident.name() }, Self::Document | Self::I32 => parse_quote! { #ident }, - Self::Operator => parse_quote! { #ident.into() }, - Self::OperatorIter => { + Self::IntoBson | Self::IntoDocument => parse_quote! { #ident.into() }, + Self::IntoDocumentIter => { parse_quote! { #ident.into_iter().map(Into::into).collect::>() } } + Self::String => parse_quote! { #ident.as_ref() }, + Self::StringOrArray => parse_quote! { #ident.to_bson() }, + Self::TokenOrder | Self::MatchCriteria => parse_quote! { #ident.name() }, } } } @@ -215,7 +220,13 @@ impl TokenStreamExt for TokenStream { fn main() { let mut operators = TokenStream::new(); let mut short = TokenStream::new(); - for name in ["autocomplete", "compound", "embeddedDocument", "text"] { + for name in [ + "autocomplete", + "compound", + "embeddedDocument", + "equals", + "text", + ] { let mut path = PathBuf::from("yaml/search"); path.push(name); path.set_extension("yaml"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 7758b36f1..fe6d56435 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -142,70 +142,86 @@ impl MatchCriteria { } } +mod private { + pub struct Sealed; +} + /// An Atlas Search operator parameter that can be either a string or array of strings. pub trait StringOrArray { #[allow(missing_docs)] fn to_bson(self) -> Bson; + #[allow(missing_docs)] + fn sealed(_: private::Sealed); } impl StringOrArray for &str { fn to_bson(self) -> Bson { Bson::String(self.to_owned()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for String { fn to_bson(self) -> Bson { Bson::String(self) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &String { fn to_bson(self) -> Bson { Bson::String(self.clone()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &[&str] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|&s| Bson::String(s.to_owned())).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &[&str; N] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|&s| Bson::String(s.to_owned())).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &[String] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|s| Bson::String(s.clone())).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &[String; N] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|s| Bson::String(s.clone())).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for [String; N] { fn to_bson(self) -> Bson { Bson::Array(self.into_iter().map(Bson::String).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &[&String] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|&s| Bson::String(s.clone())).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for &[&String; N] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|&s| Bson::String(s.clone())).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for Vec<&str> { @@ -216,18 +232,21 @@ impl StringOrArray for Vec<&str> { .collect(), ) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for Vec { fn to_bson(self) -> Bson { Bson::Array(self.into_iter().map(Bson::String).collect()) } + fn sealed(_: private::Sealed) {} } impl StringOrArray for Vec<&String> { fn to_bson(self) -> Bson { Bson::Array(self.into_iter().map(|s| Bson::String(s.clone())).collect()) } + fn sealed(_: private::Sealed) {} } #[tokio::test] @@ -307,5 +326,17 @@ async fn api_flow() { }, ]) .await; + let _ = coll + .aggregate(vec![ + AtlasSearch::equals("verified_user", true).into(), + doc! { + "$project": { + "name": 1, + "_id": 0, + "score": { "$meta": "searchScore" }, + } + }, + ]) + .await; } } diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 622cb3770..074cd35c3 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -119,6 +119,28 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Equals; +impl AtlasSearch { + /**The equals operator checks whether a field matches a value you specify. + * */ + /// + ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). + pub fn equals(path: impl StringOrArray, value: impl Into) -> Self { + AtlasSearch { + name: "equals", + stage: doc! { + "path" : path.to_bson(), "value" : value.into(), + }, + _t: PhantomData, + } + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; impl AtlasSearch { /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. @@ -195,6 +217,13 @@ pub mod short { ) -> AtlasSearch { AtlasSearch::embedded_document(path, operator) } + /**The equals operator checks whether a field matches a value you specify. + * */ + /// + ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). + pub fn equals(path: impl StringOrArray, value: impl Into) -> AtlasSearch { + AtlasSearch::equals(path, value) + } /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. */ From 61d1bbf86923d4087b9afe8e7365b9adef7c4671 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 25 Aug 2025 16:50:03 +0100 Subject: [PATCH 04/32] exists --- etc/gen_atlas_search/src/main.rs | 1 + src/atlas_search/gen.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index c8ede7f5e..2a4defdba 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -225,6 +225,7 @@ fn main() { "compound", "embeddedDocument", "equals", + "exists", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 074cd35c3..f31e1260b 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -141,6 +141,29 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Exists; +impl AtlasSearch { + /**The exists operator tests if a path to a specified indexed field name exists in a + * document. + * */ + /// + ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). + pub fn exists(path: impl StringOrArray) -> Self { + AtlasSearch { + name: "exists", + stage: doc! { + "path" : path.to_bson(), + }, + _t: PhantomData, + } + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; impl AtlasSearch { /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. @@ -224,6 +247,14 @@ pub mod short { pub fn equals(path: impl StringOrArray, value: impl Into) -> AtlasSearch { AtlasSearch::equals(path, value) } + /**The exists operator tests if a path to a specified indexed field name exists in a + * document. + * */ + /// + ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). + pub fn exists(path: impl StringOrArray) -> AtlasSearch { + AtlasSearch::exists(path) + } /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. */ From 453fa2d5a66610d5f798285e4aa8f8579ac91ea1 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 26 Aug 2025 12:16:49 +0100 Subject: [PATCH 05/32] facet and a lot of fixes --- etc/gen_atlas_search/src/main.rs | 29 ++-- src/atlas_search.rs | 275 +++++++++++++++++++++---------- src/atlas_search/gen.rs | 64 +++++-- 3 files changed, 259 insertions(+), 109 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 2a4defdba..2f19f8609 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -76,6 +76,10 @@ impl Operator { "For more details, see the [{name_text} operator reference]({}).", self.link ); + let meta: TokenStream = match self.name.as_str() { + "facet" => parse_quote! { true }, + _ => parse_quote! { false }, + }; let toplevel = parse_quote! { #[allow(missing_docs)] pub struct #name_ident; @@ -88,6 +92,7 @@ impl Operator { AtlasSearch { name: #name_text, stage: doc! { #init_doc }, + meta: #meta, _t: PhantomData, } } @@ -154,8 +159,8 @@ impl Argument { [Object] => ArgumentRustType::Document, [SearchScore] => ArgumentRustType::Document, [SearchPath] => ArgumentRustType::StringOrArray, - [SearchOperator] => ArgumentRustType::IntoDocument, - [SearchOperator, Array] => ArgumentRustType::IntoDocumentIter, + [SearchOperator] => ArgumentRustType::SearchOperator, + [SearchOperator, Array] => ArgumentRustType::SeachOperatorIter, [Int] => ArgumentRustType::I32, _ => panic!("Unexpected argument types: {:?}", self.type_), } @@ -166,9 +171,9 @@ enum ArgumentRustType { Document, I32, IntoBson, - IntoDocument, - IntoDocumentIter, MatchCriteria, + SearchOperator, + SeachOperatorIter, String, StringOrArray, TokenOrder, @@ -180,11 +185,11 @@ impl ArgumentRustType { Self::Document => parse_quote! { Document }, Self::I32 => parse_quote! { i32 }, Self::IntoBson => parse_quote! { impl Into }, - Self::IntoDocument => parse_quote! { impl Into }, - Self::IntoDocumentIter => { - parse_quote! { impl IntoIterator> } - } Self::MatchCriteria => parse_quote! { MatchCriteria }, + Self::SearchOperator => parse_quote! { impl SearchOperator }, + Self::SeachOperatorIter => { + parse_quote! { impl IntoIterator } + } Self::String => parse_quote! { impl AsRef }, Self::StringOrArray => parse_quote! { impl StringOrArray }, Self::TokenOrder => parse_quote! { TokenOrder }, @@ -194,9 +199,10 @@ impl ArgumentRustType { fn bson_expr(&self, ident: &syn::Ident) -> syn::Expr { match self { Self::Document | Self::I32 => parse_quote! { #ident }, - Self::IntoBson | Self::IntoDocument => parse_quote! { #ident.into() }, - Self::IntoDocumentIter => { - parse_quote! { #ident.into_iter().map(Into::into).collect::>() } + Self::IntoBson => parse_quote! { #ident.into() }, + Self::SearchOperator => parse_quote! { #ident.to_doc() }, + Self::SeachOperatorIter => { + parse_quote! { #ident.into_iter().map(|o| o.to_doc()).collect::>() } } Self::String => parse_quote! { #ident.as_ref() }, Self::StringOrArray => parse_quote! { #ident.to_bson() }, @@ -226,6 +232,7 @@ fn main() { "embeddedDocument", "equals", "exists", + "facet", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index fe6d56435..e728cf91f 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -35,13 +35,15 @@ use crate::bson::{doc, Bson, Document}; pub struct AtlasSearch { name: &'static str, stage: Document, + meta: bool, _t: PhantomData, } impl From> for Document { fn from(value: AtlasSearch) -> Self { + let key = if value.meta { "$searchMeta" } else { "$search" }; doc! { - "$search": { + key: { value.name: value.stage } } @@ -71,6 +73,7 @@ impl AtlasSearch { AtlasSearch { name: self.name, stage: self.stage, + meta: self.meta, _t: PhantomData, } } @@ -249,94 +252,194 @@ impl StringOrArray for Vec<&String> { fn sealed(_: private::Sealed) {} } -#[tokio::test] -async fn api_flow() { - // This is currently intended as a testbed for how the API works, not as an actual test. - return; +/// An Atlas Search operator parameter that is itself a search operator. +pub trait SearchOperator { + #[allow(missing_docs)] + fn to_doc(self) -> Document; + #[allow(missing_docs)] + fn sealed(_: private::Sealed) {} +} + +impl SearchOperator for AtlasSearch { + fn to_doc(self) -> Document { + doc! { self.name: self.stage } + } + fn sealed(_: private::Sealed) {} +} - #[allow(unreachable_code)] +impl SearchOperator for Document { + fn to_doc(self) -> Document { + self + } + fn sealed(_: private::Sealed) {} +} + +impl AtlasSearch { + /// Use the `$search` stage instead of the default `$searchMeta` stage. + pub fn search(mut self) -> Self { + self.meta = false; + self + } +} + +#[test] +fn api_flow() { + assert_eq!( + doc! { + "$search": { + "autocomplete": { + "query": "pre", + "path": "title", + "fuzzy": { + "maxEdits": 1, + "prefixLength": 1, + "maxExpansions": 256, + }, + } + } + }, + AtlasSearch::autocomplete("title", "pre") + .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) + .into() + ); + assert_eq!( + doc! { + "$search": { + "text": { + "path": "plot", + "query": "baseball", + } + } + }, + AtlasSearch::text("plot", "baseball").into() + ); + assert_eq!( + doc! { + "$search": { + "compound": { + "must": [{ + "text": { + "path": "description", + "query": "varieties", + } + }], + "should": [{ + "text": { + "path": "description", + "query": "Fuji", + } + }], + } + } + }, + AtlasSearch::compound() + .must(AtlasSearch::text("description", "varieties")) + .should(AtlasSearch::text("description", "Fuji")) + .into() + ); { - #[allow(unused_variables)] - let coll: crate::Collection = todo!(); - let _ = coll - .aggregate(vec![ - AtlasSearch::autocomplete("title", "pre") - .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) - .into(), - doc! { - "$limit": 10, - }, - doc! { - "$project": { - "_id": 0, - "title": 1, - } - }, - ]) - .await; - let _ = coll - .aggregate(vec![ - AtlasSearch::text("plot", "baseball").into(), - doc! { "$limit": 3 }, - doc! { - "$project": { - "_id": 0, - "title": 1, - "plot": 1, - } - }, - ]) - .await; - let _ = coll - .aggregate(vec![ - AtlasSearch::compound() - .must(AtlasSearch::text("description", "varieties")) - .should(AtlasSearch::text("description", "Fuji")) - .into(), - doc! { - "$project": { - "score": { "$meta": "searchScore" } - } - }, - ]) - .await; use short::*; - let _ = coll - .aggregate(vec![ - embedded_document( - "items", - compound() - .must(text("items.name", "school")) - .should(text("items.name", "backpack")), - ) - .score(doc! { - "embedded": { - "aggregate": "mean" + assert_eq!( + doc! { + "$search": { + "embeddedDocument": { + "path": "items", + "operator": { + "compound": { + "must": [{ + "text": { + "path": "items.tags", + "query": "school", + } + }], + "should": [{ + "text": { + "path": "items.name", + "query": "backpack", + } + }] + } + }, + "score": { + "embedded": { + "aggregate": "mean" + } + }, } - }) - .into(), - doc! { "$limit": 5 }, - doc! { - "$project": { - "_id": 0, - "items.name": 1, - "items.tags": 1, - "score": { "$meta": "searchScore" }, - } - }, - ]) - .await; - let _ = coll - .aggregate(vec![ - AtlasSearch::equals("verified_user", true).into(), - doc! { - "$project": { - "name": 1, - "_id": 0, - "score": { "$meta": "searchScore" }, - } - }, - ]) - .await; + } + }, + embedded_document( + "items", + compound() + .must(text("items.tags", "school")) + .should(text("items.name", "backpack")), + ) + .score(doc! { + "embedded": { + "aggregate": "mean" + } + }) + .into() + ); } + assert_eq!( + doc! { + "$search": { + "equals": { + "path": "verified_user", + "value": true, + } + } + }, + AtlasSearch::equals("verified_user", true).into() + ); + let gte_dt = crate::bson::DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); + let lte_dt = crate::bson::DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); + assert_eq!( + doc! { + "$searchMeta": { + "facet": { + "operator": { + "range": { + "path": "released", + "gte": gte_dt, + "lte": lte_dt, + } + }, + "facets": { + "directorsFacet": { + "type": "string", + "path": "directors", + "numBuckets": 7, + }, + "yearFacet": { + "type": "number", + "path": "year", + "boundaries": [2000, 2005, 2010, 2015] + }, + } + } + } + }, + AtlasSearch::facet(doc! { + "directorsFacet": { + "type": "string", + "path": "directors", + "numBuckets": 7, + }, + "yearFacet": { + "type": "number", + "path": "year", + "boundaries": [2000, 2005, 2010, 2015] + }, + }) + .operator(doc! { + "range": { + "path": "released", + "gte": gte_dt, + "lte": lte_dt, + } + }) + .into() + ); } diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index f31e1260b..a2d3b5ed4 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -16,6 +16,7 @@ impl AtlasSearch { stage: doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, + meta: false, _t: PhantomData, } } @@ -48,36 +49,39 @@ impl AtlasSearch { AtlasSearch { name: "compound", stage: doc! {}, + meta: false, _t: PhantomData, } } #[allow(missing_docs)] - pub fn must(mut self, must: impl IntoIterator>) -> Self { - self.stage - .insert("must", must.into_iter().map(Into::into).collect::>()); + pub fn must(mut self, must: impl IntoIterator) -> Self { + self.stage.insert( + "must", + must.into_iter().map(|o| o.to_doc()).collect::>(), + ); self } #[allow(missing_docs)] - pub fn must_not(mut self, must_not: impl IntoIterator>) -> Self { + pub fn must_not(mut self, must_not: impl IntoIterator) -> Self { self.stage.insert( "mustNot", - must_not.into_iter().map(Into::into).collect::>(), + must_not.into_iter().map(|o| o.to_doc()).collect::>(), ); self } #[allow(missing_docs)] - pub fn should(mut self, should: impl IntoIterator>) -> Self { + pub fn should(mut self, should: impl IntoIterator) -> Self { self.stage.insert( "should", - should.into_iter().map(Into::into).collect::>(), + should.into_iter().map(|o| o.to_doc()).collect::>(), ); self } #[allow(missing_docs)] - pub fn filter(mut self, filter: impl IntoIterator>) -> Self { + pub fn filter(mut self, filter: impl IntoIterator) -> Self { self.stage.insert( "filter", - filter.into_iter().map(Into::into).collect::>(), + filter.into_iter().map(|o| o.to_doc()).collect::>(), ); self } @@ -103,12 +107,13 @@ impl AtlasSearch { */ /// ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). - pub fn embedded_document(path: impl StringOrArray, operator: impl Into) -> Self { + pub fn embedded_document(path: impl StringOrArray, operator: impl SearchOperator) -> Self { AtlasSearch { name: "embeddedDocument", stage: doc! { - "path" : path.to_bson(), "operator" : operator.into(), + "path" : path.to_bson(), "operator" : operator.to_doc(), }, + meta: false, _t: PhantomData, } } @@ -131,6 +136,7 @@ impl AtlasSearch { stage: doc! { "path" : path.to_bson(), "value" : value.into(), }, + meta: false, _t: PhantomData, } } @@ -154,6 +160,7 @@ impl AtlasSearch { stage: doc! { "path" : path.to_bson(), }, + meta: false, _t: PhantomData, } } @@ -164,6 +171,30 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Facet; +impl AtlasSearch { + /**The facet collector groups results by values or ranges in the specified + faceted fields and returns the count for each of those groups. + */ + /// + ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). + pub fn facet(facets: Document) -> Self { + AtlasSearch { + name: "facet", + stage: doc! { + "facets" : facets, + }, + meta: true, + _t: PhantomData, + } + } + #[allow(missing_docs)] + pub fn operator(mut self, operator: impl SearchOperator) -> Self { + self.stage.insert("operator", operator.to_doc()); + self + } +} +#[allow(missing_docs)] pub struct Text; impl AtlasSearch { /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. @@ -177,6 +208,7 @@ impl AtlasSearch { stage: doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, + meta: false, _t: PhantomData, } } @@ -236,7 +268,7 @@ pub mod short { ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). pub fn embedded_document( path: impl StringOrArray, - operator: impl Into, + operator: impl SearchOperator, ) -> AtlasSearch { AtlasSearch::embedded_document(path, operator) } @@ -255,6 +287,14 @@ pub mod short { pub fn exists(path: impl StringOrArray) -> AtlasSearch { AtlasSearch::exists(path) } + /**The facet collector groups results by values or ranges in the specified + faceted fields and returns the count for each of those groups. + */ + /// + ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). + pub fn facet(facets: Document) -> AtlasSearch { + AtlasSearch::facet(facets) + } /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. */ From 2bf884c6a5aa2a361b133586cd89c9f2b089ce72 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 26 Aug 2025 14:17:31 +0100 Subject: [PATCH 06/32] less stutter --- etc/gen_atlas_search/src/main.rs | 52 ++---- src/atlas_search.rs | 178 +------------------ src/atlas_search/gen.rs | 290 ++++++++++++------------------- src/test.rs | 1 + src/test/atlas_search.rs | 163 +++++++++++++++++ 5 files changed, 299 insertions(+), 385 deletions(-) create mode 100644 src/test/atlas_search.rs diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 2f19f8609..499b59f27 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -39,7 +39,7 @@ impl Operator { self } - fn gen_helper(&self) -> Helper { + fn gen_helper(&self) -> TokenStream { let name_text = &self.name; let name_ident = format_ident!("{}", name_text.to_case(Case::Pascal)); let constr_ident = format_ident!("{}", name_text.to_case(Case::Snake)); @@ -80,40 +80,27 @@ impl Operator { "facet" => parse_quote! { true }, _ => parse_quote! { false }, }; - let toplevel = parse_quote! { + parse_quote! { #[allow(missing_docs)] pub struct #name_ident; - impl AtlasSearch<#name_ident> { - #[doc = #desc] - #[doc = ""] - #[doc = #link] - pub fn #constr_ident(#required_args) -> Self { - AtlasSearch { - name: #name_text, - stage: doc! { #init_doc }, - meta: #meta, - _t: PhantomData, - } - } - #setters - } - }; - let short = parse_quote! { #[doc = #desc] #[doc = ""] #[doc = #link] pub fn #constr_ident(#required_args) -> AtlasSearch<#name_ident> { - AtlasSearch::#constr_ident(#required_arg_names) + AtlasSearch { + name: #name_text, + stage: doc! { #init_doc }, + meta: #meta, + _t: PhantomData, + } } - }; - Helper { toplevel, short } - } -} -struct Helper { - toplevel: TokenStream, - short: TokenStream, + impl AtlasSearch<#name_ident> { + #setters + } + } + } } #[derive(Debug, Deserialize)] @@ -225,7 +212,6 @@ impl TokenStreamExt for TokenStream { fn main() { let mut operators = TokenStream::new(); - let mut short = TokenStream::new(); for name in [ "autocomplete", "compound", @@ -242,9 +228,7 @@ fn main() { let parsed = serde_yaml::from_str::(&contents) .unwrap() .clear_tests(); - let helper = parsed.gen_helper(); - operators.push(helper.toplevel); - short.push(helper.short); + operators.push(parsed.gen_helper()); } let file = parse_quote! { @@ -252,14 +236,6 @@ fn main() { use super::*; #operators - - /// Atlas Search constructor functions without the `AtlasSearch::` prefix; can be useful to - /// improve readability when constructing deeply nested searches. - pub mod short { - use super::*; - - #short - } }; let text = prettyplease::unparse(&file); println!("{text}"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index e728cf91f..ada5e0be0 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -14,10 +14,11 @@ use crate::bson::{doc, Bson, Document}; /// /// ```no_run /// # async fn wrapper() -> mongodb::error::Result<()> { -/// # use mongodb::{Collection, atlas_search::AtlasSearch, bson::{Document, doc}}; +/// # use mongodb::{Collection, bson::{Document, doc}}; /// # let collection: Collection = todo!(); +/// use mongodb::atlas_search; /// let cursor = collection.aggregate(vec![ -/// AtlasSearch::autocomplete("title", "pre") +/// atlas_search::autocomplete("title", "pre") /// .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) /// .into(), /// doc! { @@ -55,14 +56,15 @@ impl AtlasSearch { /// of different types in a single `Vec`: /// ```no_run /// # async fn wrapper() -> mongodb::error::Result<()> { - /// # use mongodb::{Collection, atlas_search::AtlasSearch, bson::{Document, doc}}; + /// # use mongodb::{Collection, bson::{Document, doc}}; /// # let collection: Collection = todo!(); + /// use mongodb::atlas_search; /// let cursor = collection.aggregate(vec![ - /// AtlasSearch::compound() + /// atlas_search::compound() /// .must(vec![ - /// AtlasSearch::text("description", "varieties").unit(), - /// AtlasSearch::compound() - /// .should(AtlasSearch::text("description", "Fuji")) + /// atlas_search::text("description", "varieties").unit(), + /// atlas_search::compound() + /// .should(atlas_search::text("description", "Fuji")) /// .unit(), /// ]) /// .into(), @@ -281,165 +283,3 @@ impl AtlasSearch { self } } - -#[test] -fn api_flow() { - assert_eq!( - doc! { - "$search": { - "autocomplete": { - "query": "pre", - "path": "title", - "fuzzy": { - "maxEdits": 1, - "prefixLength": 1, - "maxExpansions": 256, - }, - } - } - }, - AtlasSearch::autocomplete("title", "pre") - .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) - .into() - ); - assert_eq!( - doc! { - "$search": { - "text": { - "path": "plot", - "query": "baseball", - } - } - }, - AtlasSearch::text("plot", "baseball").into() - ); - assert_eq!( - doc! { - "$search": { - "compound": { - "must": [{ - "text": { - "path": "description", - "query": "varieties", - } - }], - "should": [{ - "text": { - "path": "description", - "query": "Fuji", - } - }], - } - } - }, - AtlasSearch::compound() - .must(AtlasSearch::text("description", "varieties")) - .should(AtlasSearch::text("description", "Fuji")) - .into() - ); - { - use short::*; - assert_eq!( - doc! { - "$search": { - "embeddedDocument": { - "path": "items", - "operator": { - "compound": { - "must": [{ - "text": { - "path": "items.tags", - "query": "school", - } - }], - "should": [{ - "text": { - "path": "items.name", - "query": "backpack", - } - }] - } - }, - "score": { - "embedded": { - "aggregate": "mean" - } - }, - } - } - }, - embedded_document( - "items", - compound() - .must(text("items.tags", "school")) - .should(text("items.name", "backpack")), - ) - .score(doc! { - "embedded": { - "aggregate": "mean" - } - }) - .into() - ); - } - assert_eq!( - doc! { - "$search": { - "equals": { - "path": "verified_user", - "value": true, - } - } - }, - AtlasSearch::equals("verified_user", true).into() - ); - let gte_dt = crate::bson::DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); - let lte_dt = crate::bson::DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); - assert_eq!( - doc! { - "$searchMeta": { - "facet": { - "operator": { - "range": { - "path": "released", - "gte": gte_dt, - "lte": lte_dt, - } - }, - "facets": { - "directorsFacet": { - "type": "string", - "path": "directors", - "numBuckets": 7, - }, - "yearFacet": { - "type": "number", - "path": "year", - "boundaries": [2000, 2005, 2010, 2015] - }, - } - } - } - }, - AtlasSearch::facet(doc! { - "directorsFacet": { - "type": "string", - "path": "directors", - "numBuckets": 7, - }, - "yearFacet": { - "type": "number", - "path": "year", - "boundaries": [2000, 2005, 2010, 2015] - }, - }) - .operator(doc! { - "range": { - "path": "released", - "gte": gte_dt, - "lte": lte_dt, - } - }) - .into() - ); -} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index a2d3b5ed4..fd8745d8a 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -2,24 +2,27 @@ use super::*; #[allow(missing_docs)] pub struct Autocomplete; -impl AtlasSearch { - /**The autocomplete operator performs a search for a word or phrase that - contains a sequence of characters from an incomplete input string. The - fields that you intend to query with the autocomplete operator must be - indexed with the autocomplete data type in the collection's index definition. - */ - /// - ///For more details, see the [autocomplete operator reference](https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/). - pub fn autocomplete(path: impl StringOrArray, query: impl StringOrArray) -> Self { - AtlasSearch { - name: "autocomplete", - stage: doc! { - "path" : path.to_bson(), "query" : query.to_bson(), - }, - meta: false, - _t: PhantomData, - } +/**The autocomplete operator performs a search for a word or phrase that +contains a sequence of characters from an incomplete input string. The +fields that you intend to query with the autocomplete operator must be +indexed with the autocomplete data type in the collection's index definition. +*/ +/// +///For more details, see the [autocomplete operator reference](https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/). +pub fn autocomplete( + path: impl StringOrArray, + query: impl StringOrArray, +) -> AtlasSearch { + AtlasSearch { + name: "autocomplete", + stage: doc! { + "path" : path.to_bson(), "query" : query.to_bson(), + }, + meta: false, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn token_order(mut self, token_order: TokenOrder) -> Self { self.stage.insert("tokenOrder", token_order.name()); @@ -38,21 +41,21 @@ impl AtlasSearch { } #[allow(missing_docs)] pub struct Compound; -impl AtlasSearch { - /**The compound operator combines two or more operators into a single query. - Each element of a compound query is called a clause, and each clause - consists of one or more sub-queries. - */ - /// - ///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). - pub fn compound() -> Self { - AtlasSearch { - name: "compound", - stage: doc! {}, - meta: false, - _t: PhantomData, - } +/**The compound operator combines two or more operators into a single query. +Each element of a compound query is called a clause, and each clause +consists of one or more sub-queries. +*/ +/// +///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). +pub fn compound() -> AtlasSearch { + AtlasSearch { + name: "compound", + stage: doc! {}, + meta: false, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn must(mut self, must: impl IntoIterator) -> Self { self.stage.insert( @@ -99,24 +102,27 @@ impl AtlasSearch { } #[allow(missing_docs)] pub struct EmbeddedDocument; -impl AtlasSearch { - /**The embeddedDocument operator is similar to $elemMatch operator. - It constrains multiple query predicates to be satisfied from a single - element of an array of embedded documents. embeddedDocument can be used only - for queries over fields of the embeddedDocuments - */ - /// - ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). - pub fn embedded_document(path: impl StringOrArray, operator: impl SearchOperator) -> Self { - AtlasSearch { - name: "embeddedDocument", - stage: doc! { - "path" : path.to_bson(), "operator" : operator.to_doc(), - }, - meta: false, - _t: PhantomData, - } +/**The embeddedDocument operator is similar to $elemMatch operator. +It constrains multiple query predicates to be satisfied from a single +element of an array of embedded documents. embeddedDocument can be used only +for queries over fields of the embeddedDocuments +*/ +/// +///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). +pub fn embedded_document( + path: impl StringOrArray, + operator: impl SearchOperator, +) -> AtlasSearch { + AtlasSearch { + name: "embeddedDocument", + stage: doc! { + "path" : path.to_bson(), "operator" : operator.to_doc(), + }, + meta: false, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { self.stage.insert("score", score); @@ -125,21 +131,21 @@ impl AtlasSearch { } #[allow(missing_docs)] pub struct Equals; -impl AtlasSearch { - /**The equals operator checks whether a field matches a value you specify. - * */ - /// - ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). - pub fn equals(path: impl StringOrArray, value: impl Into) -> Self { - AtlasSearch { - name: "equals", - stage: doc! { - "path" : path.to_bson(), "value" : value.into(), - }, - meta: false, - _t: PhantomData, - } +/**The equals operator checks whether a field matches a value you specify. + * */ +/// +///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). +pub fn equals(path: impl StringOrArray, value: impl Into) -> AtlasSearch { + AtlasSearch { + name: "equals", + stage: doc! { + "path" : path.to_bson(), "value" : value.into(), + }, + meta: false, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { self.stage.insert("score", score); @@ -148,22 +154,21 @@ impl AtlasSearch { } #[allow(missing_docs)] pub struct Exists; -impl AtlasSearch { - /**The exists operator tests if a path to a specified indexed field name exists in a - * document. - * */ - /// - ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). - pub fn exists(path: impl StringOrArray) -> Self { - AtlasSearch { - name: "exists", - stage: doc! { - "path" : path.to_bson(), - }, - meta: false, - _t: PhantomData, - } +/**The exists operator tests if a path to a specified indexed field name exists in a document. + * */ +/// +///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). +pub fn exists(path: impl StringOrArray) -> AtlasSearch { + AtlasSearch { + name: "exists", + stage: doc! { + "path" : path.to_bson(), + }, + meta: false, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { self.stage.insert("score", score); @@ -172,22 +177,22 @@ impl AtlasSearch { } #[allow(missing_docs)] pub struct Facet; -impl AtlasSearch { - /**The facet collector groups results by values or ranges in the specified - faceted fields and returns the count for each of those groups. - */ - /// - ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). - pub fn facet(facets: Document) -> Self { - AtlasSearch { - name: "facet", - stage: doc! { - "facets" : facets, - }, - meta: true, - _t: PhantomData, - } +/**The facet collector groups results by values or ranges in the specified +faceted fields and returns the count for each of those groups. +*/ +/// +///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). +pub fn facet(facets: Document) -> AtlasSearch { + AtlasSearch { + name: "facet", + stage: doc! { + "facets" : facets, + }, + meta: true, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn operator(mut self, operator: impl SearchOperator) -> Self { self.stage.insert("operator", operator.to_doc()); @@ -196,22 +201,22 @@ impl AtlasSearch { } #[allow(missing_docs)] pub struct Text; -impl AtlasSearch { - /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. - If you omit an analyzer, the text operator uses the default standard analyzer. - */ - /// - ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). - pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> Self { - AtlasSearch { - name: "text", - stage: doc! { - "path" : path.to_bson(), "query" : query.to_bson(), - }, - meta: false, - _t: PhantomData, - } +/**The text operator performs a full-text search using the analyzer that you specify in the index configuration. +If you omit an analyzer, the text operator uses the default standard analyzer. +*/ +/// +///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). +pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { + AtlasSearch { + name: "text", + stage: doc! { + "path" : path.to_bson(), "query" : query.to_bson(), + }, + meta: false, + _t: PhantomData, } +} +impl AtlasSearch { #[allow(missing_docs)] pub fn fuzzy(mut self, fuzzy: Document) -> Self { self.stage.insert("fuzzy", fuzzy); @@ -233,74 +238,3 @@ impl AtlasSearch { self } } -/// Atlas Search constructor functions without the `AtlasSearch::` prefix; can be useful to -/// improve readability when constructing deeply nested searches. -pub mod short { - use super::*; - /**The autocomplete operator performs a search for a word or phrase that - contains a sequence of characters from an incomplete input string. The - fields that you intend to query with the autocomplete operator must be - indexed with the autocomplete data type in the collection's index definition. - */ - /// - ///For more details, see the [autocomplete operator reference](https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/). - pub fn autocomplete( - path: impl StringOrArray, - query: impl StringOrArray, - ) -> AtlasSearch { - AtlasSearch::autocomplete(path, query) - } - /**The compound operator combines two or more operators into a single query. - Each element of a compound query is called a clause, and each clause - consists of one or more sub-queries. - */ - /// - ///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). - pub fn compound() -> AtlasSearch { - AtlasSearch::compound() - } - /**The embeddedDocument operator is similar to $elemMatch operator. - It constrains multiple query predicates to be satisfied from a single - element of an array of embedded documents. embeddedDocument can be used only - for queries over fields of the embeddedDocuments - */ - /// - ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). - pub fn embedded_document( - path: impl StringOrArray, - operator: impl SearchOperator, - ) -> AtlasSearch { - AtlasSearch::embedded_document(path, operator) - } - /**The equals operator checks whether a field matches a value you specify. - * */ - /// - ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). - pub fn equals(path: impl StringOrArray, value: impl Into) -> AtlasSearch { - AtlasSearch::equals(path, value) - } - /**The exists operator tests if a path to a specified indexed field name exists in a - * document. - * */ - /// - ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). - pub fn exists(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch::exists(path) - } - /**The facet collector groups results by values or ranges in the specified - faceted fields and returns the count for each of those groups. - */ - /// - ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). - pub fn facet(facets: Document) -> AtlasSearch { - AtlasSearch::facet(facets) - } - /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. - If you omit an analyzer, the text operator uses the default standard analyzer. - */ - /// - ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). - pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { - AtlasSearch::text(path, query) - } -} diff --git a/src/test.rs b/src/test.rs index 751247855..f9de13f0e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,6 +4,7 @@ #[cfg(feature = "dns-resolver")] #[path = "test/atlas_connectivity.rs"] mod atlas_connectivity_skip_ci; // requires Atlas URI environment variables set +mod atlas_search; mod auth; mod bulk_write; mod change_stream; diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs new file mode 100644 index 000000000..5b211fc6f --- /dev/null +++ b/src/test/atlas_search.rs @@ -0,0 +1,163 @@ +use crate::{atlas_search, bson::doc}; + +#[test] +fn api_flow() { + assert_eq!( + doc! { + "$search": { + "autocomplete": { + "query": "pre", + "path": "title", + "fuzzy": { + "maxEdits": 1, + "prefixLength": 1, + "maxExpansions": 256, + }, + } + } + }, + atlas_search::autocomplete("title", "pre") + .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) + .into() + ); + assert_eq!( + doc! { + "$search": { + "text": { + "path": "plot", + "query": "baseball", + } + } + }, + atlas_search::text("plot", "baseball").into() + ); + assert_eq!( + doc! { + "$search": { + "compound": { + "must": [{ + "text": { + "path": "description", + "query": "varieties", + } + }], + "should": [{ + "text": { + "path": "description", + "query": "Fuji", + } + }], + } + } + }, + atlas_search::compound() + .must(atlas_search::text("description", "varieties")) + .should(atlas_search::text("description", "Fuji")) + .into() + ); + { + use atlas_search::*; + assert_eq!( + doc! { + "$search": { + "embeddedDocument": { + "path": "items", + "operator": { + "compound": { + "must": [{ + "text": { + "path": "items.tags", + "query": "school", + } + }], + "should": [{ + "text": { + "path": "items.name", + "query": "backpack", + } + }] + } + }, + "score": { + "embedded": { + "aggregate": "mean" + } + }, + } + } + }, + embedded_document( + "items", + compound() + .must(text("items.tags", "school")) + .should(text("items.name", "backpack")), + ) + .score(doc! { + "embedded": { + "aggregate": "mean" + } + }) + .into() + ); + } + assert_eq!( + doc! { + "$search": { + "equals": { + "path": "verified_user", + "value": true, + } + } + }, + atlas_search::equals("verified_user", true).into() + ); + let gte_dt = crate::bson::DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); + let lte_dt = crate::bson::DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); + assert_eq!( + doc! { + "$searchMeta": { + "facet": { + "operator": { + "range": { + "path": "released", + "gte": gte_dt, + "lte": lte_dt, + } + }, + "facets": { + "directorsFacet": { + "type": "string", + "path": "directors", + "numBuckets": 7, + }, + "yearFacet": { + "type": "number", + "path": "year", + "boundaries": [2000, 2005, 2010, 2015] + }, + } + } + } + }, + atlas_search::facet(doc! { + "directorsFacet": { + "type": "string", + "path": "directors", + "numBuckets": 7, + }, + "yearFacet": { + "type": "number", + "path": "year", + "boundaries": [2000, 2005, 2010, 2015] + }, + }) + .operator(doc! { + "range": { + "path": "released", + "gte": gte_dt, + "lte": lte_dt, + } + }) + .into() + ); +} From fc95880d2fb3947349774c19ea536f83b510ddd8 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 26 Aug 2025 15:11:51 +0100 Subject: [PATCH 07/32] facet helpers --- etc/gen_atlas_search/src/main.rs | 2 + src/atlas_search.rs | 95 ++++++++++++++++++++++ src/atlas_search/gen.rs | 44 +++++++++++ src/test/atlas_search.rs | 130 ++++++++++++++----------------- 4 files changed, 199 insertions(+), 72 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 499b59f27..d9e87cdc5 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -138,6 +138,7 @@ impl Argument { ("autocomplete", "tokenOrder") => return ArgumentRustType::TokenOrder, ("text", "matchCriteria") => return ArgumentRustType::MatchCriteria, ("equals", "value") => return ArgumentRustType::IntoBson, + ("range", "gt" | "gte" | "lt" | "lte") => return ArgumentRustType::IntoBson, _ => (), } use ArgumentType::*; @@ -219,6 +220,7 @@ fn main() { "equals", "exists", "facet", + "range", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index ada5e0be0..0a1044612 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -283,3 +283,98 @@ impl AtlasSearch { self } } + +/// Facet definitions. These can be used when constructing a facet definition doc: +/// ``` +/// use mongodb::atlas_search; +/// let search = atlas_search::facet(doc! { +/// "directorsFacet": atlas_search::facet::string("directors").num_buckets(7), +/// "yearFacet": atlas_search::facet::number("year", [2000, 2005, 2010, 2015]), +/// }); +/// ``` +pub mod facet { + use crate::bson::{doc, Bson, Document}; + use std::marker::PhantomData; + + /// A facet definition. + pub struct Facet { + inner: Document, + _t: PhantomData, + } + + impl From> for Bson { + fn from(value: Facet) -> Self { + Bson::Document(value.inner) + } + } + + #[allow(missing_docs)] + pub struct String; + /// String facets allow you to narrow down Atlas Search results based on the most frequent + /// string values in the specified string field. + pub fn string(path: impl AsRef) -> Facet { + Facet { + inner: doc! { + "type": "string", + "path": path.as_ref(), + }, + _t: PhantomData, + } + } + impl Facet { + #[allow(missing_docs)] + pub fn num_buckets(mut self, num: i32) -> Self { + self.inner.insert("numBuckets", num); + self + } + } + + #[allow(missing_docs)] + pub struct Number; + /// Numeric facets allow you to determine the frequency of numeric values in your search results + /// by breaking the results into separate ranges of numbers. + pub fn number( + path: impl AsRef, + boundaries: impl IntoIterator>, + ) -> Facet { + Facet { + inner: doc! { + "type": "number", + "path": path.as_ref(), + "boundaries": boundaries.into_iter().map(Into::into).collect::>(), + }, + _t: PhantomData, + } + } + impl Facet { + #[allow(missing_docs)] + pub fn default(mut self, bucket: impl AsRef) -> Self { + self.inner.insert("default", bucket.as_ref()); + self + } + } + + #[allow(missing_docs)] + pub struct Date; + /// Date facets allow you to narrow down search results based on a date. + pub fn date( + path: impl AsRef, + boundaries: impl IntoIterator, + ) -> Facet { + Facet { + inner: doc! { + "type": "date", + "path": path.as_ref(), + "boundaries": boundaries.into_iter().collect::>(), + }, + _t: PhantomData, + } + } + impl Facet { + #[allow(missing_docs)] + pub fn default(mut self, bucket: impl AsRef) -> Self { + self.inner.insert("default", bucket.as_ref()); + self + } + } +} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index fd8745d8a..abbb6e2af 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -200,6 +200,50 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Range; +/**The range operator supports querying and scoring numeric, date, and string values. +You can use this operator to find results that are within a given numeric, date, objectId, or letter (from the English alphabet) range. +*/ +/// +///For more details, see the [range operator reference](https://www.mongodb.com/docs/atlas/atlas-search/range/). +pub fn range(path: impl StringOrArray) -> AtlasSearch { + AtlasSearch { + name: "range", + stage: doc! { + "path" : path.to_bson(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn gt(mut self, gt: impl Into) -> Self { + self.stage.insert("gt", gt.into()); + self + } + #[allow(missing_docs)] + pub fn gte(mut self, gte: impl Into) -> Self { + self.stage.insert("gte", gte.into()); + self + } + #[allow(missing_docs)] + pub fn lt(mut self, lt: impl Into) -> Self { + self.stage.insert("lt", lt.into()); + self + } + #[allow(missing_docs)] + pub fn lte(mut self, lte: impl Into) -> Self { + self.stage.insert("lte", lte.into()); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 5b211fc6f..d8d8ff8fa 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -1,7 +1,10 @@ -use crate::{atlas_search, bson::doc}; +use crate::{ + atlas_search::*, + bson::{doc, DateTime}, +}; #[test] -fn api_flow() { +fn helper_output_doc() { assert_eq!( doc! { "$search": { @@ -16,7 +19,7 @@ fn api_flow() { } } }, - atlas_search::autocomplete("title", "pre") + autocomplete("title", "pre") .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) .into() ); @@ -29,7 +32,7 @@ fn api_flow() { } } }, - atlas_search::text("plot", "baseball").into() + text("plot", "baseball").into() ); assert_eq!( doc! { @@ -50,56 +53,53 @@ fn api_flow() { } } }, - atlas_search::compound() - .must(atlas_search::text("description", "varieties")) - .should(atlas_search::text("description", "Fuji")) + compound() + .must(text("description", "varieties")) + .should(text("description", "Fuji")) .into() ); - { - use atlas_search::*; - assert_eq!( - doc! { - "$search": { - "embeddedDocument": { - "path": "items", - "operator": { - "compound": { - "must": [{ - "text": { - "path": "items.tags", - "query": "school", - } - }], - "should": [{ - "text": { - "path": "items.name", - "query": "backpack", - } - }] - } - }, - "score": { - "embedded": { - "aggregate": "mean" - } - }, - } - } - }, - embedded_document( - "items", - compound() - .must(text("items.tags", "school")) - .should(text("items.name", "backpack")), - ) - .score(doc! { - "embedded": { - "aggregate": "mean" + assert_eq!( + doc! { + "$search": { + "embeddedDocument": { + "path": "items", + "operator": { + "compound": { + "must": [{ + "text": { + "path": "items.tags", + "query": "school", + } + }], + "should": [{ + "text": { + "path": "items.name", + "query": "backpack", + } + }] + } + }, + "score": { + "embedded": { + "aggregate": "mean" + } + }, } - }) - .into() - ); - } + } + }, + embedded_document( + "items", + compound() + .must(text("items.tags", "school")) + .should(text("items.name", "backpack")), + ) + .score(doc! { + "embedded": { + "aggregate": "mean" + } + }) + .into() + ); assert_eq!( doc! { "$search": { @@ -109,10 +109,10 @@ fn api_flow() { } } }, - atlas_search::equals("verified_user", true).into() + equals("verified_user", true).into() ); - let gte_dt = crate::bson::DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); - let lte_dt = crate::bson::DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); + let gte_dt = DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); + let lte_dt = DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); assert_eq!( doc! { "$searchMeta": { @@ -139,25 +139,11 @@ fn api_flow() { } } }, - atlas_search::facet(doc! { - "directorsFacet": { - "type": "string", - "path": "directors", - "numBuckets": 7, - }, - "yearFacet": { - "type": "number", - "path": "year", - "boundaries": [2000, 2005, 2010, 2015] - }, - }) - .operator(doc! { - "range": { - "path": "released", - "gte": gte_dt, - "lte": lte_dt, - } + facet(doc! { + "directorsFacet": facet::string("directors").num_buckets(7), + "yearFacet": facet::number("year", [2000, 2005, 2010, 2015]), }) + .operator(range("released").gte(gte_dt).lte(lte_dt)) .into() ); } From 8cf359e57bdc81813b8268715262337993a316c6 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 26 Aug 2025 15:26:42 +0100 Subject: [PATCH 08/32] geo_shape --- etc/gen_atlas_search/src/main.rs | 11 ++++++++++- src/atlas_search.rs | 29 +++++++++++++++++++++++++++++ src/atlas_search/gen.rs | 28 ++++++++++++++++++++++++++++ src/test/atlas_search.rs | 29 +++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index d9e87cdc5..4be2ab12a 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -120,6 +120,7 @@ enum ArgumentType { BinData, Bool, Date, + Geometry, Int, Null, Number, @@ -138,7 +139,9 @@ impl Argument { ("autocomplete", "tokenOrder") => return ArgumentRustType::TokenOrder, ("text", "matchCriteria") => return ArgumentRustType::MatchCriteria, ("equals", "value") => return ArgumentRustType::IntoBson, + ("geoShape", "relation") => return ArgumentRustType::Relation, ("range", "gt" | "gte" | "lt" | "lte") => return ArgumentRustType::IntoBson, + _ => (), } use ArgumentType::*; @@ -150,6 +153,7 @@ impl Argument { [SearchOperator] => ArgumentRustType::SearchOperator, [SearchOperator, Array] => ArgumentRustType::SeachOperatorIter, [Int] => ArgumentRustType::I32, + [Geometry] => ArgumentRustType::Document, _ => panic!("Unexpected argument types: {:?}", self.type_), } } @@ -160,6 +164,7 @@ enum ArgumentRustType { I32, IntoBson, MatchCriteria, + Relation, SearchOperator, SeachOperatorIter, String, @@ -174,6 +179,7 @@ impl ArgumentRustType { Self::I32 => parse_quote! { i32 }, Self::IntoBson => parse_quote! { impl Into }, Self::MatchCriteria => parse_quote! { MatchCriteria }, + Self::Relation => parse_quote! { Relation }, Self::SearchOperator => parse_quote! { impl SearchOperator }, Self::SeachOperatorIter => { parse_quote! { impl IntoIterator } @@ -194,7 +200,9 @@ impl ArgumentRustType { } Self::String => parse_quote! { #ident.as_ref() }, Self::StringOrArray => parse_quote! { #ident.to_bson() }, - Self::TokenOrder | Self::MatchCriteria => parse_quote! { #ident.name() }, + Self::TokenOrder | Self::MatchCriteria | Self::Relation => { + parse_quote! { #ident.name() } + } } } } @@ -221,6 +229,7 @@ fn main() { "exists", "facet", "range", + "geoShape", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 0a1044612..c80b0d6b6 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -378,3 +378,32 @@ pub mod facet { } } } + +/// Relation of the query shape geometry to the indexed field geometry. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub enum Relation { + /// Indicates that the indexed geometry contains the query geometry. + Contains, + /// Indicates that both the query and indexed geometries have nothing in common. + Disjoint, + /// Indicates that both the query and indexed geometries intersect. + Intersects, + /// Indicates that the indexed geometry is within the query geometry. You can't use within with + /// LineString or Point. + Within, + /// Fallback for future compatibility. + Other(String), +} + +impl Relation { + fn name(&self) -> &str { + match self { + Self::Contains => "contains", + Self::Disjoint => "disjoint", + Self::Intersects => "intersects", + Self::Within => "within", + Self::Other(s) => &s, + } + } +} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index abbb6e2af..9e51921e3 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -244,6 +244,34 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct GeoShape; +/**The geoShape operator supports querying shapes with a relation to a given +geometry if indexShapes is set to true in the index definition. +*/ +/// +///For more details, see the [geoShape operator reference](https://www.mongodb.com/docs/atlas/atlas-search/geoShape/). +pub fn geo_shape( + path: impl StringOrArray, + relation: Relation, + geometry: Document, +) -> AtlasSearch { + AtlasSearch { + name: "geoShape", + stage: doc! { + "path" : path.to_bson(), "relation" : relation.name(), "geometry" : geometry, + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index d8d8ff8fa..e0b156716 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -146,4 +146,33 @@ fn helper_output_doc() { .operator(range("released").gte(gte_dt).lte(lte_dt)) .into() ); + assert_eq!( + doc! { + "$search": { + "geoShape": { + "relation": "disjoint", + "geometry": { + "type": "Polygon", + "coordinates": [[[-161.323242,22.512557], + [-152.446289,22.065278], + [-156.09375,17.811456], + [-161.323242,22.512557]]] + }, + "path": "address.location" + } + } + }, + geo_shape( + "address.location", + Relation::Disjoint, + doc! { + "type": "Polygon", + "coordinates": [[[-161.323242,22.512557], + [-152.446289,22.065278], + [-156.09375,17.811456], + [-161.323242,22.512557]]] + } + ) + .into() + ); } From 2599ee993d0abdcc074e05c6b94e33daf5e1163c Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 26 Aug 2025 15:53:37 +0100 Subject: [PATCH 09/32] geoWithin --- etc/gen_atlas_search/src/main.rs | 10 +++++++- src/atlas_search/gen.rs | 40 ++++++++++++++++++++++++++++++++ src/test/atlas_search.rs | 31 +++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 4be2ab12a..04a04ed0f 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -50,7 +50,14 @@ impl Operator { let mut setters = TokenStream::new(); for arg in &self.arguments { - let ident = format_ident!("{}", arg.name.to_case(Case::Snake)); + let ident = format_ident!( + "{}", + match arg.name.as_str() { + // `box` is a reserved word + "box" => "geo_box".to_owned(), + _ => arg.name.to_case(Case::Snake), + } + ); let rust_type = arg.rust_type(&self.name); let type_ = rust_type.tokens(); let arg_name = &arg.name; @@ -230,6 +237,7 @@ fn main() { "facet", "range", "geoShape", + "geoWithin", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 9e51921e3..5a5af30c3 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -272,6 +272,46 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct GeoWithin; +/**The geoWithin operator supports querying geographic points within a given +geometry. Only points are returned, even if indexShapes value is true in +the index definition. +*/ +/// +///For more details, see the [geoWithin operator reference](https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/). +pub fn geo_within(path: impl StringOrArray) -> AtlasSearch { + AtlasSearch { + name: "geoWithin", + stage: doc! { + "path" : path.to_bson(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn geo_box(mut self, geo_box: Document) -> Self { + self.stage.insert("box", geo_box); + self + } + #[allow(missing_docs)] + pub fn circle(mut self, circle: Document) -> Self { + self.stage.insert("circle", circle); + self + } + #[allow(missing_docs)] + pub fn geometry(mut self, geometry: Document) -> Self { + self.stage.insert("geometry", geometry); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index e0b156716..203d52380 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -175,4 +175,35 @@ fn helper_output_doc() { ) .into() ); + assert_eq!( + doc! { + "$search": { + "geoWithin": { + "path": "address.location", + "box": { + "bottomLeft": { + "type": "Point", + "coordinates": [112.467, -55.050] + }, + "topRight": { + "type": "Point", + "coordinates": [168.000, -9.133] + } + } + } + } + }, + geo_within("address.location") + .geo_box(doc! { + "bottomLeft": { + "type": "Point", + "coordinates": [112.467, -55.050] + }, + "topRight": { + "type": "Point", + "coordinates": [168.000, -9.133] + } + }) + .into() + ) } From cf05cc0004dda6ddef778992b11c051d88d61c02 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Wed, 27 Aug 2025 14:51:59 +0100 Subject: [PATCH 10/32] boil down StringOrArray --- src/atlas_search.rs | 64 ++-------------------------------------- src/test/atlas_search.rs | 9 ++++++ 2 files changed, 11 insertions(+), 62 deletions(-) diff --git a/src/atlas_search.rs b/src/atlas_search.rs index c80b0d6b6..d1829aa83 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -173,21 +173,14 @@ impl StringOrArray for String { fn sealed(_: private::Sealed) {} } -impl StringOrArray for &String { - fn to_bson(self) -> Bson { - Bson::String(self.clone()) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for &[&str] { +impl StringOrArray for [&str; N] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|&s| Bson::String(s.to_owned())).collect()) } fn sealed(_: private::Sealed) {} } -impl StringOrArray for &[&str; N] { +impl StringOrArray for &[&str] { fn to_bson(self) -> Bson { Bson::Array(self.iter().map(|&s| Bson::String(s.to_owned())).collect()) } @@ -201,59 +194,6 @@ impl StringOrArray for &[String] { fn sealed(_: private::Sealed) {} } -impl StringOrArray for &[String; N] { - fn to_bson(self) -> Bson { - Bson::Array(self.iter().map(|s| Bson::String(s.clone())).collect()) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for [String; N] { - fn to_bson(self) -> Bson { - Bson::Array(self.into_iter().map(Bson::String).collect()) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for &[&String] { - fn to_bson(self) -> Bson { - Bson::Array(self.iter().map(|&s| Bson::String(s.clone())).collect()) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for &[&String; N] { - fn to_bson(self) -> Bson { - Bson::Array(self.iter().map(|&s| Bson::String(s.clone())).collect()) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for Vec<&str> { - fn to_bson(self) -> Bson { - Bson::Array( - self.into_iter() - .map(|s| Bson::String(s.to_owned())) - .collect(), - ) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for Vec { - fn to_bson(self) -> Bson { - Bson::Array(self.into_iter().map(Bson::String).collect()) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for Vec<&String> { - fn to_bson(self) -> Bson { - Bson::Array(self.into_iter().map(|s| Bson::String(s.clone())).collect()) - } - fn sealed(_: private::Sealed) {} -} - /// An Atlas Search operator parameter that is itself a search operator. pub trait SearchOperator { #[allow(missing_docs)] diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 203d52380..1df05a594 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -207,3 +207,12 @@ fn helper_output_doc() { .into() ) } + +#[test] +fn string_or_array_forms() { + exists("hello"); + exists("hello".to_owned()); + exists(["hello", "world"]); + exists(&["hello", "world"] as &[&str]); + exists(&["hello".to_owned()] as &[String]); +} From f456822b25905faa55722203ad59f22affe2196c Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Wed, 27 Aug 2025 15:16:22 +0100 Subject: [PATCH 11/32] in --- etc/gen_atlas_search/src/main.rs | 11 +++++++++-- src/atlas_search/gen.rs | 23 +++++++++++++++++++++++ src/test/atlas_search.rs | 13 ++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 04a04ed0f..667f36a5b 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -41,8 +41,12 @@ impl Operator { fn gen_helper(&self) -> TokenStream { let name_text = &self.name; - let name_ident = format_ident!("{}", name_text.to_case(Case::Pascal)); - let constr_ident = format_ident!("{}", name_text.to_case(Case::Snake)); + let ident_base = match name_text.as_str() { + "in" => "searchIn", + _ => name_text, + }; + let name_ident = format_ident!("{}", ident_base.to_case(Case::Pascal)); + let constr_ident = format_ident!("{}", ident_base.to_case(Case::Snake)); let mut required_args = TokenStream::new(); let mut required_arg_names = TokenStream::new(); @@ -123,6 +127,7 @@ struct Argument { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] enum ArgumentType { + Any, Array, BinData, Bool, @@ -161,6 +166,7 @@ impl Argument { [SearchOperator, Array] => ArgumentRustType::SeachOperatorIter, [Int] => ArgumentRustType::I32, [Geometry] => ArgumentRustType::Document, + [Any, Array] => ArgumentRustType::IntoBson, _ => panic!("Unexpected argument types: {:?}", self.type_), } } @@ -238,6 +244,7 @@ fn main() { "range", "geoShape", "geoWithin", + "in", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 5a5af30c3..d6d79d00f 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -312,6 +312,29 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct SearchIn; +/**The in operator performs a search for an array of BSON values in a field. + * */ +/// +///For more details, see the [in operator reference](https://www.mongodb.com/docs/atlas/atlas-search/in/). +pub fn search_in(path: impl StringOrArray, value: impl Into) -> AtlasSearch { + AtlasSearch { + name: "in", + stage: doc! { + "path" : path.to_bson(), "value" : value.into(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 1df05a594..c10b16492 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -205,7 +205,18 @@ fn helper_output_doc() { } }) .into() - ) + ); + assert_eq!( + doc! { + "$search": { + "in": { + "path": "accounts", + "value": [371138, 371139, 371140] + } + } + }, + search_in("accounts", [371138, 371139, 371140].as_ref()).into() + ); } #[test] From 13d4c86809120e1bbd29345619ac691ec83074a2 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Wed, 27 Aug 2025 15:35:32 +0100 Subject: [PATCH 12/32] moreLikeThis --- etc/gen_atlas_search/src/main.rs | 6 ++++- src/atlas_search.rs | 45 ++++++++++++++++++++++++++------ src/atlas_search/gen.rs | 25 ++++++++++++++++++ src/test/atlas_search.rs | 17 ++++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 667f36a5b..9e34642a1 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -167,6 +167,7 @@ impl Argument { [Int] => ArgumentRustType::I32, [Geometry] => ArgumentRustType::Document, [Any, Array] => ArgumentRustType::IntoBson, + [Object, Array] => ArgumentRustType::DocumentOrArray, _ => panic!("Unexpected argument types: {:?}", self.type_), } } @@ -174,6 +175,7 @@ impl Argument { enum ArgumentRustType { Document, + DocumentOrArray, I32, IntoBson, MatchCriteria, @@ -189,6 +191,7 @@ impl ArgumentRustType { fn tokens(&self) -> syn::Type { match self { Self::Document => parse_quote! { Document }, + Self::DocumentOrArray => parse_quote! { impl DocumentOrArray }, Self::I32 => parse_quote! { i32 }, Self::IntoBson => parse_quote! { impl Into }, Self::MatchCriteria => parse_quote! { MatchCriteria }, @@ -212,7 +215,7 @@ impl ArgumentRustType { parse_quote! { #ident.into_iter().map(|o| o.to_doc()).collect::>() } } Self::String => parse_quote! { #ident.as_ref() }, - Self::StringOrArray => parse_quote! { #ident.to_bson() }, + Self::StringOrArray | Self::DocumentOrArray => parse_quote! { #ident.to_bson() }, Self::TokenOrder | Self::MatchCriteria | Self::Relation => { parse_quote! { #ident.name() } } @@ -245,6 +248,7 @@ fn main() { "geoShape", "geoWithin", "in", + "moreLikeThis", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index d1829aa83..8d3fbdc84 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -153,9 +153,9 @@ mod private { /// An Atlas Search operator parameter that can be either a string or array of strings. pub trait StringOrArray { - #[allow(missing_docs)] + #[doc(hidden)] fn to_bson(self) -> Bson; - #[allow(missing_docs)] + #[doc(hidden)] fn sealed(_: private::Sealed); } @@ -196,9 +196,9 @@ impl StringOrArray for &[String] { /// An Atlas Search operator parameter that is itself a search operator. pub trait SearchOperator { - #[allow(missing_docs)] + #[doc(hidden)] fn to_doc(self) -> Document; - #[allow(missing_docs)] + #[doc(hidden)] fn sealed(_: private::Sealed) {} } @@ -226,10 +226,10 @@ impl AtlasSearch { /// Facet definitions. These can be used when constructing a facet definition doc: /// ``` -/// use mongodb::atlas_search; -/// let search = atlas_search::facet(doc! { -/// "directorsFacet": atlas_search::facet::string("directors").num_buckets(7), -/// "yearFacet": atlas_search::facet::number("year", [2000, 2005, 2010, 2015]), +/// use mongodb::atlas_search::facet; +/// let search = facet(doc! { +/// "directorsFacet": facet::string("directors").num_buckets(7), +/// "yearFacet": facet::number("year", [2000, 2005, 2010, 2015]), /// }); /// ``` pub mod facet { @@ -347,3 +347,32 @@ impl Relation { } } } + +/// An Atlas Search operator parameter that can be either a document or array of documents. +pub trait DocumentOrArray { + #[doc(hidden)] + fn to_bson(self) -> Bson; + #[doc(hidden)] + fn sealed(_: private::Sealed); +} + +impl DocumentOrArray for Document { + fn to_bson(self) -> Bson { + Bson::Document(self) + } + fn sealed(_: private::Sealed) {} +} + +impl DocumentOrArray for [Document; N] { + fn to_bson(self) -> Bson { + Bson::Array(self.into_iter().map(Bson::Document).collect()) + } + fn sealed(_: private::Sealed) {} +} + +impl DocumentOrArray for &[Document] { + fn to_bson(self) -> Bson { + Bson::from(self) + } + fn sealed(_: private::Sealed) {} +} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index d6d79d00f..9b1e79293 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -335,6 +335,31 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct MoreLikeThis; +/**The moreLikeThis operator returns documents similar to input documents. +The moreLikeThis operator allows you to build features for your applications +that display similar or alternative results based on one or more given documents. +*/ +/// +///For more details, see the [moreLikeThis operator reference](https://www.mongodb.com/docs/atlas/atlas-search/moreLikeThis/). +pub fn more_like_this(like: impl DocumentOrArray) -> AtlasSearch { + AtlasSearch { + name: "moreLikeThis", + stage: doc! { + "like" : like.to_bson(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index c10b16492..5f9da9919 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -217,6 +217,23 @@ fn helper_output_doc() { }, search_in("accounts", [371138, 371139, 371140].as_ref()).into() ); + assert_eq!( + doc! { + "$search": { + "moreLikeThis": { + "like": { + "title": "The Godfather", + "genres": "action" + } + } + } + }, + more_like_this(doc! { + "title": "The Godfather", + "genres": "action" + }) + .into() + ); } #[test] From 28eb8a1a93472d3c2156204f25de58fa6b2ca828 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 11:29:30 +0100 Subject: [PATCH 13/32] better parameter machinery --- Cargo.lock | 2 +- etc/gen_atlas_search/src/main.rs | 7 +- src/atlas_search.rs | 135 +++++++++---------------------- src/atlas_search/gen.rs | 15 ++-- src/test/atlas_search.rs | 21 ++--- 5 files changed, 65 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45edf1832..623daf990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,7 +589,7 @@ dependencies = [ [[package]] name = "bson" version = "3.0.0" -source = "git+https://github.com/mongodb/bson-rust?branch=main#69f82fb1c99fa8aa3be3dee22e1834efb7428c66" +source = "git+https://github.com/mongodb/bson-rust?branch=main#440bfeb54d68edaff4a86e57de30e2d0adcdb5fd" dependencies = [ "ahash", "base64 0.22.1", diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 9e34642a1..d77cfc8bc 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -210,12 +210,13 @@ impl ArgumentRustType { match self { Self::Document | Self::I32 => parse_quote! { #ident }, Self::IntoBson => parse_quote! { #ident.into() }, - Self::SearchOperator => parse_quote! { #ident.to_doc() }, Self::SeachOperatorIter => { - parse_quote! { #ident.into_iter().map(|o| o.to_doc()).collect::>() } + parse_quote! { #ident.into_iter().map(|o| o.to_bson()).collect::>() } } Self::String => parse_quote! { #ident.as_ref() }, - Self::StringOrArray | Self::DocumentOrArray => parse_quote! { #ident.to_bson() }, + Self::StringOrArray | Self::DocumentOrArray | Self::SearchOperator => { + parse_quote! { #ident.to_bson() } + } Self::TokenOrder | Self::MatchCriteria | Self::Relation => { parse_quote! { #ident.name() } } diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 8d3fbdc84..c7f3ce49b 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -40,17 +40,6 @@ pub struct AtlasSearch { _t: PhantomData, } -impl From> for Document { - fn from(value: AtlasSearch) -> Self { - let key = if value.meta { "$searchMeta" } else { "$search" }; - doc! { - key: { - value.name: value.stage - } - } - } -} - impl AtlasSearch { /// Erase the type of this builder. Not typically needed, but can be useful to include builders /// of different types in a single `Vec`: @@ -80,7 +69,17 @@ impl AtlasSearch { } } - /// Like [`into`](Into::into), converts this builder into an aggregate pipeline stage + /// Converts this builder into an aggregate pipeline stage [`Document`]. + pub fn stage(self) -> Document { + let key = if self.meta { "$searchMeta" } else { "$search" }; + doc! { + key: { + self.name: self.stage + } + } + } + + /// Like [`stage`](AtlasSearch::stage), converts this builder into an aggregate pipeline stage /// [`Document`], but also specify the search index to use. pub fn on_index(self, index: impl AsRef) -> Document { doc! { @@ -148,73 +147,39 @@ impl MatchCriteria { } mod private { - pub struct Sealed; -} - -/// An Atlas Search operator parameter that can be either a string or array of strings. -pub trait StringOrArray { - #[doc(hidden)] - fn to_bson(self) -> Bson; - #[doc(hidden)] - fn sealed(_: private::Sealed); -} + use crate::bson::{doc, Bson}; -impl StringOrArray for &str { - fn to_bson(self) -> Bson { - Bson::String(self.to_owned()) + /// An Atlas Search operator parameter that can accept multiple types. + pub trait Parameter { + fn to_bson(self) -> Bson; } - fn sealed(_: private::Sealed) {} -} -impl StringOrArray for String { - fn to_bson(self) -> Bson { - Bson::String(self) - } - fn sealed(_: private::Sealed) {} -} - -impl StringOrArray for [&str; N] { - fn to_bson(self) -> Bson { - Bson::Array(self.iter().map(|&s| Bson::String(s.to_owned())).collect()) + impl> Parameter for T { + fn to_bson(self) -> Bson { + self.into() + } } - fn sealed(_: private::Sealed) {} -} -impl StringOrArray for &[&str] { - fn to_bson(self) -> Bson { - Bson::Array(self.iter().map(|&s| Bson::String(s.to_owned())).collect()) + impl Parameter for super::AtlasSearch { + fn to_bson(self) -> Bson { + Bson::Document(doc! { self.name: self.stage }) + } } - fn sealed(_: private::Sealed) {} } -impl StringOrArray for &[String] { - fn to_bson(self) -> Bson { - Bson::Array(self.iter().map(|s| Bson::String(s.clone())).collect()) - } - fn sealed(_: private::Sealed) {} -} +/// An Atlas Search operator parameter that can be either a string or array of strings. +pub trait StringOrArray: private::Parameter {} +impl StringOrArray for &str {} +impl StringOrArray for String {} +#[cfg(feature = "bson-3")] +impl StringOrArray for [&str; N] {} +impl StringOrArray for &[&str] {} +impl StringOrArray for &[String] {} /// An Atlas Search operator parameter that is itself a search operator. -pub trait SearchOperator { - #[doc(hidden)] - fn to_doc(self) -> Document; - #[doc(hidden)] - fn sealed(_: private::Sealed) {} -} - -impl SearchOperator for AtlasSearch { - fn to_doc(self) -> Document { - doc! { self.name: self.stage } - } - fn sealed(_: private::Sealed) {} -} - -impl SearchOperator for Document { - fn to_doc(self) -> Document { - self - } - fn sealed(_: private::Sealed) {} -} +pub trait SearchOperator: private::Parameter {} +impl SearchOperator for AtlasSearch {} +impl SearchOperator for Document {} impl AtlasSearch { /// Use the `$search` stage instead of the default `$searchMeta` stage. @@ -349,30 +314,10 @@ impl Relation { } /// An Atlas Search operator parameter that can be either a document or array of documents. -pub trait DocumentOrArray { - #[doc(hidden)] - fn to_bson(self) -> Bson; - #[doc(hidden)] - fn sealed(_: private::Sealed); -} - -impl DocumentOrArray for Document { - fn to_bson(self) -> Bson { - Bson::Document(self) - } - fn sealed(_: private::Sealed) {} -} +pub trait DocumentOrArray: private::Parameter {} +impl DocumentOrArray for Document {} +//impl DocumentOrArray for [Document; N] {} +impl DocumentOrArray for &[Document] {} -impl DocumentOrArray for [Document; N] { - fn to_bson(self) -> Bson { - Bson::Array(self.into_iter().map(Bson::Document).collect()) - } - fn sealed(_: private::Sealed) {} -} - -impl DocumentOrArray for &[Document] { - fn to_bson(self) -> Bson { - Bson::from(self) - } - fn sealed(_: private::Sealed) {} -} +/// An Atlas Search operator parameter that can be any BSON numeric type. +pub trait BsonNumber {} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 9b1e79293..da6e44afd 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -60,7 +60,7 @@ impl AtlasSearch { pub fn must(mut self, must: impl IntoIterator) -> Self { self.stage.insert( "must", - must.into_iter().map(|o| o.to_doc()).collect::>(), + must.into_iter().map(|o| o.to_bson()).collect::>(), ); self } @@ -68,7 +68,10 @@ impl AtlasSearch { pub fn must_not(mut self, must_not: impl IntoIterator) -> Self { self.stage.insert( "mustNot", - must_not.into_iter().map(|o| o.to_doc()).collect::>(), + must_not + .into_iter() + .map(|o| o.to_bson()) + .collect::>(), ); self } @@ -76,7 +79,7 @@ impl AtlasSearch { pub fn should(mut self, should: impl IntoIterator) -> Self { self.stage.insert( "should", - should.into_iter().map(|o| o.to_doc()).collect::>(), + should.into_iter().map(|o| o.to_bson()).collect::>(), ); self } @@ -84,7 +87,7 @@ impl AtlasSearch { pub fn filter(mut self, filter: impl IntoIterator) -> Self { self.stage.insert( "filter", - filter.into_iter().map(|o| o.to_doc()).collect::>(), + filter.into_iter().map(|o| o.to_bson()).collect::>(), ); self } @@ -116,7 +119,7 @@ pub fn embedded_document( AtlasSearch { name: "embeddedDocument", stage: doc! { - "path" : path.to_bson(), "operator" : operator.to_doc(), + "path" : path.to_bson(), "operator" : operator.to_bson(), }, meta: false, _t: PhantomData, @@ -195,7 +198,7 @@ pub fn facet(facets: Document) -> AtlasSearch { impl AtlasSearch { #[allow(missing_docs)] pub fn operator(mut self, operator: impl SearchOperator) -> Self { - self.stage.insert("operator", operator.to_doc()); + self.stage.insert("operator", operator.to_bson()); self } } diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 5f9da9919..cc024b5f9 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -21,7 +21,7 @@ fn helper_output_doc() { }, autocomplete("title", "pre") .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) - .into() + .stage() ); assert_eq!( doc! { @@ -32,7 +32,7 @@ fn helper_output_doc() { } } }, - text("plot", "baseball").into() + text("plot", "baseball").stage() ); assert_eq!( doc! { @@ -56,7 +56,7 @@ fn helper_output_doc() { compound() .must(text("description", "varieties")) .should(text("description", "Fuji")) - .into() + .stage() ); assert_eq!( doc! { @@ -98,7 +98,7 @@ fn helper_output_doc() { "aggregate": "mean" } }) - .into() + .stage() ); assert_eq!( doc! { @@ -109,7 +109,7 @@ fn helper_output_doc() { } } }, - equals("verified_user", true).into() + equals("verified_user", true).stage() ); let gte_dt = DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); let lte_dt = DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); @@ -144,7 +144,7 @@ fn helper_output_doc() { "yearFacet": facet::number("year", [2000, 2005, 2010, 2015]), }) .operator(range("released").gte(gte_dt).lte(lte_dt)) - .into() + .stage() ); assert_eq!( doc! { @@ -173,7 +173,7 @@ fn helper_output_doc() { [-161.323242,22.512557]]] } ) - .into() + .stage() ); assert_eq!( doc! { @@ -204,7 +204,7 @@ fn helper_output_doc() { "coordinates": [168.000, -9.133] } }) - .into() + .stage() ); assert_eq!( doc! { @@ -215,7 +215,7 @@ fn helper_output_doc() { } } }, - search_in("accounts", [371138, 371139, 371140].as_ref()).into() + search_in("accounts", [371138, 371139, 371140].as_ref()).stage() ); assert_eq!( doc! { @@ -232,7 +232,7 @@ fn helper_output_doc() { "title": "The Godfather", "genres": "action" }) - .into() + .stage() ); } @@ -240,6 +240,7 @@ fn helper_output_doc() { fn string_or_array_forms() { exists("hello"); exists("hello".to_owned()); + #[cfg(feature = "bson-3")] exists(["hello", "world"]); exists(&["hello", "world"] as &[&str]); exists(&["hello".to_owned()] as &[String]); From ed4270aef21019f30148f8f53daf0fe6a15a2aa2 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 11:45:01 +0100 Subject: [PATCH 14/32] near --- etc/gen_atlas_search/src/main.rs | 14 +++++++-- src/atlas_search.rs | 17 ++++++++++- src/atlas_search/gen.rs | 28 ++++++++++++++++++ src/test/atlas_search.rs | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index d77cfc8bc..2870652d7 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -153,7 +153,7 @@ impl Argument { ("equals", "value") => return ArgumentRustType::IntoBson, ("geoShape", "relation") => return ArgumentRustType::Relation, ("range", "gt" | "gte" | "lt" | "lte") => return ArgumentRustType::IntoBson, - + ("near", "origin") => return ArgumentRustType::NearOrigin, _ => (), } use ArgumentType::*; @@ -168,17 +168,20 @@ impl Argument { [Geometry] => ArgumentRustType::Document, [Any, Array] => ArgumentRustType::IntoBson, [Object, Array] => ArgumentRustType::DocumentOrArray, + [Number] => ArgumentRustType::BsonNumber, _ => panic!("Unexpected argument types: {:?}", self.type_), } } } enum ArgumentRustType { + BsonNumber, Document, DocumentOrArray, I32, IntoBson, MatchCriteria, + NearOrigin, Relation, SearchOperator, SeachOperatorIter, @@ -190,11 +193,13 @@ enum ArgumentRustType { impl ArgumentRustType { fn tokens(&self) -> syn::Type { match self { + Self::BsonNumber => parse_quote! { impl BsonNumber }, Self::Document => parse_quote! { Document }, Self::DocumentOrArray => parse_quote! { impl DocumentOrArray }, Self::I32 => parse_quote! { i32 }, Self::IntoBson => parse_quote! { impl Into }, Self::MatchCriteria => parse_quote! { MatchCriteria }, + Self::NearOrigin => parse_quote! { impl NearOrigin }, Self::Relation => parse_quote! { Relation }, Self::SearchOperator => parse_quote! { impl SearchOperator }, Self::SeachOperatorIter => { @@ -214,7 +219,11 @@ impl ArgumentRustType { parse_quote! { #ident.into_iter().map(|o| o.to_bson()).collect::>() } } Self::String => parse_quote! { #ident.as_ref() }, - Self::StringOrArray | Self::DocumentOrArray | Self::SearchOperator => { + Self::StringOrArray + | Self::DocumentOrArray + | Self::SearchOperator + | Self::NearOrigin + | Self::BsonNumber => { parse_quote! { #ident.to_bson() } } Self::TokenOrder | Self::MatchCriteria | Self::Relation => { @@ -250,6 +259,7 @@ fn main() { "geoWithin", "in", "moreLikeThis", + "near", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index c7f3ce49b..0dceb1b8e 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -319,5 +319,20 @@ impl DocumentOrArray for Document {} //impl DocumentOrArray for [Document; N] {} impl DocumentOrArray for &[Document] {} +/// An Atlas Search operator parameter that can be a date, number, or GeoJSON point. +pub trait NearOrigin: private::Parameter {} +impl NearOrigin for crate::bson::DateTime {} +impl NearOrigin for i32 {} +impl NearOrigin for i64 {} +impl NearOrigin for u32 {} +impl NearOrigin for f32 {} +impl NearOrigin for f64 {} +impl NearOrigin for Document {} + /// An Atlas Search operator parameter that can be any BSON numeric type. -pub trait BsonNumber {} +pub trait BsonNumber: private::Parameter {} +impl BsonNumber for i32 {} +impl BsonNumber for i64 {} +impl BsonNumber for u32 {} +impl BsonNumber for f32 {} +impl BsonNumber for f64 {} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index da6e44afd..f13f48a81 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -363,6 +363,34 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Near; +/**The near operator supports querying and scoring numeric, date, and GeoJSON point values. + * */ +/// +///For more details, see the [near operator reference](https://www.mongodb.com/docs/atlas/atlas-search/near/). +pub fn near( + path: impl StringOrArray, + origin: impl NearOrigin, + pivot: impl BsonNumber, +) -> AtlasSearch { + AtlasSearch { + name: "near", + stage: doc! { + "path" : path.to_bson(), "origin" : origin.to_bson(), "pivot" : pivot + .to_bson(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index cc024b5f9..7618fa708 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -234,6 +234,56 @@ fn helper_output_doc() { }) .stage() ); + assert_eq!( + doc! { + "$search": { + "index": "runtimes", + "near": { + "path": "year", + "origin": 2000, + "pivot": 2 + } + } + }, + near("year", 2000, 2).on_index("runtimes") + ); + let dt = DateTime::parse_rfc3339_str("1915-09-13T00:00:00.000+00:00").unwrap(); + assert_eq!( + doc! { + "$search": { + "index": "releaseddate", + "near": { + "path": "released", + "origin": dt, + "pivot": 7776000000i64 + } + } + }, + near("released", dt, 7776000000i64).on_index("releaseddate") + ); + assert_eq!( + doc! { + "$search": { + "near": { + "origin": { + "type": "Point", + "coordinates": [-8.61308, 41.1413] + }, + "pivot": 1000, + "path": "address.location" + } + } + }, + near( + "address.location", + doc! { + "type": "Point", + "coordinates": [-8.61308, 41.1413] + }, + 1000, + ) + .stage() + ); } #[test] From 8c618025564461a81362df6dc288dad227e86b7e Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 11:50:27 +0100 Subject: [PATCH 15/32] phrase --- etc/gen_atlas_search/src/main.rs | 2 ++ src/atlas_search/gen.rs | 34 ++++++++++++++++++++++++++++++++ src/test/atlas_search.rs | 23 +++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 2870652d7..f2a6c14c3 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -169,6 +169,7 @@ impl Argument { [Any, Array] => ArgumentRustType::IntoBson, [Object, Array] => ArgumentRustType::DocumentOrArray, [Number] => ArgumentRustType::BsonNumber, + [String, Array] => ArgumentRustType::StringOrArray, _ => panic!("Unexpected argument types: {:?}", self.type_), } } @@ -260,6 +261,7 @@ fn main() { "in", "moreLikeThis", "near", + "phrase", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index f13f48a81..d164f45e4 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -391,6 +391,40 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Phrase; +/**The phrase operator performs search for documents containing an ordered sequence of terms + * using the analyzer specified in the index configuration. + * */ +/// +///For more details, see the [phrase operator reference](https://www.mongodb.com/docs/atlas/atlas-search/phrase/). +pub fn phrase(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { + AtlasSearch { + name: "phrase", + stage: doc! { + "path" : path.to_bson(), "query" : query.to_bson(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn slop(mut self, slop: i32) -> Self { + self.stage.insert("slop", slop); + self + } + #[allow(missing_docs)] + pub fn synonyms(mut self, synonyms: impl AsRef) -> Self { + self.stage.insert("synonyms", synonyms.as_ref()); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 7618fa708..b8cd5449d 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -284,6 +284,29 @@ fn helper_output_doc() { ) .stage() ); + assert_eq!( + doc! { + "$search": { + "phrase": { + "path": "title", + "query": "new york" + } + } + }, + phrase("title", "new york").stage() + ); + #[cfg(feature = "bson-3")] + assert_eq!( + doc! { + "$search": { + "phrase": { + "path": "title", + "query": ["the man", "the moon"] + } + } + }, + phrase("title", ["the man", "the moon"]).stage() + ); } #[test] From 81c1da01d6c2662be3081bc43d1622c645bd70ba Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 11:53:05 +0100 Subject: [PATCH 16/32] queryString --- etc/gen_atlas_search/src/main.rs | 1 + src/atlas_search/gen.rs | 19 +++++++++++++++++++ src/test/atlas_search.rs | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index f2a6c14c3..114aa70c8 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -262,6 +262,7 @@ fn main() { "moreLikeThis", "near", "phrase", + "queryString", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index d164f45e4..93c50db29 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -425,6 +425,25 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct QueryString; +/// +/// +///For more details, see the [queryString operator reference](https://www.mongodb.com/docs/atlas/atlas-search/queryString/). +pub fn query_string( + default_path: impl StringOrArray, + query: impl AsRef, +) -> AtlasSearch { + AtlasSearch { + name: "queryString", + stage: doc! { + "defaultPath" : default_path.to_bson(), "query" : query.as_ref(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch {} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index b8cd5449d..be841c3c1 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -307,6 +307,17 @@ fn helper_output_doc() { }, phrase("title", ["the man", "the moon"]).stage() ); + assert_eq!( + doc! { + "$search": { + "queryString": { + "defaultPath": "title", + "query": "Rocky AND (IV OR 4 OR Four)" + } + } + }, + query_string("title", "Rocky AND (IV OR 4 OR Four)").stage() + ); } #[test] From 188f5061eefcf6bc0f2aa4f9292ca1b2f845d5fd Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 12:02:31 +0100 Subject: [PATCH 17/32] improved range --- etc/gen_atlas_search/src/main.rs | 7 ++- src/atlas_search.rs | 35 ++++++++----- src/atlas_search/gen.rs | 88 ++++++++++++++++---------------- 3 files changed, 72 insertions(+), 58 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 114aa70c8..1bc222b1a 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -152,7 +152,7 @@ impl Argument { ("text", "matchCriteria") => return ArgumentRustType::MatchCriteria, ("equals", "value") => return ArgumentRustType::IntoBson, ("geoShape", "relation") => return ArgumentRustType::Relation, - ("range", "gt" | "gte" | "lt" | "lte") => return ArgumentRustType::IntoBson, + ("range", "gt" | "gte" | "lt" | "lte") => return ArgumentRustType::RangeValue, ("near", "origin") => return ArgumentRustType::NearOrigin, _ => (), } @@ -183,6 +183,7 @@ enum ArgumentRustType { IntoBson, MatchCriteria, NearOrigin, + RangeValue, Relation, SearchOperator, SeachOperatorIter, @@ -201,6 +202,7 @@ impl ArgumentRustType { Self::IntoBson => parse_quote! { impl Into }, Self::MatchCriteria => parse_quote! { MatchCriteria }, Self::NearOrigin => parse_quote! { impl NearOrigin }, + Self::RangeValue => parse_quote! { impl RangeValue }, Self::Relation => parse_quote! { Relation }, Self::SearchOperator => parse_quote! { impl SearchOperator }, Self::SeachOperatorIter => { @@ -224,6 +226,7 @@ impl ArgumentRustType { | Self::DocumentOrArray | Self::SearchOperator | Self::NearOrigin + | Self::RangeValue | Self::BsonNumber => { parse_quote! { #ident.to_bson() } } @@ -255,7 +258,6 @@ fn main() { "equals", "exists", "facet", - "range", "geoShape", "geoWithin", "in", @@ -263,6 +265,7 @@ fn main() { "near", "phrase", "queryString", + "range", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 0dceb1b8e..1e7065d6b 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -6,7 +6,7 @@ pub use gen::*; use std::marker::PhantomData; -use crate::bson::{doc, Bson, Document}; +use crate::bson::{doc, Bson, DateTime, Document}; /// A helper to build the aggregation stage for Atlas Search. Use one of the constructor functions /// and chain optional value setters, and then convert to a pipeline stage [`Document`] via @@ -319,20 +319,31 @@ impl DocumentOrArray for Document {} //impl DocumentOrArray for [Document; N] {} impl DocumentOrArray for &[Document] {} +macro_rules! numeric { + ($trait:ty) => { + impl $trait for i32 {} + impl $trait for i64 {} + impl $trait for u32 {} + impl $trait for f32 {} + impl $trait for f64 {} + }; +} + /// An Atlas Search operator parameter that can be a date, number, or GeoJSON point. pub trait NearOrigin: private::Parameter {} -impl NearOrigin for crate::bson::DateTime {} -impl NearOrigin for i32 {} -impl NearOrigin for i64 {} -impl NearOrigin for u32 {} -impl NearOrigin for f32 {} -impl NearOrigin for f64 {} +impl NearOrigin for DateTime {} impl NearOrigin for Document {} +numeric! { NearOrigin } /// An Atlas Search operator parameter that can be any BSON numeric type. pub trait BsonNumber: private::Parameter {} -impl BsonNumber for i32 {} -impl BsonNumber for i64 {} -impl BsonNumber for u32 {} -impl BsonNumber for f32 {} -impl BsonNumber for f64 {} +numeric! { BsonNumber } + +/// At Atlas Search operator parameter that can be compared using [`range`]. +pub trait RangeValue: private::Parameter {} +numeric! { RangeValue } +impl RangeValue for DateTime {} +impl RangeValue for &str {} +impl RangeValue for &String {} +impl RangeValue for String {} +impl RangeValue for crate::bson::oid::ObjectId {} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 93c50db29..9e3e66930 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -203,50 +203,6 @@ impl AtlasSearch { } } #[allow(missing_docs)] -pub struct Range; -/**The range operator supports querying and scoring numeric, date, and string values. -You can use this operator to find results that are within a given numeric, date, objectId, or letter (from the English alphabet) range. -*/ -/// -///For more details, see the [range operator reference](https://www.mongodb.com/docs/atlas/atlas-search/range/). -pub fn range(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch { - name: "range", - stage: doc! { - "path" : path.to_bson(), - }, - meta: false, - _t: PhantomData, - } -} -impl AtlasSearch { - #[allow(missing_docs)] - pub fn gt(mut self, gt: impl Into) -> Self { - self.stage.insert("gt", gt.into()); - self - } - #[allow(missing_docs)] - pub fn gte(mut self, gte: impl Into) -> Self { - self.stage.insert("gte", gte.into()); - self - } - #[allow(missing_docs)] - pub fn lt(mut self, lt: impl Into) -> Self { - self.stage.insert("lt", lt.into()); - self - } - #[allow(missing_docs)] - pub fn lte(mut self, lte: impl Into) -> Self { - self.stage.insert("lte", lte.into()); - self - } - #[allow(missing_docs)] - pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); - self - } -} -#[allow(missing_docs)] pub struct GeoShape; /**The geoShape operator supports querying shapes with a relation to a given geometry if indexShapes is set to true in the index definition. @@ -444,6 +400,50 @@ pub fn query_string( } impl AtlasSearch {} #[allow(missing_docs)] +pub struct Range; +/**The range operator supports querying and scoring numeric, date, and string values. +You can use this operator to find results that are within a given numeric, date, objectId, or letter (from the English alphabet) range. +*/ +/// +///For more details, see the [range operator reference](https://www.mongodb.com/docs/atlas/atlas-search/range/). +pub fn range(path: impl StringOrArray) -> AtlasSearch { + AtlasSearch { + name: "range", + stage: doc! { + "path" : path.to_bson(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn gt(mut self, gt: impl RangeValue) -> Self { + self.stage.insert("gt", gt.to_bson()); + self + } + #[allow(missing_docs)] + pub fn gte(mut self, gte: impl RangeValue) -> Self { + self.stage.insert("gte", gte.to_bson()); + self + } + #[allow(missing_docs)] + pub fn lt(mut self, lt: impl RangeValue) -> Self { + self.stage.insert("lt", lt.to_bson()); + self + } + #[allow(missing_docs)] + pub fn lte(mut self, lte: impl RangeValue) -> Self { + self.stage.insert("lte", lte.to_bson()); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. From 4cd138e9914409dfe01a6c133333865366c1f88d Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 12:06:00 +0100 Subject: [PATCH 18/32] regex --- etc/gen_atlas_search/src/main.rs | 6 +++++- src/atlas_search/gen.rs | 30 ++++++++++++++++++++++++++++++ src/test/atlas_search.rs | 11 +++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 1bc222b1a..bb972cd84 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -170,12 +170,14 @@ impl Argument { [Object, Array] => ArgumentRustType::DocumentOrArray, [Number] => ArgumentRustType::BsonNumber, [String, Array] => ArgumentRustType::StringOrArray, + [Bool] => ArgumentRustType::Bool, _ => panic!("Unexpected argument types: {:?}", self.type_), } } } enum ArgumentRustType { + Bool, BsonNumber, Document, DocumentOrArray, @@ -195,6 +197,7 @@ enum ArgumentRustType { impl ArgumentRustType { fn tokens(&self) -> syn::Type { match self { + Self::Bool => parse_quote! { bool }, Self::BsonNumber => parse_quote! { impl BsonNumber }, Self::Document => parse_quote! { Document }, Self::DocumentOrArray => parse_quote! { impl DocumentOrArray }, @@ -216,7 +219,7 @@ impl ArgumentRustType { fn bson_expr(&self, ident: &syn::Ident) -> syn::Expr { match self { - Self::Document | Self::I32 => parse_quote! { #ident }, + Self::Document | Self::I32 | Self::Bool => parse_quote! { #ident }, Self::IntoBson => parse_quote! { #ident.into() }, Self::SeachOperatorIter => { parse_quote! { #ident.into_iter().map(|o| o.to_bson()).collect::>() } @@ -266,6 +269,7 @@ fn main() { "phrase", "queryString", "range", + "regex", "text", ] { let mut path = PathBuf::from("yaml/search"); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 9e3e66930..dce580293 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -444,6 +444,36 @@ impl AtlasSearch { } } #[allow(missing_docs)] +pub struct Regex; +/**regex interprets the query field as a regular expression. +regex is a term-level operator, meaning that the query field isn't analyzed. +*/ +/// +///For more details, see the [regex operator reference](https://www.mongodb.com/docs/atlas/atlas-search/regex/). +pub fn regex(path: impl StringOrArray, query: impl AsRef) -> AtlasSearch { + AtlasSearch { + name: "regex", + stage: doc! { + "path" : path.to_bson(), "query" : query.as_ref(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn allow_analyzed_field(mut self, allow_analyzed_field: bool) -> Self { + self.stage + .insert("allowAnalyzedField", allow_analyzed_field); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} +#[allow(missing_docs)] pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index be841c3c1..32e015d4c 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -318,6 +318,17 @@ fn helper_output_doc() { }, query_string("title", "Rocky AND (IV OR 4 OR Four)").stage() ); + assert_eq!( + doc! { + "$search": { + "regex": { + "path": "title", + "query": "(.*) Seattle" + } + } + }, + regex("title", "(.*) Seattle").stage() + ); } #[test] From 2b733f6dc84d2f8abdb837caaf0e3d52c93d03cc Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 12:10:42 +0100 Subject: [PATCH 19/32] wildcard --- etc/gen_atlas_search/src/main.rs | 1 + src/atlas_search/gen.rs | 30 ++++++++++++++++++++++++++++++ src/test/atlas_search.rs | 11 +++++++++++ 3 files changed, 42 insertions(+) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index bb972cd84..78ec188d6 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -271,6 +271,7 @@ fn main() { "range", "regex", "text", + "wildcard", ] { let mut path = PathBuf::from("yaml/search"); path.push(name); diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index dce580293..7e024c216 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -512,3 +512,33 @@ impl AtlasSearch { self } } +#[allow(missing_docs)] +pub struct Wildcard; +/**The wildcard operator enables queries which use special characters in the search string that + * can match any character. + * */ +/// +///For more details, see the [wildcard operator reference](https://www.mongodb.com/docs/atlas/atlas-search/wildcard/). +pub fn wildcard(path: impl StringOrArray, query: impl AsRef) -> AtlasSearch { + AtlasSearch { + name: "wildcard", + stage: doc! { + "path" : path.to_bson(), "query" : query.as_ref(), + }, + meta: false, + _t: PhantomData, + } +} +impl AtlasSearch { + #[allow(missing_docs)] + pub fn allow_analyzed_field(mut self, allow_analyzed_field: bool) -> Self { + self.stage + .insert("allowAnalyzedField", allow_analyzed_field); + self + } + #[allow(missing_docs)] + pub fn score(mut self, score: Document) -> Self { + self.stage.insert("score", score); + self + } +} diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 32e015d4c..cde4f9d7c 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -329,6 +329,17 @@ fn helper_output_doc() { }, regex("title", "(.*) Seattle").stage() ); + assert_eq!( + doc! { + "$search": { + "wildcard": { + "query": "Green D*", + "path": "title" + } + } + }, + wildcard("title", "Green D*").stage() + ); } #[test] From 763779d922967bd148f5922b8c732b19462e2ae4 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 12:13:58 +0100 Subject: [PATCH 20/32] uncomment impl --- src/atlas_search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 1e7065d6b..9d4aad55b 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -316,7 +316,8 @@ impl Relation { /// An Atlas Search operator parameter that can be either a document or array of documents. pub trait DocumentOrArray: private::Parameter {} impl DocumentOrArray for Document {} -//impl DocumentOrArray for [Document; N] {} +#[cfg(feature = "bson-3")] +impl DocumentOrArray for [Document; N] {} impl DocumentOrArray for &[Document] {} macro_rules! numeric { From 68f77c62bc8fa48d98d4c478b2318826e6c85d41 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 12:27:34 +0100 Subject: [PATCH 21/32] directory scan --- etc/gen_atlas_search/src/main.rs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 78ec188d6..93e3d62c7 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::Path; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; @@ -254,28 +254,14 @@ impl TokenStreamExt for TokenStream { fn main() { let mut operators = TokenStream::new(); - for name in [ - "autocomplete", - "compound", - "embeddedDocument", - "equals", - "exists", - "facet", - "geoShape", - "geoWithin", - "in", - "moreLikeThis", - "near", - "phrase", - "queryString", - "range", - "regex", - "text", - "wildcard", - ] { - let mut path = PathBuf::from("yaml/search"); - path.push(name); - path.set_extension("yaml"); + let mut paths = Path::new("yaml/search") + .read_dir() + .unwrap() + .map(|e| e.unwrap().path()) + .filter(|p| p.extension().is_some_and(|e| e == "yaml")) + .collect::>(); + paths.sort(); + for path in paths { let contents = std::fs::read_to_string(path).unwrap(); let parsed = serde_yaml::from_str::(&contents) .unwrap() From c4acd900de8676506564a50fb9acb5264ee4a192 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Fri, 29 Aug 2025 12:47:45 +0100 Subject: [PATCH 22/32] fix docs --- src/atlas_search.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 9d4aad55b..39a2f6258 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -10,7 +10,7 @@ use crate::bson::{doc, Bson, DateTime, Document}; /// A helper to build the aggregation stage for Atlas Search. Use one of the constructor functions /// and chain optional value setters, and then convert to a pipeline stage [`Document`] via -/// [`into`](Into::into) or [`on_index`](AtlasSearch::on_index). +/// [`stage`](AtlasSearch::stage) or [`on_index`](AtlasSearch::on_index). /// /// ```no_run /// # async fn wrapper() -> mongodb::error::Result<()> { @@ -20,7 +20,7 @@ use crate::bson::{doc, Bson, DateTime, Document}; /// let cursor = collection.aggregate(vec![ /// atlas_search::autocomplete("title", "pre") /// .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) -/// .into(), +/// .stage(), /// doc! { /// "$limit": 10, /// }, @@ -56,7 +56,7 @@ impl AtlasSearch { /// .should(atlas_search::text("description", "Fuji")) /// .unit(), /// ]) - /// .into(), + /// .stage(), /// ]).await?; /// # } /// ``` @@ -191,6 +191,7 @@ impl AtlasSearch { /// Facet definitions. These can be used when constructing a facet definition doc: /// ``` +/// # use mongodb::bson::doc; /// use mongodb::atlas_search::facet; /// let search = facet(doc! { /// "directorsFacet": facet::string("directors").num_buckets(7), From a1ac81bb9a6b5e942f7b2a347a4633f7859b0c5d Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 2 Sep 2025 10:50:56 +0100 Subject: [PATCH 23/32] toplevel options --- etc/gen_atlas_search/src/main.rs | 14 +-- src/atlas_search.rs | 115 +++++++++++++++++---- src/atlas_search/gen.rs | 167 ++++++++++++------------------- src/test/atlas_search.rs | 6 +- 4 files changed, 167 insertions(+), 135 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 93e3d62c7..e1facc994 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -87,10 +87,6 @@ impl Operator { "For more details, see the [{name_text} operator reference]({}).", self.link ); - let meta: TokenStream = match self.name.as_str() { - "facet" => parse_quote! { true }, - _ => parse_quote! { false }, - }; parse_quote! { #[allow(missing_docs)] pub struct #name_ident; @@ -99,12 +95,10 @@ impl Operator { #[doc = ""] #[doc = #link] pub fn #constr_ident(#required_args) -> AtlasSearch<#name_ident> { - AtlasSearch { - name: #name_text, - stage: doc! { #init_doc }, - meta: #meta, - _t: PhantomData, - } + AtlasSearch::new( + #name_text, + doc! { #init_doc }, + ) } impl AtlasSearch<#name_ident> { diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 39a2f6258..7bfe6c932 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -37,10 +37,103 @@ pub struct AtlasSearch { name: &'static str, stage: Document, meta: bool, + options: Document, _t: PhantomData, } impl AtlasSearch { + fn new(name: &'static str, stage: Document) -> Self { + Self { + name, + stage, + meta: false, + options: doc! {}, + _t: PhantomData, + } + } + + /// Whether to use the `$searchMeta` stage instead of the default `$search` stage. + /// + /// Note that only the [`count`](AtlasSearch::count) and [`index`](AtlasSearch::index) options + /// are valid for `$searchMeta`. + pub fn meta(mut self, value: bool) -> Self { + self.meta = value; + self + } + + /// Parallelize search across segments on dedicated search nodes. + pub fn concurrent(mut self, value: bool) -> Self { + self.options.insert("concurrent", value); + self + } + + /// Document that specifies the count options for retrieving a count of the results. + pub fn count(mut self, value: Document) -> Self { + self.options.insert("count", value); + self + } + + /// Document that specifies the highlighting options for displaying search terms in their + /// original context. + pub fn highlight(mut self, value: Document) -> Self { + self.options.insert("highlight", value); + self + } + + /// Name of the Atlas Search index to use. + pub fn index(mut self, value: impl Into) -> Self { + self.options.insert("index", value.into()); + self + } + + /// Flag that specifies whether to perform a full document lookup on the backend database or + /// return only stored source fields directly from Atlas Search. + pub fn return_stored_source(mut self, value: bool) -> Self { + self.options.insert("returnStoredSource", value); + self + } + + /// Reference point for retrieving results. + pub fn search_after(mut self, value: impl Into) -> Self { + self.options.insert("searchAfter", value.into()); + self + } + + /// Reference point for retrieving results. + pub fn search_before(mut self, value: impl Into) -> Self { + self.options.insert("searchBefore", value.into()); + self + } + + /// Flag that specifies whether to retrieve a detailed breakdown of the score for the documents + /// in the results. + pub fn score_details(mut self, value: bool) -> Self { + self.options.insert("scoreDetails", value); + self + } + + /// Document that specifies the fields to sort the Atlas Search results by in ascending or + /// descending order. + pub fn sort(mut self, value: Document) -> Self { + self.options.insert("sort", value); + self + } + + /// Document that specifies the tracking option to retrieve analytics information on the search + /// terms. + pub fn tracking(mut self, value: Document) -> Self { + self.options.insert("tracking", value); + self + } + + /// Converts this builder into an aggregate pipeline stage [`Document`]. + pub fn stage(self) -> Document { + let mut inner = self.options; + inner.insert(self.name, self.stage); + let key = if self.meta { "$searchMeta" } else { "$search" }; + doc! { key: inner } + } + /// Erase the type of this builder. Not typically needed, but can be useful to include builders /// of different types in a single `Vec`: /// ```no_run @@ -65,30 +158,10 @@ impl AtlasSearch { name: self.name, stage: self.stage, meta: self.meta, + options: self.options, _t: PhantomData, } } - - /// Converts this builder into an aggregate pipeline stage [`Document`]. - pub fn stage(self) -> Document { - let key = if self.meta { "$searchMeta" } else { "$search" }; - doc! { - key: { - self.name: self.stage - } - } - } - - /// Like [`stage`](AtlasSearch::stage), converts this builder into an aggregate pipeline stage - /// [`Document`], but also specify the search index to use. - pub fn on_index(self, index: impl AsRef) -> Document { - doc! { - "$search": { - "index": index.as_ref(), - self.name: self.stage, - } - } - } } impl IntoIterator for AtlasSearch { diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 7e024c216..585ed0885 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -13,14 +13,12 @@ pub fn autocomplete( path: impl StringOrArray, query: impl StringOrArray, ) -> AtlasSearch { - AtlasSearch { - name: "autocomplete", - stage: doc! { + AtlasSearch::new( + "autocomplete", + doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -48,12 +46,7 @@ consists of one or more sub-queries. /// ///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). pub fn compound() -> AtlasSearch { - AtlasSearch { - name: "compound", - stage: doc! {}, - meta: false, - _t: PhantomData, - } + AtlasSearch::new("compound", doc! {}) } impl AtlasSearch { #[allow(missing_docs)] @@ -116,14 +109,12 @@ pub fn embedded_document( path: impl StringOrArray, operator: impl SearchOperator, ) -> AtlasSearch { - AtlasSearch { - name: "embeddedDocument", - stage: doc! { + AtlasSearch::new( + "embeddedDocument", + doc! { "path" : path.to_bson(), "operator" : operator.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -139,14 +130,12 @@ pub struct Equals; /// ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). pub fn equals(path: impl StringOrArray, value: impl Into) -> AtlasSearch { - AtlasSearch { - name: "equals", - stage: doc! { + AtlasSearch::new( + "equals", + doc! { "path" : path.to_bson(), "value" : value.into(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -162,14 +151,12 @@ pub struct Exists; /// ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). pub fn exists(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch { - name: "exists", - stage: doc! { + AtlasSearch::new( + "exists", + doc! { "path" : path.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -186,14 +173,12 @@ faceted fields and returns the count for each of those groups. /// ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). pub fn facet(facets: Document) -> AtlasSearch { - AtlasSearch { - name: "facet", - stage: doc! { + AtlasSearch::new( + "facet", + doc! { "facets" : facets, }, - meta: true, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -214,14 +199,12 @@ pub fn geo_shape( relation: Relation, geometry: Document, ) -> AtlasSearch { - AtlasSearch { - name: "geoShape", - stage: doc! { + AtlasSearch::new( + "geoShape", + doc! { "path" : path.to_bson(), "relation" : relation.name(), "geometry" : geometry, }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -239,14 +222,12 @@ the index definition. /// ///For more details, see the [geoWithin operator reference](https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/). pub fn geo_within(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch { - name: "geoWithin", - stage: doc! { + AtlasSearch::new( + "geoWithin", + doc! { "path" : path.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -277,14 +258,12 @@ pub struct SearchIn; /// ///For more details, see the [in operator reference](https://www.mongodb.com/docs/atlas/atlas-search/in/). pub fn search_in(path: impl StringOrArray, value: impl Into) -> AtlasSearch { - AtlasSearch { - name: "in", - stage: doc! { + AtlasSearch::new( + "in", + doc! { "path" : path.to_bson(), "value" : value.into(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -302,14 +281,12 @@ that display similar or alternative results based on one or more given documents /// ///For more details, see the [moreLikeThis operator reference](https://www.mongodb.com/docs/atlas/atlas-search/moreLikeThis/). pub fn more_like_this(like: impl DocumentOrArray) -> AtlasSearch { - AtlasSearch { - name: "moreLikeThis", - stage: doc! { + AtlasSearch::new( + "moreLikeThis", + doc! { "like" : like.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -329,15 +306,13 @@ pub fn near( origin: impl NearOrigin, pivot: impl BsonNumber, ) -> AtlasSearch { - AtlasSearch { - name: "near", - stage: doc! { + AtlasSearch::new( + "near", + doc! { "path" : path.to_bson(), "origin" : origin.to_bson(), "pivot" : pivot .to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -354,14 +329,12 @@ pub struct Phrase; /// ///For more details, see the [phrase operator reference](https://www.mongodb.com/docs/atlas/atlas-search/phrase/). pub fn phrase(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { - AtlasSearch { - name: "phrase", - stage: doc! { + AtlasSearch::new( + "phrase", + doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -389,14 +362,12 @@ pub fn query_string( default_path: impl StringOrArray, query: impl AsRef, ) -> AtlasSearch { - AtlasSearch { - name: "queryString", - stage: doc! { + AtlasSearch::new( + "queryString", + doc! { "defaultPath" : default_path.to_bson(), "query" : query.as_ref(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch {} #[allow(missing_docs)] @@ -407,14 +378,12 @@ You can use this operator to find results that are within a given numeric, date, /// ///For more details, see the [range operator reference](https://www.mongodb.com/docs/atlas/atlas-search/range/). pub fn range(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch { - name: "range", - stage: doc! { + AtlasSearch::new( + "range", + doc! { "path" : path.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -451,14 +420,12 @@ regex is a term-level operator, meaning that the query field isn't analyzed. /// ///For more details, see the [regex operator reference](https://www.mongodb.com/docs/atlas/atlas-search/regex/). pub fn regex(path: impl StringOrArray, query: impl AsRef) -> AtlasSearch { - AtlasSearch { - name: "regex", - stage: doc! { + AtlasSearch::new( + "regex", + doc! { "path" : path.to_bson(), "query" : query.as_ref(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -481,14 +448,12 @@ If you omit an analyzer, the text operator uses the default standard analyzer. /// ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { - AtlasSearch { - name: "text", - stage: doc! { + AtlasSearch::new( + "text", + doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] @@ -520,14 +485,12 @@ pub struct Wildcard; /// ///For more details, see the [wildcard operator reference](https://www.mongodb.com/docs/atlas/atlas-search/wildcard/). pub fn wildcard(path: impl StringOrArray, query: impl AsRef) -> AtlasSearch { - AtlasSearch { - name: "wildcard", - stage: doc! { + AtlasSearch::new( + "wildcard", + doc! { "path" : path.to_bson(), "query" : query.as_ref(), }, - meta: false, - _t: PhantomData, - } + ) } impl AtlasSearch { #[allow(missing_docs)] diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index cde4f9d7c..dd936a9f4 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -245,7 +245,7 @@ fn helper_output_doc() { } } }, - near("year", 2000, 2).on_index("runtimes") + near("year", 2000, 2).index("runtimes").stage() ); let dt = DateTime::parse_rfc3339_str("1915-09-13T00:00:00.000+00:00").unwrap(); assert_eq!( @@ -259,7 +259,9 @@ fn helper_output_doc() { } } }, - near("released", dt, 7776000000i64).on_index("releaseddate") + near("released", dt, 7776000000i64) + .index("releaseddate") + .stage() ); assert_eq!( doc! { From 4517a667b39e5fca13ceba7ee796d1412689f5d3 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 2 Sep 2025 14:40:36 +0100 Subject: [PATCH 24/32] distinct toplevel value --- etc/gen_atlas_search/src/main.rs | 12 +- macros/src/lib.rs | 11 ++ src/action/aggregate.rs | 34 +++--- src/atlas_search.rs | 183 +++++++++++++++++------------ src/atlas_search/gen.rs | 194 +++++++++++++++---------------- src/test/atlas_search.rs | 3 +- 6 files changed, 243 insertions(+), 194 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index e1facc994..737374567 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -71,7 +71,7 @@ impl Operator { setters.push(parse_quote! { #[allow(missing_docs)] pub fn #ident(mut self, #ident: #type_) -> Self { - self.stage.insert(#arg_name, #init_expr); + self.spec.insert(#arg_name, #init_expr); self } }); @@ -94,14 +94,14 @@ impl Operator { #[doc = #desc] #[doc = ""] #[doc = #link] - pub fn #constr_ident(#required_args) -> AtlasSearch<#name_ident> { - AtlasSearch::new( + pub fn #constr_ident(#required_args) -> SearchOperator<#name_ident> { + SearchOperator::new( #name_text, doc! { #init_doc }, ) } - impl AtlasSearch<#name_ident> { + impl SearchOperator<#name_ident> { #setters } } @@ -201,9 +201,9 @@ impl ArgumentRustType { Self::NearOrigin => parse_quote! { impl NearOrigin }, Self::RangeValue => parse_quote! { impl RangeValue }, Self::Relation => parse_quote! { Relation }, - Self::SearchOperator => parse_quote! { impl SearchOperator }, + Self::SearchOperator => parse_quote! { impl SearchOperatorParam }, Self::SeachOperatorIter => { - parse_quote! { impl IntoIterator } + parse_quote! { impl IntoIterator } } Self::String => parse_quote! { impl AsRef }, Self::StringOrArray => parse_quote! { impl StringOrArray }, diff --git a/macros/src/lib.rs b/macros/src/lib.rs index df8299391..293ffa510 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -30,6 +30,9 @@ pub fn deeplink( crate::rustdoc::deeplink(attr, item) } +/// Generate setters for the given options struct. +/// Arguments: +/// * the fully-qualified path of the struct type #[import_tokens_attr] #[with_custom_parsing(crate::option::OptionSettersArgs)] #[proc_macro_attribute] @@ -40,6 +43,10 @@ pub fn option_setters( crate::option::option_setters(attr, item, __custom_tokens) } +/// Export the setters in this `impl` block so they can be used in `options_doc`. +/// Arguments: +/// * an identifier for the exported list +/// * an optional `extra = [fn_name[,..]]` list of additional setters to include #[proc_macro_attribute] pub fn export_doc( attr: proc_macro::TokenStream, @@ -48,6 +55,10 @@ pub fn export_doc( crate::rustdoc::export_doc(attr, item) } +/// Include options documentation generated by `export_doc` in the rustdoc for this method: +/// Arguments: +/// * the doc identifier given to `export_doc` +/// * an optional `sync` keyword that alters the documentation to be appropriate for a sync action #[import_tokens_attr] #[with_custom_parsing(crate::rustdoc::OptionsDocArgs)] #[proc_macro_attribute] diff --git a/src/action/aggregate.rs b/src/action/aggregate.rs index deca70703..ccfbdea8d 100644 --- a/src/action/aggregate.rs +++ b/src/action/aggregate.rs @@ -40,13 +40,10 @@ impl Database { #[deeplink] #[options_doc(aggregate)] pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { - Aggregate { - target: AggregateTargetRef::Database(self), - pipeline: pipeline.into_iter().collect(), - options: None, - session: ImplicitSession, - _phantom: PhantomData, - } + Aggregate::new( + AggregateTargetRef::Database(self), + pipeline.into_iter().collect(), + ) } } @@ -65,13 +62,10 @@ where #[deeplink] #[options_doc(aggregate)] pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { - Aggregate { - target: AggregateTargetRef::Collection(CollRef::new(self)), - pipeline: pipeline.into_iter().collect(), - options: None, - session: ImplicitSession, - _phantom: PhantomData, - } + Aggregate::new( + AggregateTargetRef::Collection(CollRef::new(self)), + pipeline.into_iter().collect(), + ) } } @@ -125,6 +119,18 @@ pub struct Aggregate<'a, Session = ImplicitSession, T = Document> { _phantom: PhantomData, } +impl<'a> Aggregate<'a> { + fn new(target: AggregateTargetRef<'a>, pipeline: Vec) -> Self { + Self { + target, + pipeline, + options: None, + session: ImplicitSession, + _phantom: PhantomData, + } + } +} + #[option_setters(crate::coll::options::AggregateOptions)] #[export_doc(aggregate, extra = [session])] impl<'a, Session, T> Aggregate<'a, Session, T> { diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 7bfe6c932..24ad91b1c 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -10,7 +10,7 @@ use crate::bson::{doc, Bson, DateTime, Document}; /// A helper to build the aggregation stage for Atlas Search. Use one of the constructor functions /// and chain optional value setters, and then convert to a pipeline stage [`Document`] via -/// [`stage`](AtlasSearch::stage) or [`on_index`](AtlasSearch::on_index). +/// [`stage`](SearchOperator::stage). /// /// ```no_run /// # async fn wrapper() -> mongodb::error::Result<()> { @@ -33,141 +33,180 @@ use crate::bson::{doc, Bson, DateTime, Document}; /// ]).await?; /// # Ok(()) /// # } -pub struct AtlasSearch { - name: &'static str, - stage: Document, - meta: bool, - options: Document, +pub struct SearchOperator { + pub(crate) name: &'static str, + pub(crate) spec: Document, _t: PhantomData, } -impl AtlasSearch { - fn new(name: &'static str, stage: Document) -> Self { +impl SearchOperator { + fn new(name: &'static str, spec: Document) -> Self { Self { name, - stage, - meta: false, - options: doc! {}, + spec, _t: PhantomData, } } - /// Whether to use the `$searchMeta` stage instead of the default `$search` stage. - /// - /// Note that only the [`count`](AtlasSearch::count) and [`index`](AtlasSearch::index) options - /// are valid for `$searchMeta`. - pub fn meta(mut self, value: bool) -> Self { - self.meta = value; - self + /// Finalize this search operator as a pending `$search` aggregation stage, allowing + /// options to be set. + pub fn search(self) -> AtlasSearch { + AtlasSearch { + stage: doc! { self.name: self.spec }, + } } + /// Finalize this search operator as a `$search` aggregation stage document. + pub fn stage(self) -> Document { + self.search().stage() + } + + /// Finalize this search operator as a pending `$searchMeta` aggregation stage, allowing + /// options to be set. + pub fn search_meta(self) -> AtlasSearchMeta { + AtlasSearchMeta { + stage: doc! { self.name: self.spec }, + } + } + + /// Finalize this search operator as a `$searchMeta` aggregation stage document. + pub fn stage_meta(self) -> Document { + self.search_meta().stage() + } + + /// Erase the type of this builder. Not typically needed, but can be useful to include builders + /// of different types in a single `Vec`: + /// ```no_run + /// # async fn wrapper() -> mongodb::error::Result<()> { + /// # use mongodb::{Collection, bson::{Document, doc}}; + /// # let collection: Collection = todo!(); + /// use mongodb::atlas_search; + /// let cursor = collection.aggregate(vec![ + /// atlas_search::compound() + /// .must(vec![ + /// atlas_search::text("description", "varieties").unit(), + /// atlas_search::compound() + /// .should(atlas_search::text("description", "Fuji")) + /// .unit(), + /// ]) + /// .stage(), + /// ]).await?; + /// # } + /// ``` + pub fn unit(self) -> SearchOperator<()> { + SearchOperator { + name: self.name, + spec: self.spec, + _t: PhantomData, + } + } +} + +/// A pending `$search` aggregation stage. +pub struct AtlasSearch { + stage: Document, +} + +impl AtlasSearch { /// Parallelize search across segments on dedicated search nodes. pub fn concurrent(mut self, value: bool) -> Self { - self.options.insert("concurrent", value); + self.stage.insert("concurrent", value); self } /// Document that specifies the count options for retrieving a count of the results. pub fn count(mut self, value: Document) -> Self { - self.options.insert("count", value); + self.stage.insert("count", value); self } /// Document that specifies the highlighting options for displaying search terms in their /// original context. pub fn highlight(mut self, value: Document) -> Self { - self.options.insert("highlight", value); + self.stage.insert("highlight", value); self } /// Name of the Atlas Search index to use. pub fn index(mut self, value: impl Into) -> Self { - self.options.insert("index", value.into()); + self.stage.insert("index", value.into()); self } /// Flag that specifies whether to perform a full document lookup on the backend database or /// return only stored source fields directly from Atlas Search. pub fn return_stored_source(mut self, value: bool) -> Self { - self.options.insert("returnStoredSource", value); + self.stage.insert("returnStoredSource", value); self } /// Reference point for retrieving results. pub fn search_after(mut self, value: impl Into) -> Self { - self.options.insert("searchAfter", value.into()); + self.stage.insert("searchAfter", value.into()); self } /// Reference point for retrieving results. pub fn search_before(mut self, value: impl Into) -> Self { - self.options.insert("searchBefore", value.into()); + self.stage.insert("searchBefore", value.into()); self } /// Flag that specifies whether to retrieve a detailed breakdown of the score for the documents /// in the results. pub fn score_details(mut self, value: bool) -> Self { - self.options.insert("scoreDetails", value); + self.stage.insert("scoreDetails", value); self } /// Document that specifies the fields to sort the Atlas Search results by in ascending or /// descending order. pub fn sort(mut self, value: Document) -> Self { - self.options.insert("sort", value); + self.stage.insert("sort", value); self } /// Document that specifies the tracking option to retrieve analytics information on the search /// terms. pub fn tracking(mut self, value: Document) -> Self { - self.options.insert("tracking", value); + self.stage.insert("tracking", value); self } - /// Converts this builder into an aggregate pipeline stage [`Document`]. + /// Convert to an aggregation stage document. pub fn stage(self) -> Document { - let mut inner = self.options; - inner.insert(self.name, self.stage); - let key = if self.meta { "$searchMeta" } else { "$search" }; - doc! { key: inner } + doc! { "$search": self.stage } } +} - /// Erase the type of this builder. Not typically needed, but can be useful to include builders - /// of different types in a single `Vec`: - /// ```no_run - /// # async fn wrapper() -> mongodb::error::Result<()> { - /// # use mongodb::{Collection, bson::{Document, doc}}; - /// # let collection: Collection = todo!(); - /// use mongodb::atlas_search; - /// let cursor = collection.aggregate(vec![ - /// atlas_search::compound() - /// .must(vec![ - /// atlas_search::text("description", "varieties").unit(), - /// atlas_search::compound() - /// .should(atlas_search::text("description", "Fuji")) - /// .unit(), - /// ]) - /// .stage(), - /// ]).await?; - /// # } - /// ``` - pub fn unit(self) -> AtlasSearch<()> { - AtlasSearch { - name: self.name, - stage: self.stage, - meta: self.meta, - options: self.options, - _t: PhantomData, - } +/// A pending `$searchMeta` aggregation stage. +pub struct AtlasSearchMeta { + stage: Document, +} + +impl AtlasSearchMeta { + /// Document that specifies the count options for retrieving a count of the results. + pub fn count(mut self, value: Document) -> Self { + self.stage.insert("count", value); + self + } + + /// Name of the Atlas Search index to use. + pub fn index(mut self, value: impl Into) -> Self { + self.stage.insert("index", value.into()); + self + } + + /// Convert to an aggregation stage document. + pub fn stage(self) -> Document { + doc! { "$searchMeta": self.stage } } } -impl IntoIterator for AtlasSearch { - type Item = AtlasSearch; +impl IntoIterator for SearchOperator { + type Item = SearchOperator; - type IntoIter = std::iter::Once>; + type IntoIter = std::iter::Once>; fn into_iter(self) -> Self::IntoIter { std::iter::once(self) @@ -233,9 +272,9 @@ mod private { } } - impl Parameter for super::AtlasSearch { + impl Parameter for super::SearchOperator { fn to_bson(self) -> Bson { - Bson::Document(doc! { self.name: self.stage }) + Bson::Document(doc! { self.name: self.spec }) } } } @@ -250,17 +289,9 @@ impl StringOrArray for &[&str] {} impl StringOrArray for &[String] {} /// An Atlas Search operator parameter that is itself a search operator. -pub trait SearchOperator: private::Parameter {} -impl SearchOperator for AtlasSearch {} -impl SearchOperator for Document {} - -impl AtlasSearch { - /// Use the `$search` stage instead of the default `$searchMeta` stage. - pub fn search(mut self) -> Self { - self.meta = false; - self - } -} +pub trait SearchOperatorParam: private::Parameter {} +impl SearchOperatorParam for SearchOperator {} +impl SearchOperatorParam for Document {} /// Facet definitions. These can be used when constructing a facet definition doc: /// ``` diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 585ed0885..bb4968ada 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -12,28 +12,28 @@ indexed with the autocomplete data type in the collection's index definition. pub fn autocomplete( path: impl StringOrArray, query: impl StringOrArray, -) -> AtlasSearch { - AtlasSearch::new( +) -> SearchOperator { + SearchOperator::new( "autocomplete", doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn token_order(mut self, token_order: TokenOrder) -> Self { - self.stage.insert("tokenOrder", token_order.name()); + self.spec.insert("tokenOrder", token_order.name()); self } #[allow(missing_docs)] pub fn fuzzy(mut self, fuzzy: Document) -> Self { - self.stage.insert("fuzzy", fuzzy); + self.spec.insert("fuzzy", fuzzy); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -45,21 +45,24 @@ consists of one or more sub-queries. */ /// ///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). -pub fn compound() -> AtlasSearch { - AtlasSearch::new("compound", doc! {}) +pub fn compound() -> SearchOperator { + SearchOperator::new("compound", doc! {}) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] - pub fn must(mut self, must: impl IntoIterator) -> Self { - self.stage.insert( + pub fn must(mut self, must: impl IntoIterator) -> Self { + self.spec.insert( "must", must.into_iter().map(|o| o.to_bson()).collect::>(), ); self } #[allow(missing_docs)] - pub fn must_not(mut self, must_not: impl IntoIterator) -> Self { - self.stage.insert( + pub fn must_not( + mut self, + must_not: impl IntoIterator, + ) -> Self { + self.spec.insert( "mustNot", must_not .into_iter() @@ -69,16 +72,16 @@ impl AtlasSearch { self } #[allow(missing_docs)] - pub fn should(mut self, should: impl IntoIterator) -> Self { - self.stage.insert( + pub fn should(mut self, should: impl IntoIterator) -> Self { + self.spec.insert( "should", should.into_iter().map(|o| o.to_bson()).collect::>(), ); self } #[allow(missing_docs)] - pub fn filter(mut self, filter: impl IntoIterator) -> Self { - self.stage.insert( + pub fn filter(mut self, filter: impl IntoIterator) -> Self { + self.spec.insert( "filter", filter.into_iter().map(|o| o.to_bson()).collect::>(), ); @@ -86,13 +89,12 @@ impl AtlasSearch { } #[allow(missing_docs)] pub fn minimum_should_match(mut self, minimum_should_match: i32) -> Self { - self.stage - .insert("minimumShouldMatch", minimum_should_match); + self.spec.insert("minimumShouldMatch", minimum_should_match); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -107,19 +109,19 @@ for queries over fields of the embeddedDocuments ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). pub fn embedded_document( path: impl StringOrArray, - operator: impl SearchOperator, -) -> AtlasSearch { - AtlasSearch::new( + operator: impl SearchOperatorParam, +) -> SearchOperator { + SearchOperator::new( "embeddedDocument", doc! { "path" : path.to_bson(), "operator" : operator.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -129,18 +131,18 @@ pub struct Equals; * */ /// ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). -pub fn equals(path: impl StringOrArray, value: impl Into) -> AtlasSearch { - AtlasSearch::new( +pub fn equals(path: impl StringOrArray, value: impl Into) -> SearchOperator { + SearchOperator::new( "equals", doc! { "path" : path.to_bson(), "value" : value.into(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -150,18 +152,18 @@ pub struct Exists; * */ /// ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). -pub fn exists(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch::new( +pub fn exists(path: impl StringOrArray) -> SearchOperator { + SearchOperator::new( "exists", doc! { "path" : path.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -172,18 +174,18 @@ faceted fields and returns the count for each of those groups. */ /// ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). -pub fn facet(facets: Document) -> AtlasSearch { - AtlasSearch::new( +pub fn facet(facets: Document) -> SearchOperator { + SearchOperator::new( "facet", doc! { "facets" : facets, }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] - pub fn operator(mut self, operator: impl SearchOperator) -> Self { - self.stage.insert("operator", operator.to_bson()); + pub fn operator(mut self, operator: impl SearchOperatorParam) -> Self { + self.spec.insert("operator", operator.to_bson()); self } } @@ -198,18 +200,18 @@ pub fn geo_shape( path: impl StringOrArray, relation: Relation, geometry: Document, -) -> AtlasSearch { - AtlasSearch::new( +) -> SearchOperator { + SearchOperator::new( "geoShape", doc! { "path" : path.to_bson(), "relation" : relation.name(), "geometry" : geometry, }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -221,33 +223,33 @@ the index definition. */ /// ///For more details, see the [geoWithin operator reference](https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/). -pub fn geo_within(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch::new( +pub fn geo_within(path: impl StringOrArray) -> SearchOperator { + SearchOperator::new( "geoWithin", doc! { "path" : path.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn geo_box(mut self, geo_box: Document) -> Self { - self.stage.insert("box", geo_box); + self.spec.insert("box", geo_box); self } #[allow(missing_docs)] pub fn circle(mut self, circle: Document) -> Self { - self.stage.insert("circle", circle); + self.spec.insert("circle", circle); self } #[allow(missing_docs)] pub fn geometry(mut self, geometry: Document) -> Self { - self.stage.insert("geometry", geometry); + self.spec.insert("geometry", geometry); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -257,18 +259,18 @@ pub struct SearchIn; * */ /// ///For more details, see the [in operator reference](https://www.mongodb.com/docs/atlas/atlas-search/in/). -pub fn search_in(path: impl StringOrArray, value: impl Into) -> AtlasSearch { - AtlasSearch::new( +pub fn search_in(path: impl StringOrArray, value: impl Into) -> SearchOperator { + SearchOperator::new( "in", doc! { "path" : path.to_bson(), "value" : value.into(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -280,18 +282,18 @@ that display similar or alternative results based on one or more given documents */ /// ///For more details, see the [moreLikeThis operator reference](https://www.mongodb.com/docs/atlas/atlas-search/moreLikeThis/). -pub fn more_like_this(like: impl DocumentOrArray) -> AtlasSearch { - AtlasSearch::new( +pub fn more_like_this(like: impl DocumentOrArray) -> SearchOperator { + SearchOperator::new( "moreLikeThis", doc! { "like" : like.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -305,8 +307,8 @@ pub fn near( path: impl StringOrArray, origin: impl NearOrigin, pivot: impl BsonNumber, -) -> AtlasSearch { - AtlasSearch::new( +) -> SearchOperator { + SearchOperator::new( "near", doc! { "path" : path.to_bson(), "origin" : origin.to_bson(), "pivot" : pivot @@ -314,10 +316,10 @@ pub fn near( }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -328,28 +330,28 @@ pub struct Phrase; * */ /// ///For more details, see the [phrase operator reference](https://www.mongodb.com/docs/atlas/atlas-search/phrase/). -pub fn phrase(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { - AtlasSearch::new( +pub fn phrase(path: impl StringOrArray, query: impl StringOrArray) -> SearchOperator { + SearchOperator::new( "phrase", doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn slop(mut self, slop: i32) -> Self { - self.stage.insert("slop", slop); + self.spec.insert("slop", slop); self } #[allow(missing_docs)] pub fn synonyms(mut self, synonyms: impl AsRef) -> Self { - self.stage.insert("synonyms", synonyms.as_ref()); + self.spec.insert("synonyms", synonyms.as_ref()); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -361,15 +363,15 @@ pub struct QueryString; pub fn query_string( default_path: impl StringOrArray, query: impl AsRef, -) -> AtlasSearch { - AtlasSearch::new( +) -> SearchOperator { + SearchOperator::new( "queryString", doc! { "defaultPath" : default_path.to_bson(), "query" : query.as_ref(), }, ) } -impl AtlasSearch {} +impl SearchOperator {} #[allow(missing_docs)] pub struct Range; /**The range operator supports querying and scoring numeric, date, and string values. @@ -377,38 +379,38 @@ You can use this operator to find results that are within a given numeric, date, */ /// ///For more details, see the [range operator reference](https://www.mongodb.com/docs/atlas/atlas-search/range/). -pub fn range(path: impl StringOrArray) -> AtlasSearch { - AtlasSearch::new( +pub fn range(path: impl StringOrArray) -> SearchOperator { + SearchOperator::new( "range", doc! { "path" : path.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn gt(mut self, gt: impl RangeValue) -> Self { - self.stage.insert("gt", gt.to_bson()); + self.spec.insert("gt", gt.to_bson()); self } #[allow(missing_docs)] pub fn gte(mut self, gte: impl RangeValue) -> Self { - self.stage.insert("gte", gte.to_bson()); + self.spec.insert("gte", gte.to_bson()); self } #[allow(missing_docs)] pub fn lt(mut self, lt: impl RangeValue) -> Self { - self.stage.insert("lt", lt.to_bson()); + self.spec.insert("lt", lt.to_bson()); self } #[allow(missing_docs)] pub fn lte(mut self, lte: impl RangeValue) -> Self { - self.stage.insert("lte", lte.to_bson()); + self.spec.insert("lte", lte.to_bson()); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -419,24 +421,23 @@ regex is a term-level operator, meaning that the query field isn't analyzed. */ /// ///For more details, see the [regex operator reference](https://www.mongodb.com/docs/atlas/atlas-search/regex/). -pub fn regex(path: impl StringOrArray, query: impl AsRef) -> AtlasSearch { - AtlasSearch::new( +pub fn regex(path: impl StringOrArray, query: impl AsRef) -> SearchOperator { + SearchOperator::new( "regex", doc! { "path" : path.to_bson(), "query" : query.as_ref(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn allow_analyzed_field(mut self, allow_analyzed_field: bool) -> Self { - self.stage - .insert("allowAnalyzedField", allow_analyzed_field); + self.spec.insert("allowAnalyzedField", allow_analyzed_field); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -447,33 +448,33 @@ If you omit an analyzer, the text operator uses the default standard analyzer. */ /// ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). -pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> AtlasSearch { - AtlasSearch::new( +pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> SearchOperator { + SearchOperator::new( "text", doc! { "path" : path.to_bson(), "query" : query.to_bson(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn fuzzy(mut self, fuzzy: Document) -> Self { - self.stage.insert("fuzzy", fuzzy); + self.spec.insert("fuzzy", fuzzy); self } #[allow(missing_docs)] pub fn match_criteria(mut self, match_criteria: MatchCriteria) -> Self { - self.stage.insert("matchCriteria", match_criteria.name()); + self.spec.insert("matchCriteria", match_criteria.name()); self } #[allow(missing_docs)] pub fn synonyms(mut self, synonyms: impl AsRef) -> Self { - self.stage.insert("synonyms", synonyms.as_ref()); + self.spec.insert("synonyms", synonyms.as_ref()); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } @@ -484,24 +485,23 @@ pub struct Wildcard; * */ /// ///For more details, see the [wildcard operator reference](https://www.mongodb.com/docs/atlas/atlas-search/wildcard/). -pub fn wildcard(path: impl StringOrArray, query: impl AsRef) -> AtlasSearch { - AtlasSearch::new( +pub fn wildcard(path: impl StringOrArray, query: impl AsRef) -> SearchOperator { + SearchOperator::new( "wildcard", doc! { "path" : path.to_bson(), "query" : query.as_ref(), }, ) } -impl AtlasSearch { +impl SearchOperator { #[allow(missing_docs)] pub fn allow_analyzed_field(mut self, allow_analyzed_field: bool) -> Self { - self.stage - .insert("allowAnalyzedField", allow_analyzed_field); + self.spec.insert("allowAnalyzedField", allow_analyzed_field); self } #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { - self.stage.insert("score", score); + self.spec.insert("score", score); self } } diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index dd936a9f4..cdf0f164f 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -245,7 +245,7 @@ fn helper_output_doc() { } } }, - near("year", 2000, 2).index("runtimes").stage() + near("year", 2000, 2).search().index("runtimes").stage() ); let dt = DateTime::parse_rfc3339_str("1915-09-13T00:00:00.000+00:00").unwrap(); assert_eq!( @@ -260,6 +260,7 @@ fn helper_output_doc() { } }, near("released", dt, 7776000000i64) + .search() .index("releaseddate") .stage() ); From 47b093eb2df78f341422a18f1a285e07611c560c Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 2 Sep 2025 14:59:00 +0100 Subject: [PATCH 25/32] unchain search --- src/atlas_search.rs | 36 ++++++++++++++++++------------------ src/test/atlas_search.rs | 5 ++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 24ad91b1c..0c4b8f0b2 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -48,30 +48,14 @@ impl SearchOperator { } } - /// Finalize this search operator as a pending `$search` aggregation stage, allowing - /// options to be set. - pub fn search(self) -> AtlasSearch { - AtlasSearch { - stage: doc! { self.name: self.spec }, - } - } - /// Finalize this search operator as a `$search` aggregation stage document. pub fn stage(self) -> Document { - self.search().stage() - } - - /// Finalize this search operator as a pending `$searchMeta` aggregation stage, allowing - /// options to be set. - pub fn search_meta(self) -> AtlasSearchMeta { - AtlasSearchMeta { - stage: doc! { self.name: self.spec }, - } + search(self).stage() } /// Finalize this search operator as a `$searchMeta` aggregation stage document. pub fn stage_meta(self) -> Document { - self.search_meta().stage() + search_meta(self).stage() } /// Erase the type of this builder. Not typically needed, but can be useful to include builders @@ -102,6 +86,14 @@ impl SearchOperator { } } +/// Finalize a search operator as a pending `$search` aggregation stage, allowing +/// options to be set. +pub fn search(op: SearchOperator) -> AtlasSearch { + AtlasSearch { + stage: doc! { op.name: op.spec }, + } +} + /// A pending `$search` aggregation stage. pub struct AtlasSearch { stage: Document, @@ -179,6 +171,14 @@ impl AtlasSearch { } } +/// Finalize a search operator as a pending `$searchMeta` aggregation stage, allowing +/// options to be set. +pub fn search_meta(op: SearchOperator) -> AtlasSearchMeta { + AtlasSearchMeta { + stage: doc! { op.name: op.spec }, + } +} + /// A pending `$searchMeta` aggregation stage. pub struct AtlasSearchMeta { stage: Document, diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index cdf0f164f..81f2a1a12 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -245,7 +245,7 @@ fn helper_output_doc() { } } }, - near("year", 2000, 2).search().index("runtimes").stage() + search(near("year", 2000, 2)).index("runtimes").stage() ); let dt = DateTime::parse_rfc3339_str("1915-09-13T00:00:00.000+00:00").unwrap(); assert_eq!( @@ -259,8 +259,7 @@ fn helper_output_doc() { } } }, - near("released", dt, 7776000000i64) - .search() + search(near("released", dt, 7776000000i64)) .index("releaseddate") .stage() ); From b0d502cd73f1e9a31d3a9cdd592488c6e293ce79 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 2 Sep 2025 16:22:03 +0100 Subject: [PATCH 26/32] fix test --- src/test/atlas_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index 81f2a1a12..dc6a2fada 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -144,7 +144,7 @@ fn helper_output_doc() { "yearFacet": facet::number("year", [2000, 2005, 2010, 2015]), }) .operator(range("released").gte(gte_dt).lte(lte_dt)) - .stage() + .stage_meta() ); assert_eq!( doc! { From 75e74eb2f2d88e0e23520d63c23e31f2cc5227c2 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Tue, 2 Sep 2025 16:23:46 +0100 Subject: [PATCH 27/32] clippy --- src/atlas_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 0c4b8f0b2..25c1a4015 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -413,7 +413,7 @@ impl Relation { Self::Disjoint => "disjoint", Self::Intersects => "intersects", Self::Within => "within", - Self::Other(s) => &s, + Self::Other(s) => s, } } } From 2605f47999e0474ea7567a35e3df7715e19cfd91 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Wed, 3 Sep 2025 10:32:55 +0100 Subject: [PATCH 28/32] reactivate module --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 77c5f65cc..d41b2da1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ pub mod options; pub use ::mongocrypt; pub mod action; -//pub mod atlas_search; +pub mod atlas_search; pub(crate) mod bson_compat; mod bson_util; pub mod change_stream; From 164729c5dc620f7b460f64dc0deaa22a40596cf9 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 8 Sep 2025 10:38:18 +0100 Subject: [PATCH 29/32] review --- etc/gen_atlas_search/src/main.rs | 6 ++++- src/atlas_search.rs | 38 ++++++++++++++------------------ src/atlas_search/gen.rs | 35 +++++++++++++++-------------- src/test/atlas_search.rs | 36 +++++++++++++++--------------- 4 files changed, 58 insertions(+), 57 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index 737374567..be45e9702 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -87,8 +87,12 @@ impl Operator { "For more details, see the [{name_text} operator reference]({}).", self.link ); + let struct_doc = format!( + "`{name_text}` Atlas Search operator. Construct with \ + [`{constr_ident}`]({constr_ident}())." + ); parse_quote! { - #[allow(missing_docs)] + #[doc = #struct_doc] pub struct #name_ident; #[doc = #desc] diff --git a/src/atlas_search.rs b/src/atlas_search.rs index 25c1a4015..bd9a8b67f 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -10,7 +10,7 @@ use crate::bson::{doc, Bson, DateTime, Document}; /// A helper to build the aggregation stage for Atlas Search. Use one of the constructor functions /// and chain optional value setters, and then convert to a pipeline stage [`Document`] via -/// [`stage`](SearchOperator::stage). +/// [`into_stage`](SearchOperator::into_stage). /// /// ```no_run /// # async fn wrapper() -> mongodb::error::Result<()> { @@ -20,7 +20,7 @@ use crate::bson::{doc, Bson, DateTime, Document}; /// let cursor = collection.aggregate(vec![ /// atlas_search::autocomplete("title", "pre") /// .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) -/// .stage(), +/// .into_stage(), /// doc! { /// "$limit": 10, /// }, @@ -49,13 +49,13 @@ impl SearchOperator { } /// Finalize this search operator as a `$search` aggregation stage document. - pub fn stage(self) -> Document { - search(self).stage() + pub fn into_stage(self) -> Document { + search(self).into_stage() } /// Finalize this search operator as a `$searchMeta` aggregation stage document. - pub fn stage_meta(self) -> Document { - search_meta(self).stage() + pub fn into_stage_meta(self) -> Document { + search_meta(self).into_stage() } /// Erase the type of this builder. Not typically needed, but can be useful to include builders @@ -73,7 +73,7 @@ impl SearchOperator { /// .should(atlas_search::text("description", "Fuji")) /// .unit(), /// ]) - /// .stage(), + /// .into_stage(), /// ]).await?; /// # } /// ``` @@ -158,15 +158,8 @@ impl AtlasSearch { self } - /// Document that specifies the tracking option to retrieve analytics information on the search - /// terms. - pub fn tracking(mut self, value: Document) -> Self { - self.stage.insert("tracking", value); - self - } - /// Convert to an aggregation stage document. - pub fn stage(self) -> Document { + pub fn into_stage(self) -> Document { doc! { "$search": self.stage } } } @@ -198,7 +191,7 @@ impl AtlasSearchMeta { } /// Convert to an aggregation stage document. - pub fn stage(self) -> Document { + pub fn into_stage(self) -> Document { doc! { "$searchMeta": self.stage } } } @@ -306,7 +299,7 @@ pub mod facet { use crate::bson::{doc, Bson, Document}; use std::marker::PhantomData; - /// A facet definition. + /// A facet definition; see the [facet docs](https://www.mongodb.com/docs/atlas/atlas-search/facet/) for more details. pub struct Facet { inner: Document, _t: PhantomData, @@ -332,7 +325,8 @@ pub mod facet { } } impl Facet { - #[allow(missing_docs)] + /// Maximum number of facet categories to return in the results. Value must be less than or + /// equal to 1000. pub fn num_buckets(mut self, num: i32) -> Self { self.inner.insert("numBuckets", num); self @@ -357,8 +351,9 @@ pub mod facet { } } impl Facet { - #[allow(missing_docs)] - pub fn default(mut self, bucket: impl AsRef) -> Self { + /// Name of an additional bucket that counts documents returned from the operator that do + /// not fall within the specified boundaries. + pub fn default_bucket(mut self, bucket: impl AsRef) -> Self { self.inner.insert("default", bucket.as_ref()); self } @@ -381,7 +376,8 @@ pub mod facet { } } impl Facet { - #[allow(missing_docs)] + /// Name of an additional bucket that counts documents returned from the operator that do + /// not fall within the specified boundaries. pub fn default(mut self, bucket: impl AsRef) -> Self { self.inner.insert("default", bucket.as_ref()); self diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index bb4968ada..96f83f956 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -1,6 +1,6 @@ //! This file was autogenerated. Do not manually edit. use super::*; -#[allow(missing_docs)] +///`autocomplete` Atlas Search operator. Construct with [`autocomplete`](autocomplete()). pub struct Autocomplete; /**The autocomplete operator performs a search for a word or phrase that contains a sequence of characters from an incomplete input string. The @@ -37,7 +37,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`compound` Atlas Search operator. Construct with [`compound`](compound()). pub struct Compound; /**The compound operator combines two or more operators into a single query. Each element of a compound query is called a clause, and each clause @@ -98,7 +98,8 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`embeddedDocument` Atlas Search operator. Construct with +/// [`embedded_document`](embedded_document()). pub struct EmbeddedDocument; /**The embeddedDocument operator is similar to $elemMatch operator. It constrains multiple query predicates to be satisfied from a single @@ -125,7 +126,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`equals` Atlas Search operator. Construct with [`equals`](equals()). pub struct Equals; /**The equals operator checks whether a field matches a value you specify. * */ @@ -146,7 +147,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`exists` Atlas Search operator. Construct with [`exists`](exists()). pub struct Exists; /**The exists operator tests if a path to a specified indexed field name exists in a document. * */ @@ -167,7 +168,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`facet` Atlas Search operator. Construct with [`facet`](facet()). pub struct Facet; /**The facet collector groups results by values or ranges in the specified faceted fields and returns the count for each of those groups. @@ -189,7 +190,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`geoShape` Atlas Search operator. Construct with [`geo_shape`](geo_shape()). pub struct GeoShape; /**The geoShape operator supports querying shapes with a relation to a given geometry if indexShapes is set to true in the index definition. @@ -215,7 +216,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`geoWithin` Atlas Search operator. Construct with [`geo_within`](geo_within()). pub struct GeoWithin; /**The geoWithin operator supports querying geographic points within a given geometry. Only points are returned, even if indexShapes value is true in @@ -253,7 +254,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`in` Atlas Search operator. Construct with [`search_in`](search_in()). pub struct SearchIn; /**The in operator performs a search for an array of BSON values in a field. * */ @@ -274,7 +275,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`moreLikeThis` Atlas Search operator. Construct with [`more_like_this`](more_like_this()). pub struct MoreLikeThis; /**The moreLikeThis operator returns documents similar to input documents. The moreLikeThis operator allows you to build features for your applications @@ -297,7 +298,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`near` Atlas Search operator. Construct with [`near`](near()). pub struct Near; /**The near operator supports querying and scoring numeric, date, and GeoJSON point values. * */ @@ -323,7 +324,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`phrase` Atlas Search operator. Construct with [`phrase`](phrase()). pub struct Phrase; /**The phrase operator performs search for documents containing an ordered sequence of terms * using the analyzer specified in the index configuration. @@ -355,7 +356,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`queryString` Atlas Search operator. Construct with [`query_string`](query_string()). pub struct QueryString; /// /// @@ -372,7 +373,7 @@ pub fn query_string( ) } impl SearchOperator {} -#[allow(missing_docs)] +///`range` Atlas Search operator. Construct with [`range`](range()). pub struct Range; /**The range operator supports querying and scoring numeric, date, and string values. You can use this operator to find results that are within a given numeric, date, objectId, or letter (from the English alphabet) range. @@ -414,7 +415,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`regex` Atlas Search operator. Construct with [`regex`](regex()). pub struct Regex; /**regex interprets the query field as a regular expression. regex is a term-level operator, meaning that the query field isn't analyzed. @@ -441,7 +442,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`text` Atlas Search operator. Construct with [`text`](text()). pub struct Text; /**The text operator performs a full-text search using the analyzer that you specify in the index configuration. If you omit an analyzer, the text operator uses the default standard analyzer. @@ -478,7 +479,7 @@ impl SearchOperator { self } } -#[allow(missing_docs)] +///`wildcard` Atlas Search operator. Construct with [`wildcard`](wildcard()). pub struct Wildcard; /**The wildcard operator enables queries which use special characters in the search string that * can match any character. diff --git a/src/test/atlas_search.rs b/src/test/atlas_search.rs index dc6a2fada..d3a455f86 100644 --- a/src/test/atlas_search.rs +++ b/src/test/atlas_search.rs @@ -21,7 +21,7 @@ fn helper_output_doc() { }, autocomplete("title", "pre") .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -32,7 +32,7 @@ fn helper_output_doc() { } } }, - text("plot", "baseball").stage() + text("plot", "baseball").into_stage() ); assert_eq!( doc! { @@ -56,7 +56,7 @@ fn helper_output_doc() { compound() .must(text("description", "varieties")) .should(text("description", "Fuji")) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -98,7 +98,7 @@ fn helper_output_doc() { "aggregate": "mean" } }) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -109,7 +109,7 @@ fn helper_output_doc() { } } }, - equals("verified_user", true).stage() + equals("verified_user", true).into_stage() ); let gte_dt = DateTime::parse_rfc3339_str("2000-01-01T00:00:00.000Z").unwrap(); let lte_dt = DateTime::parse_rfc3339_str("2015-01-31T00:00:00.000Z").unwrap(); @@ -144,7 +144,7 @@ fn helper_output_doc() { "yearFacet": facet::number("year", [2000, 2005, 2010, 2015]), }) .operator(range("released").gte(gte_dt).lte(lte_dt)) - .stage_meta() + .into_stage_meta() ); assert_eq!( doc! { @@ -173,7 +173,7 @@ fn helper_output_doc() { [-161.323242,22.512557]]] } ) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -204,7 +204,7 @@ fn helper_output_doc() { "coordinates": [168.000, -9.133] } }) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -215,7 +215,7 @@ fn helper_output_doc() { } } }, - search_in("accounts", [371138, 371139, 371140].as_ref()).stage() + search_in("accounts", [371138, 371139, 371140].as_ref()).into_stage() ); assert_eq!( doc! { @@ -232,7 +232,7 @@ fn helper_output_doc() { "title": "The Godfather", "genres": "action" }) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -245,7 +245,7 @@ fn helper_output_doc() { } } }, - search(near("year", 2000, 2)).index("runtimes").stage() + search(near("year", 2000, 2)).index("runtimes").into_stage() ); let dt = DateTime::parse_rfc3339_str("1915-09-13T00:00:00.000+00:00").unwrap(); assert_eq!( @@ -261,7 +261,7 @@ fn helper_output_doc() { }, search(near("released", dt, 7776000000i64)) .index("releaseddate") - .stage() + .into_stage() ); assert_eq!( doc! { @@ -284,7 +284,7 @@ fn helper_output_doc() { }, 1000, ) - .stage() + .into_stage() ); assert_eq!( doc! { @@ -295,7 +295,7 @@ fn helper_output_doc() { } } }, - phrase("title", "new york").stage() + phrase("title", "new york").into_stage() ); #[cfg(feature = "bson-3")] assert_eq!( @@ -307,7 +307,7 @@ fn helper_output_doc() { } } }, - phrase("title", ["the man", "the moon"]).stage() + phrase("title", ["the man", "the moon"]).into_stage() ); assert_eq!( doc! { @@ -318,7 +318,7 @@ fn helper_output_doc() { } } }, - query_string("title", "Rocky AND (IV OR 4 OR Four)").stage() + query_string("title", "Rocky AND (IV OR 4 OR Four)").into_stage() ); assert_eq!( doc! { @@ -329,7 +329,7 @@ fn helper_output_doc() { } } }, - regex("title", "(.*) Seattle").stage() + regex("title", "(.*) Seattle").into_stage() ); assert_eq!( doc! { @@ -340,7 +340,7 @@ fn helper_output_doc() { } } }, - wildcard("title", "Green D*").stage() + wildcard("title", "Green D*").into_stage() ); } From f45e12b89e14b072f1ec6d86e02c571f02c8bbff Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 8 Sep 2025 12:39:14 +0100 Subject: [PATCH 30/32] doc macro generalization --- macros/.gitignore | 1 + macros/src/rustdoc.rs | 24 +++++++++++------------- src/action/aggregate.rs | 8 ++++---- src/action/bulk_write.rs | 6 +++--- src/action/count.rs | 4 ++-- src/action/create_collection.rs | 2 +- src/action/create_index.rs | 4 ++-- src/action/delete.rs | 4 ++-- src/action/distinct.rs | 2 +- src/action/drop.rs | 4 ++-- src/action/drop_index.rs | 4 ++-- src/action/find.rs | 4 ++-- src/action/find_and_modify.rs | 6 +++--- src/action/gridfs/download.rs | 2 +- src/action/gridfs/find.rs | 4 ++-- src/action/gridfs/upload.rs | 2 +- src/action/insert_many.rs | 2 +- src/action/insert_one.rs | 2 +- src/action/list_collections.rs | 4 ++-- src/action/list_databases.rs | 4 ++-- src/action/list_indexes.rs | 4 ++-- src/action/replace_one.rs | 2 +- src/action/run_command.rs | 8 ++++---- src/action/search_index.rs | 10 +++++----- src/action/session.rs | 2 +- src/action/shutdown.rs | 2 +- src/action/transaction.rs | 2 +- src/action/update.rs | 4 ++-- src/action/watch.rs | 6 +++--- 29 files changed, 66 insertions(+), 67 deletions(-) diff --git a/macros/.gitignore b/macros/.gitignore index b83d22266..ca98cd96e 100644 --- a/macros/.gitignore +++ b/macros/.gitignore @@ -1 +1,2 @@ /target/ +Cargo.lock diff --git a/macros/src/rustdoc.rs b/macros/src/rustdoc.rs index 034b97849..7f87e2ff0 100644 --- a/macros/src/rustdoc.rs +++ b/macros/src/rustdoc.rs @@ -153,7 +153,10 @@ pub(crate) fn options_doc( }); let preamble = format!( "These methods can be chained before `{}` to set options:", - if args.is_async() { ".await" } else { "run" } + args.term + .as_ref() + .map(|(_, b)| b.as_str()) + .unwrap_or(".await") ); impl_fn.attrs.push(parse_quote! { #[doc = #preamble] @@ -169,34 +172,29 @@ pub(crate) fn options_doc( pub(crate) struct OptionsDocArgs { foreign_path: syn::Path, - sync: Option<(Token![,], Ident)>, -} - -impl OptionsDocArgs { - fn is_async(&self) -> bool { - self.sync.is_none() - } + term: Option<(Token![,], String)>, } impl Parse for OptionsDocArgs { fn parse(input: ParseStream) -> syn::Result { let foreign_path = input.parse()?; - let sync = if input.is_empty() { + let term = if input.is_empty() { None } else { - Some((input.parse()?, parse_name(input, "sync")?)) + let (comma, lit) = (input.parse()?, input.parse::()?); + Some((comma, lit.value())) }; - Ok(Self { foreign_path, sync }) + Ok(Self { foreign_path, term }) } } impl ToTokens for OptionsDocArgs { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(self.foreign_path.to_token_stream()); - if let Some((comma, ident)) = &self.sync { + if let Some((comma, lit)) = &self.term { tokens.extend(comma.to_token_stream()); - tokens.extend(ident.to_token_stream()); + tokens.extend(lit.to_token_stream()); } } } diff --git a/src/action/aggregate.rs b/src/action/aggregate.rs index ccfbdea8d..ca12f5a67 100644 --- a/src/action/aggregate.rs +++ b/src/action/aggregate.rs @@ -76,12 +76,12 @@ impl crate::sync::Database { /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more /// information on aggregations. /// - /// [`run`](Aggregate::run) will return d[Result>`]. If a + /// [`run`](Aggregate::run) will return d[`Result>`]. If a /// [`crate::sync::ClientSession`] was provided, the returned cursor will be a /// [`crate::sync::SessionCursor`]. If [`with_type`](Aggregate::with_type) was called, the /// returned cursor will be generic over the `T` specified. #[deeplink] - #[options_doc(aggregate, sync)] + #[options_doc(aggregate, "run")] pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { self.async_database.aggregate(pipeline) } @@ -97,12 +97,12 @@ where /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more /// information on aggregations. /// - /// [`run`](Aggregate::run) will return d[Result>`]. If a + /// [`run`](Aggregate::run) will return d[`Result>`]. If a /// `crate::sync::ClientSession` was provided, the returned cursor will be a /// `crate::sync::SessionCursor`. If [`with_type`](Aggregate::with_type) was called, the /// returned cursor will be generic over the `T` specified. #[deeplink] - #[options_doc(aggregate, sync)] + #[options_doc(aggregate, "run")] pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { self.async_collection.aggregate(pipeline) } diff --git a/src/action/bulk_write.rs b/src/action/bulk_write.rs index 04b85530d..1a0ae3d77 100644 --- a/src/action/bulk_write.rs +++ b/src/action/bulk_write.rs @@ -48,13 +48,13 @@ impl crate::sync::Client { /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on /// retryable writes. /// - /// [`run`](BulkWrite::run) will return d[`Result`] or + /// d[`Result`] if [`verbose_results`](BulkWrite::verbose_results) is /// configured. /// /// Bulk write is only available on MongoDB 8.0+. #[deeplink] - #[options_doc(bulk_write, sync)] + #[options_doc(bulk_write, "run")] pub fn bulk_write( &self, models: impl IntoIterator>, diff --git a/src/action/count.rs b/src/action/count.rs index c99aeb26a..587b4b267 100644 --- a/src/action/count.rs +++ b/src/action/count.rs @@ -78,7 +78,7 @@ where /// /// [`run`](EstimatedDocumentCount::run) will return d[`Result`]. #[deeplink] - #[options_doc(estimated_doc_count, sync)] + #[options_doc(estimated_doc_count, "run")] pub fn estimated_document_count(&self) -> EstimatedDocumentCount { self.async_collection.estimated_document_count() } @@ -89,7 +89,7 @@ where /// /// [`run`](CountDocuments::run) will return d[`Result`]. #[deeplink] - #[options_doc(count_docs, sync)] + #[options_doc(count_docs, "run")] pub fn count_documents(&self, filter: Document) -> CountDocuments { self.async_collection.count_documents(filter) } diff --git a/src/action/create_collection.rs b/src/action/create_collection.rs index 2aa1cec55..41f777999 100644 --- a/src/action/create_collection.rs +++ b/src/action/create_collection.rs @@ -47,7 +47,7 @@ impl crate::sync::Database { /// /// [`run`](CreateCollection::run) will return d[`Result<()>`]. #[deeplink] - #[options_doc(create_coll, sync)] + #[options_doc(create_coll, "run")] pub fn create_collection(&self, name: impl Into) -> CreateCollection { self.async_database.create_collection(name) } diff --git a/src/action/create_index.rs b/src/action/create_index.rs index 75dc2ba8d..bd420acf8 100644 --- a/src/action/create_index.rs +++ b/src/action/create_index.rs @@ -71,7 +71,7 @@ where /// /// [`run`](CreateIndex::run) will return d[`Result`]. #[deeplink] - #[options_doc(create_index, sync)] + #[options_doc(create_index, "run")] pub fn create_index(&self, index: IndexModel) -> CreateIndex { self.async_collection.create_index(index) } @@ -80,7 +80,7 @@ where /// /// [`run`](CreateIndex::run) will return d[`Result`]. #[deeplink] - #[options_doc(create_index, sync)] + #[options_doc(create_index, "run")] pub fn create_indexes( &self, indexes: impl IntoIterator, diff --git a/src/action/delete.rs b/src/action/delete.rs index 4bf2dd78a..d94532314 100644 --- a/src/action/delete.rs +++ b/src/action/delete.rs @@ -67,7 +67,7 @@ where /// /// [`run`](Delete::run) will return d[`Result`]. #[deeplink] - #[options_doc(delete, sync)] + #[options_doc(delete, "run")] pub fn delete_one(&self, query: Document) -> Delete { self.async_collection.delete_one(query) } @@ -76,7 +76,7 @@ where /// /// [`run`](Delete::run) will return d[`Result`]. #[deeplink] - #[options_doc(delete, sync)] + #[options_doc(delete, "run")] pub fn delete_many(&self, query: Document) -> Delete { self.async_collection.delete_many(query) } diff --git a/src/action/distinct.rs b/src/action/distinct.rs index 447238f7e..f245f0afd 100644 --- a/src/action/distinct.rs +++ b/src/action/distinct.rs @@ -44,7 +44,7 @@ where /// /// [`run`](Distinct::run) will return d[`Result>`]. #[deeplink] - #[options_doc(distinct, sync)] + #[options_doc(distinct, "run")] pub fn distinct(&self, field_name: impl AsRef, filter: Document) -> Distinct { self.async_collection.distinct(field_name, filter) } diff --git a/src/action/drop.rs b/src/action/drop.rs index 3571a53ed..374dd084d 100644 --- a/src/action/drop.rs +++ b/src/action/drop.rs @@ -35,7 +35,7 @@ impl crate::sync::Database { /// /// [`run`](DropDatabase::run) will return d[`Result<()>`]. #[deeplink] - #[options_doc(drop_db, sync)] + #[options_doc(drop_db, "run")] pub fn drop(&self) -> DropDatabase { self.async_database.drop() } @@ -98,7 +98,7 @@ where /// /// [`run`](DropCollection::run) will return d[`Result<()>`]. #[deeplink] - #[options_doc(drop_coll, sync)] + #[options_doc(drop_coll, "run")] pub fn drop(&self) -> DropCollection { self.async_collection.drop() } diff --git a/src/action/drop_index.rs b/src/action/drop_index.rs index e729d3aba..2ec816ce4 100644 --- a/src/action/drop_index.rs +++ b/src/action/drop_index.rs @@ -55,7 +55,7 @@ where /// /// [`run`](DropIndex::run) will return d[`Result<()>`]. #[deeplink] - #[options_doc(drop_index, sync)] + #[options_doc(drop_index, "run")] pub fn drop_index(&self, name: impl AsRef) -> DropIndex { self.async_collection.drop_index(name) } @@ -64,7 +64,7 @@ where /// /// [`run`](DropIndex::run) will return d[`Result<()>`]. #[deeplink] - #[options_doc(drop_index, sync)] + #[options_doc(drop_index, "run")] pub fn drop_indexes(&self) -> DropIndex { self.async_collection.drop_indexes() } diff --git a/src/action/find.rs b/src/action/find.rs index 7730e25e2..121e9d00c 100644 --- a/src/action/find.rs +++ b/src/action/find.rs @@ -66,7 +66,7 @@ impl crate::sync::Collection { /// [`run`](Find::run) will return d[`Result>`] (or /// d[`Result>`] if a session is provided). #[deeplink] - #[options_doc(find, sync)] + #[options_doc(find, "run")] pub fn find(&self, filter: Document) -> Find<'_, T> { self.async_collection.find(filter) } @@ -78,7 +78,7 @@ impl crate::sync::Collection { /// /// [`run`](FindOne::run) will return d[`Result>`]. #[deeplink] - #[options_doc(find_one, sync)] + #[options_doc(find_one, "run")] pub fn find_one(&self, filter: Document) -> FindOne<'_, T> { self.async_collection.find_one(filter) } diff --git a/src/action/find_and_modify.rs b/src/action/find_and_modify.rs index e8025fd87..0919f86e5 100644 --- a/src/action/find_and_modify.rs +++ b/src/action/find_and_modify.rs @@ -125,7 +125,7 @@ impl crate::sync::Collection { /// /// [`run`](FindOneAndDelete::run) will return d[`Result>`]. #[deeplink] - #[options_doc(find_one_and_delete, sync)] + #[options_doc(find_one_and_delete, "run")] pub fn find_one_and_delete(&self, filter: Document) -> FindOneAndDelete<'_, T> { self.async_collection.find_one_and_delete(filter) } @@ -141,7 +141,7 @@ impl crate::sync::Collection { /// /// [`run`](FindOneAndDelete::run) will return d[`Result>`]. #[deeplink] - #[options_doc(find_one_and_update, sync)] + #[options_doc(find_one_and_update, "run")] pub fn find_one_and_update( &self, filter: Document, @@ -163,7 +163,7 @@ impl crate::sync::Collection { /// /// [`run`](FindOneAndReplace::run) will return d[`Result>`]. #[deeplink] - #[options_doc(find_one_and_replace, sync)] + #[options_doc(find_one_and_replace, "run")] pub fn find_one_and_replace( &self, filter: Document, diff --git a/src/action/gridfs/download.rs b/src/action/gridfs/download.rs index 888608e80..d931fb6c6 100644 --- a/src/action/gridfs/download.rs +++ b/src/action/gridfs/download.rs @@ -115,7 +115,7 @@ impl crate::sync::gridfs::GridFsBucket { /// /// [`run`](OpenDownloadStreamByName::run) will return d[`Result`]. #[deeplink] - #[options_doc(download_by_name, sync)] + #[options_doc(download_by_name, "run")] pub fn open_download_stream_by_name( &self, filename: impl Into, diff --git a/src/action/gridfs/find.rs b/src/action/gridfs/find.rs index df96b9787..afb663457 100644 --- a/src/action/gridfs/find.rs +++ b/src/action/gridfs/find.rs @@ -47,7 +47,7 @@ impl crate::sync::gridfs::GridFsBucket { /// /// [`run`](Find::run) will return d[`Result>`]. #[deeplink] - #[options_doc(find, sync)] + #[options_doc(find, "run")] pub fn find(&self, filter: Document) -> Find { self.async_bucket.find(filter) } @@ -57,7 +57,7 @@ impl crate::sync::gridfs::GridFsBucket { /// /// [`run`](FindOne::run) will return d[`Result>`]. #[deeplink] - #[options_doc(find_one, sync)] + #[options_doc(find_one, "run")] pub fn find_one(&self, filter: Document) -> FindOne { self.async_bucket.find_one(filter) } diff --git a/src/action/gridfs/upload.rs b/src/action/gridfs/upload.rs index f313c455b..fdfbbfe8b 100644 --- a/src/action/gridfs/upload.rs +++ b/src/action/gridfs/upload.rs @@ -32,7 +32,7 @@ impl crate::sync::gridfs::GridFsBucket { /// /// [`run`](OpenUploadStream::run) will return d[`Result`]. #[deeplink] - #[options_doc(open_upload_stream, sync)] + #[options_doc(open_upload_stream, "run")] pub fn open_upload_stream(&self, filename: impl AsRef) -> OpenUploadStream { self.async_bucket.open_upload_stream(filename) } diff --git a/src/action/insert_many.rs b/src/action/insert_many.rs index 62754ba4e..6bb4b8e7d 100644 --- a/src/action/insert_many.rs +++ b/src/action/insert_many.rs @@ -59,7 +59,7 @@ impl crate::sync::Collection { /// /// [`run`](InsertMany::run) will return d[`Result`]. #[deeplink] - #[options_doc(insert_many, sync)] + #[options_doc(insert_many, "run")] pub fn insert_many(&self, docs: impl IntoIterator>) -> InsertMany { self.async_collection.insert_many(docs) } diff --git a/src/action/insert_one.rs b/src/action/insert_one.rs index 997ab0e49..7699f1636 100644 --- a/src/action/insert_one.rs +++ b/src/action/insert_one.rs @@ -54,7 +54,7 @@ impl crate::sync::Collection { /// /// [`run`](InsertOne::run) will return d[`Result`]. #[deeplink] - #[options_doc(insert_one, sync)] + #[options_doc(insert_one, "run")] pub fn insert_one(&self, doc: impl Borrow) -> InsertOne { self.async_collection.insert_one(doc) } diff --git a/src/action/list_collections.rs b/src/action/list_collections.rs index 21f261fb5..60fcffa4a 100644 --- a/src/action/list_collections.rs +++ b/src/action/list_collections.rs @@ -63,7 +63,7 @@ impl crate::sync::Database { /// [`run`](ListCollections::run) will return /// d[`Result>`]. #[deeplink] - #[options_doc(list_collections, sync)] + #[options_doc(list_collections, "run")] pub fn list_collections(&self) -> ListCollections { self.async_database.list_collections() } @@ -72,7 +72,7 @@ impl crate::sync::Database { /// /// [`run`](ListCollections::run) will return d[`Result>`]. #[deeplink] - #[options_doc(list_collections, sync)] + #[options_doc(list_collections, "run")] pub fn list_collection_names(&self) -> ListCollections<'_, ListNames> { self.async_database.list_collection_names() } diff --git a/src/action/list_databases.rs b/src/action/list_databases.rs index 2d2c59dd1..79838c811 100644 --- a/src/action/list_databases.rs +++ b/src/action/list_databases.rs @@ -59,7 +59,7 @@ impl SyncClient { /// /// [run](ListDatabases::run) will return d[`Result>`]. #[deeplink] - #[options_doc(list_databases, sync)] + #[options_doc(list_databases, "run")] pub fn list_databases(&self) -> ListDatabases { self.async_client.list_databases() } @@ -68,7 +68,7 @@ impl SyncClient { /// /// [run](ListDatabases::run) will return d[`Result>`]. #[deeplink] - #[options_doc(list_databases, sync)] + #[options_doc(list_databases, "run")] pub fn list_database_names(&self) -> ListDatabases<'_, ListNames> { self.async_client.list_database_names() } diff --git a/src/action/list_indexes.rs b/src/action/list_indexes.rs index acbf4bbde..51808722f 100644 --- a/src/action/list_indexes.rs +++ b/src/action/list_indexes.rs @@ -71,7 +71,7 @@ where /// [`run`](ListIndexes::run) will return d[`Result>`] (or /// d[`Result>`] if a `ClientSession` is provided). #[deeplink] - #[options_doc(list_indexes, sync)] + #[options_doc(list_indexes, "run")] pub fn list_indexes(&self) -> ListIndexes { self.async_collection.list_indexes() } @@ -80,7 +80,7 @@ where /// /// [`run`](ListIndexes::run) will return d[`Result>`]. #[deeplink] - #[options_doc(list_indexes, sync)] + #[options_doc(list_indexes, "run")] pub fn list_index_names(&self) -> ListIndexes { self.async_collection.list_index_names() } diff --git a/src/action/replace_one.rs b/src/action/replace_one.rs index af3d31392..8504cfa16 100644 --- a/src/action/replace_one.rs +++ b/src/action/replace_one.rs @@ -50,7 +50,7 @@ impl crate::sync::Collection { /// /// [`run`](ReplaceOne::run) will return d[`Result`]. #[deeplink] - #[options_doc(replace_one, sync)] + #[options_doc(replace_one, "run")] pub fn replace_one(&self, query: Document, replacement: impl Borrow) -> ReplaceOne { self.async_collection.replace_one(query, replacement) } diff --git a/src/action/run_command.rs b/src/action/run_command.rs index 4d2353599..cc8c85b7f 100644 --- a/src/action/run_command.rs +++ b/src/action/run_command.rs @@ -110,7 +110,7 @@ impl crate::sync::Database { /// /// [`run`](RunCommand::run) will return d[`Result`]. #[deeplink] - #[options_doc(run_command, sync)] + #[options_doc(run_command, "run")] pub fn run_command(&self, command: Document) -> RunCommand { self.async_database.run_command(command) } @@ -125,7 +125,7 @@ impl crate::sync::Database { /// /// [`run`](RunCommand::run) will return d[`Result`]. #[deeplink] - #[options_doc(run_command, sync)] + #[options_doc(run_command, "run")] pub fn run_raw_command(&self, command: RawDocumentBuf) -> RunCommand { self.async_database.run_raw_command(command) } @@ -135,7 +135,7 @@ impl crate::sync::Database { /// [`run`](RunCursorCommand::run) will return d[`Result>`] or a /// d[`Result>`] if a [`ClientSession`] is provided. #[deeplink] - #[options_doc(run_cursor_command, sync)] + #[options_doc(run_cursor_command, "run")] pub fn run_cursor_command(&self, command: Document) -> RunCursorCommand { self.async_database.run_cursor_command(command) } @@ -145,7 +145,7 @@ impl crate::sync::Database { /// [`run`](RunCursorCommand::run) will return d[`Result>`] or a /// d[`Result>`] if a [`ClientSession`] is provided. #[deeplink] - #[options_doc(run_cursor_command, sync)] + #[options_doc(run_cursor_command, "run")] pub fn run_raw_cursor_command(&self, command: RawDocumentBuf) -> RunCursorCommand { self.async_database.run_raw_cursor_command(command) } diff --git a/src/action/search_index.rs b/src/action/search_index.rs index 37a5526a5..b1fe5436a 100644 --- a/src/action/search_index.rs +++ b/src/action/search_index.rs @@ -118,7 +118,7 @@ where /// /// [`run`](CreateSearchIndex::run) will return d[`Result>`]. #[deeplink] - #[options_doc(create_search_index, sync)] + #[options_doc(create_search_index, "run")] pub fn create_search_indexes( &self, models: impl IntoIterator, @@ -130,7 +130,7 @@ where /// /// [`run`](CreateSearchIndex::run) will return d[`Result`]. #[deeplink] - #[options_doc(create_search_index, sync)] + #[options_doc(create_search_index, "run")] pub fn create_search_index(&self, model: SearchIndexModel) -> CreateSearchIndex { self.async_collection.create_search_index(model) } @@ -138,7 +138,7 @@ where /// Updates the search index with the given name to use the provided definition. /// /// [`run`](UpdateSearchIndex::run) will return [`Result<()>`]. - #[options_doc(update_search_index, sync)] + #[options_doc(update_search_index, "run")] pub fn update_search_index( &self, name: impl Into, @@ -150,7 +150,7 @@ where /// Drops the search index with the given name. /// /// [`run`](DropSearchIndex::run) will return [`Result<()>`]. - #[options_doc(drop_search_index, sync)] + #[options_doc(drop_search_index, "run")] pub fn drop_search_index(&self, name: impl Into) -> DropSearchIndex { self.async_collection.drop_search_index(name) } @@ -162,7 +162,7 @@ where /// /// [`run`](ListSearchIndexes::run) will return d[`Result>`]. #[deeplink] - #[options_doc(list_search_indexes, sync)] + #[options_doc(list_search_indexes, "run")] pub fn list_search_indexes(&self) -> ListSearchIndexes { self.async_collection.list_search_indexes() } diff --git a/src/action/session.rs b/src/action/session.rs index 6b4d8fb91..ad16f0fac 100644 --- a/src/action/session.rs +++ b/src/action/session.rs @@ -27,7 +27,7 @@ impl crate::sync::Client { /// /// [run](StartSession::run) will return d[`Result`]. #[deeplink] - #[options_doc(start_session, sync)] + #[options_doc(start_session, "run")] pub fn start_session(&self) -> StartSession { self.async_client.start_session() } diff --git a/src/action/shutdown.rs b/src/action/shutdown.rs index f8336c131..2b6ff7095 100644 --- a/src/action/shutdown.rs +++ b/src/action/shutdown.rs @@ -120,7 +120,7 @@ impl crate::sync::Client { /// `GridFsUploadStream`. /// /// [`run`](Shutdown::run) will return `()`. - #[options_doc(shutdown, sync)] + #[options_doc(shutdown, "run")] pub fn shutdown(self) -> Shutdown { self.async_client.shutdown() } diff --git a/src/action/transaction.rs b/src/action/transaction.rs index 306e242d6..ea5242622 100644 --- a/src/action/transaction.rs +++ b/src/action/transaction.rs @@ -132,7 +132,7 @@ impl crate::sync::ClientSession { /// ``` /// /// [`run`](StartTransaction::run) will return [`Result<()>`]. - #[options_doc(start_transaction, sync)] + #[options_doc(start_transaction, "run")] pub fn start_transaction(&mut self) -> StartTransaction<&mut Self> { StartTransaction { session: self, diff --git a/src/action/update.rs b/src/action/update.rs index de7deb5bf..d0507568d 100644 --- a/src/action/update.rs +++ b/src/action/update.rs @@ -76,7 +76,7 @@ where /// /// [`run`](Update::run) will return d[`Result`]. #[deeplink] - #[options_doc(update, sync)] + #[options_doc(update, "run")] pub fn update_many(&self, query: Document, update: impl Into) -> Update { self.async_collection.update_many(query, update) } @@ -94,7 +94,7 @@ where /// /// [`run`](Update::run) will return d[`Result`]. #[deeplink] - #[options_doc(update, sync)] + #[options_doc(update, "run")] pub fn update_one(&self, query: Document, update: impl Into) -> Update { self.async_collection.update_one(query, update) } diff --git a/src/action/watch.rs b/src/action/watch.rs index b3044a286..df9cea910 100644 --- a/src/action/watch.rs +++ b/src/action/watch.rs @@ -127,7 +127,7 @@ impl crate::sync::Client { /// /// Change streams require either a "majority" read concern or no read /// concern. Anything else will cause a server error. - #[options_doc(watch, sync)] + #[options_doc(watch, "run")] pub fn watch(&self) -> Watch { self.async_client.watch() } @@ -144,7 +144,7 @@ impl crate::sync::Database { /// /// Change streams require either a "majority" read concern or no read /// concern. Anything else will cause a server error. - #[options_doc(watch, sync)] + #[options_doc(watch, "run")] pub fn watch(&self) -> Watch { self.async_database.watch() } @@ -165,7 +165,7 @@ where /// /// Change streams require either a "majority" read concern or no read concern. Anything else /// will cause a server error. - #[options_doc(watch, sync)] + #[options_doc(watch, "run")] pub fn watch(&self) -> Watch { self.async_collection.watch() } From 0a0a206a28dbb6112c3f6d17d7827da8c8a948d1 Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 8 Sep 2025 13:13:37 +0100 Subject: [PATCH 31/32] much doc, such improvement --- etc/gen_atlas_search/src/main.rs | 3 + src/atlas_search.rs | 119 ++++++++++++++++++++++--------- src/atlas_search/gen.rs | 35 +++++++++ 3 files changed, 124 insertions(+), 33 deletions(-) diff --git a/etc/gen_atlas_search/src/main.rs b/etc/gen_atlas_search/src/main.rs index be45e9702..513abf3e7 100644 --- a/etc/gen_atlas_search/src/main.rs +++ b/etc/gen_atlas_search/src/main.rs @@ -98,6 +98,7 @@ impl Operator { #[doc = #desc] #[doc = ""] #[doc = #link] + #[options_doc(#constr_ident, "into_stage")] pub fn #constr_ident(#required_args) -> SearchOperator<#name_ident> { SearchOperator::new( #name_text, @@ -105,6 +106,7 @@ impl Operator { ) } + #[export_doc(#constr_ident)] impl SearchOperator<#name_ident> { #setters } @@ -270,6 +272,7 @@ fn main() { let file = parse_quote! { //! This file was autogenerated. Do not manually edit. use super::*; + use mongodb_internal_macros::{export_doc, options_doc}; #operators }; diff --git a/src/atlas_search.rs b/src/atlas_search.rs index bd9a8b67f..efc4f6b57 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -1,5 +1,28 @@ -//! Helpers for building Atlas Search aggregation pipelines. - +//! Helpers for building Atlas Search aggregation pipelines. Use one of the constructor functions +//! and chain optional value setters, and then convert to a pipeline stage [`Document`] via +//! [`into_stage`](SearchOperator::into_stage). +//! +//! ```no_run +//! # async fn wrapper() -> mongodb::error::Result<()> { +//! # use mongodb::{Collection, bson::{Document, doc}}; +//! # let collection: Collection = todo!(); +//! use mongodb::atlas_search; +//! let cursor = collection.aggregate(vec![ +//! atlas_search::autocomplete("title", "pre") +//! .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) +//! .into_stage(), +//! doc! { +//! "$limit": 10, +//! }, +//! doc! { +//! "$project": { +//! "_id": 0, +//! "title": 1, +//! } +//! }, +//! ]).await?; +//! # Ok(()) +//! # } mod gen; pub use gen::*; @@ -7,32 +30,9 @@ pub use gen::*; use std::marker::PhantomData; use crate::bson::{doc, Bson, DateTime, Document}; +use mongodb_internal_macros::{export_doc, options_doc}; -/// A helper to build the aggregation stage for Atlas Search. Use one of the constructor functions -/// and chain optional value setters, and then convert to a pipeline stage [`Document`] via -/// [`into_stage`](SearchOperator::into_stage). -/// -/// ```no_run -/// # async fn wrapper() -> mongodb::error::Result<()> { -/// # use mongodb::{Collection, bson::{Document, doc}}; -/// # let collection: Collection = todo!(); -/// use mongodb::atlas_search; -/// let cursor = collection.aggregate(vec![ -/// atlas_search::autocomplete("title", "pre") -/// .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) -/// .into_stage(), -/// doc! { -/// "$limit": 10, -/// }, -/// doc! { -/// "$project": { -/// "_id": 0, -/// "title": 1, -/// } -/// }, -/// ]).await?; -/// # Ok(()) -/// # } +/// A helper to build the aggregation stage for Atlas Search. pub struct SearchOperator { pub(crate) name: &'static str, pub(crate) spec: Document, @@ -88,17 +88,44 @@ impl SearchOperator { /// Finalize a search operator as a pending `$search` aggregation stage, allowing /// options to be set. +/// ```no_run +/// # async fn wrapper() -> mongodb::error::Result<()> { +/// # use mongodb::{Collection, bson::{Document, doc}}; +/// # let collection: Collection = todo!(); +/// use mongodb::atlas_search::{autocomplete, search}; +/// let cursor = collection.aggregate(vec![ +/// search( +/// autocomplete("title", "pre") +/// .fuzzy(doc! { "maxEdits": 1, "prefixLength": 1, "maxExpansions": 256 }) +/// ) +/// .index("movies") +/// .into_stage(), +/// doc! { +/// "$limit": 10, +/// }, +/// doc! { +/// "$project": { +/// "_id": 0, +/// "title": 1, +/// } +/// }, +/// ]).await?; +/// # Ok(()) +/// # } +/// ``` +#[options_doc(atlas_search, "into_stage")] pub fn search(op: SearchOperator) -> AtlasSearch { AtlasSearch { stage: doc! { op.name: op.spec }, } } -/// A pending `$search` aggregation stage. +/// A pending `$search` aggregation stage. Construct with [`search`]. pub struct AtlasSearch { stage: Document, } +#[export_doc(atlas_search)] impl AtlasSearch { /// Parallelize search across segments on dedicated search nodes. pub fn concurrent(mut self, value: bool) -> Self { @@ -166,17 +193,43 @@ impl AtlasSearch { /// Finalize a search operator as a pending `$searchMeta` aggregation stage, allowing /// options to be set. +/// ```no_run +/// # async fn wrapper() -> mongodb::error::Result<()> { +/// # use mongodb::{Collection, bson::{DateTime, Document, doc}}; +/// # let collection: Collection = todo!(); +/// # let start: DateTime = todo!(); +/// # let end: DateTime = todo!(); +/// use mongodb::atlas_search::{facet, range, search_meta}; +/// let cursor = collection.aggregate(vec![ +/// search_meta( +/// facet(doc! { +/// "directorsFacet": facet::string("directors").num_buckets(7), +/// "yearFacet": facet::number("year", [2000, 2005, 2010, 2015]), +/// }) +/// .operator(range("released").gte(start).lte(end)) +/// ) +/// .index("movies") +/// .into_stage(), +/// doc! { +/// "$limit": 10, +/// }, +/// ]).await?; +/// # Ok(()) +/// # } +/// ``` +#[options_doc(atlas_search_meta, "into_stage")] pub fn search_meta(op: SearchOperator) -> AtlasSearchMeta { AtlasSearchMeta { stage: doc! { op.name: op.spec }, } } -/// A pending `$searchMeta` aggregation stage. +/// A pending `$searchMeta` aggregation stage. Construct with [`search_meta`]. pub struct AtlasSearchMeta { stage: Document, } +#[export_doc(atlas_search_meta)] impl AtlasSearchMeta { /// Document that specifies the count options for retrieving a count of the results. pub fn count(mut self, value: Document) -> Self { @@ -311,7 +364,7 @@ pub mod facet { } } - #[allow(missing_docs)] + /// A string facet. Construct with [`facet::string`](string). pub struct String; /// String facets allow you to narrow down Atlas Search results based on the most frequent /// string values in the specified string field. @@ -333,7 +386,7 @@ pub mod facet { } } - #[allow(missing_docs)] + /// A number facet. Construct with [`facet::number`](number). pub struct Number; /// Numeric facets allow you to determine the frequency of numeric values in your search results /// by breaking the results into separate ranges of numbers. @@ -359,7 +412,7 @@ pub mod facet { } } - #[allow(missing_docs)] + /// A date facet. Construct with [`facet::date`](date). pub struct Date; /// Date facets allow you to narrow down search results based on a date. pub fn date( @@ -441,7 +494,7 @@ numeric! { NearOrigin } pub trait BsonNumber: private::Parameter {} numeric! { BsonNumber } -/// At Atlas Search operator parameter that can be compared using [`range`]. +/// An Atlas Search operator parameter that can be compared using [`range`]. pub trait RangeValue: private::Parameter {} numeric! { RangeValue } impl RangeValue for DateTime {} diff --git a/src/atlas_search/gen.rs b/src/atlas_search/gen.rs index 96f83f956..0c4eb4274 100644 --- a/src/atlas_search/gen.rs +++ b/src/atlas_search/gen.rs @@ -1,5 +1,6 @@ //! This file was autogenerated. Do not manually edit. use super::*; +use mongodb_internal_macros::{export_doc, options_doc}; ///`autocomplete` Atlas Search operator. Construct with [`autocomplete`](autocomplete()). pub struct Autocomplete; /**The autocomplete operator performs a search for a word or phrase that @@ -9,6 +10,7 @@ indexed with the autocomplete data type in the collection's index definition. */ /// ///For more details, see the [autocomplete operator reference](https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/). +#[options_doc(autocomplete, "into_stage")] pub fn autocomplete( path: impl StringOrArray, query: impl StringOrArray, @@ -20,6 +22,7 @@ pub fn autocomplete( }, ) } +#[export_doc(autocomplete)] impl SearchOperator { #[allow(missing_docs)] pub fn token_order(mut self, token_order: TokenOrder) -> Self { @@ -45,9 +48,11 @@ consists of one or more sub-queries. */ /// ///For more details, see the [compound operator reference](https://www.mongodb.com/docs/atlas/atlas-search/compound/). +#[options_doc(compound, "into_stage")] pub fn compound() -> SearchOperator { SearchOperator::new("compound", doc! {}) } +#[export_doc(compound)] impl SearchOperator { #[allow(missing_docs)] pub fn must(mut self, must: impl IntoIterator) -> Self { @@ -108,6 +113,7 @@ for queries over fields of the embeddedDocuments */ /// ///For more details, see the [embeddedDocument operator reference](https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/). +#[options_doc(embedded_document, "into_stage")] pub fn embedded_document( path: impl StringOrArray, operator: impl SearchOperatorParam, @@ -119,6 +125,7 @@ pub fn embedded_document( }, ) } +#[export_doc(embedded_document)] impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -132,6 +139,7 @@ pub struct Equals; * */ /// ///For more details, see the [equals operator reference](https://www.mongodb.com/docs/atlas/atlas-search/equals/). +#[options_doc(equals, "into_stage")] pub fn equals(path: impl StringOrArray, value: impl Into) -> SearchOperator { SearchOperator::new( "equals", @@ -140,6 +148,7 @@ pub fn equals(path: impl StringOrArray, value: impl Into) -> SearchOperato }, ) } +#[export_doc(equals)] impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -153,6 +162,7 @@ pub struct Exists; * */ /// ///For more details, see the [exists operator reference](https://www.mongodb.com/docs/atlas/atlas-search/exists/). +#[options_doc(exists, "into_stage")] pub fn exists(path: impl StringOrArray) -> SearchOperator { SearchOperator::new( "exists", @@ -161,6 +171,7 @@ pub fn exists(path: impl StringOrArray) -> SearchOperator { }, ) } +#[export_doc(exists)] impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -175,6 +186,7 @@ faceted fields and returns the count for each of those groups. */ /// ///For more details, see the [facet operator reference](https://www.mongodb.com/docs/atlas/atlas-search/facet/). +#[options_doc(facet, "into_stage")] pub fn facet(facets: Document) -> SearchOperator { SearchOperator::new( "facet", @@ -183,6 +195,7 @@ pub fn facet(facets: Document) -> SearchOperator { }, ) } +#[export_doc(facet)] impl SearchOperator { #[allow(missing_docs)] pub fn operator(mut self, operator: impl SearchOperatorParam) -> Self { @@ -197,6 +210,7 @@ geometry if indexShapes is set to true in the index definition. */ /// ///For more details, see the [geoShape operator reference](https://www.mongodb.com/docs/atlas/atlas-search/geoShape/). +#[options_doc(geo_shape, "into_stage")] pub fn geo_shape( path: impl StringOrArray, relation: Relation, @@ -209,6 +223,7 @@ pub fn geo_shape( }, ) } +#[export_doc(geo_shape)] impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -224,6 +239,7 @@ the index definition. */ /// ///For more details, see the [geoWithin operator reference](https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/). +#[options_doc(geo_within, "into_stage")] pub fn geo_within(path: impl StringOrArray) -> SearchOperator { SearchOperator::new( "geoWithin", @@ -232,6 +248,7 @@ pub fn geo_within(path: impl StringOrArray) -> SearchOperator { }, ) } +#[export_doc(geo_within)] impl SearchOperator { #[allow(missing_docs)] pub fn geo_box(mut self, geo_box: Document) -> Self { @@ -260,6 +277,7 @@ pub struct SearchIn; * */ /// ///For more details, see the [in operator reference](https://www.mongodb.com/docs/atlas/atlas-search/in/). +#[options_doc(search_in, "into_stage")] pub fn search_in(path: impl StringOrArray, value: impl Into) -> SearchOperator { SearchOperator::new( "in", @@ -268,6 +286,7 @@ pub fn search_in(path: impl StringOrArray, value: impl Into) -> SearchOper }, ) } +#[export_doc(search_in)] impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -283,6 +302,7 @@ that display similar or alternative results based on one or more given documents */ /// ///For more details, see the [moreLikeThis operator reference](https://www.mongodb.com/docs/atlas/atlas-search/moreLikeThis/). +#[options_doc(more_like_this, "into_stage")] pub fn more_like_this(like: impl DocumentOrArray) -> SearchOperator { SearchOperator::new( "moreLikeThis", @@ -291,6 +311,7 @@ pub fn more_like_this(like: impl DocumentOrArray) -> SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -304,6 +325,7 @@ pub struct Near; * */ /// ///For more details, see the [near operator reference](https://www.mongodb.com/docs/atlas/atlas-search/near/). +#[options_doc(near, "into_stage")] pub fn near( path: impl StringOrArray, origin: impl NearOrigin, @@ -317,6 +339,7 @@ pub fn near( }, ) } +#[export_doc(near)] impl SearchOperator { #[allow(missing_docs)] pub fn score(mut self, score: Document) -> Self { @@ -331,6 +354,7 @@ pub struct Phrase; * */ /// ///For more details, see the [phrase operator reference](https://www.mongodb.com/docs/atlas/atlas-search/phrase/). +#[options_doc(phrase, "into_stage")] pub fn phrase(path: impl StringOrArray, query: impl StringOrArray) -> SearchOperator { SearchOperator::new( "phrase", @@ -339,6 +363,7 @@ pub fn phrase(path: impl StringOrArray, query: impl StringOrArray) -> SearchOper }, ) } +#[export_doc(phrase)] impl SearchOperator { #[allow(missing_docs)] pub fn slop(mut self, slop: i32) -> Self { @@ -361,6 +386,7 @@ pub struct QueryString; /// /// ///For more details, see the [queryString operator reference](https://www.mongodb.com/docs/atlas/atlas-search/queryString/). +#[options_doc(query_string, "into_stage")] pub fn query_string( default_path: impl StringOrArray, query: impl AsRef, @@ -372,6 +398,7 @@ pub fn query_string( }, ) } +#[export_doc(query_string)] impl SearchOperator {} ///`range` Atlas Search operator. Construct with [`range`](range()). pub struct Range; @@ -380,6 +407,7 @@ You can use this operator to find results that are within a given numeric, date, */ /// ///For more details, see the [range operator reference](https://www.mongodb.com/docs/atlas/atlas-search/range/). +#[options_doc(range, "into_stage")] pub fn range(path: impl StringOrArray) -> SearchOperator { SearchOperator::new( "range", @@ -388,6 +416,7 @@ pub fn range(path: impl StringOrArray) -> SearchOperator { }, ) } +#[export_doc(range)] impl SearchOperator { #[allow(missing_docs)] pub fn gt(mut self, gt: impl RangeValue) -> Self { @@ -422,6 +451,7 @@ regex is a term-level operator, meaning that the query field isn't analyzed. */ /// ///For more details, see the [regex operator reference](https://www.mongodb.com/docs/atlas/atlas-search/regex/). +#[options_doc(regex, "into_stage")] pub fn regex(path: impl StringOrArray, query: impl AsRef) -> SearchOperator { SearchOperator::new( "regex", @@ -430,6 +460,7 @@ pub fn regex(path: impl StringOrArray, query: impl AsRef) -> SearchOperator }, ) } +#[export_doc(regex)] impl SearchOperator { #[allow(missing_docs)] pub fn allow_analyzed_field(mut self, allow_analyzed_field: bool) -> Self { @@ -449,6 +480,7 @@ If you omit an analyzer, the text operator uses the default standard analyzer. */ /// ///For more details, see the [text operator reference](https://www.mongodb.com/docs/atlas/atlas-search/text/). +#[options_doc(text, "into_stage")] pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> SearchOperator { SearchOperator::new( "text", @@ -457,6 +489,7 @@ pub fn text(path: impl StringOrArray, query: impl StringOrArray) -> SearchOperat }, ) } +#[export_doc(text)] impl SearchOperator { #[allow(missing_docs)] pub fn fuzzy(mut self, fuzzy: Document) -> Self { @@ -486,6 +519,7 @@ pub struct Wildcard; * */ /// ///For more details, see the [wildcard operator reference](https://www.mongodb.com/docs/atlas/atlas-search/wildcard/). +#[options_doc(wildcard, "into_stage")] pub fn wildcard(path: impl StringOrArray, query: impl AsRef) -> SearchOperator { SearchOperator::new( "wildcard", @@ -494,6 +528,7 @@ pub fn wildcard(path: impl StringOrArray, query: impl AsRef) -> SearchOpera }, ) } +#[export_doc(wildcard)] impl SearchOperator { #[allow(missing_docs)] pub fn allow_analyzed_field(mut self, allow_analyzed_field: bool) -> Self { From fe9c537aa1bf25173f86a91a84cce58b9ba5d15d Mon Sep 17 00:00:00 2001 From: Abraham Egnor Date: Mon, 8 Sep 2025 13:21:01 +0100 Subject: [PATCH 32/32] missed a bucket --- src/atlas_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atlas_search.rs b/src/atlas_search.rs index efc4f6b57..f0f6fd0a0 100644 --- a/src/atlas_search.rs +++ b/src/atlas_search.rs @@ -431,7 +431,7 @@ pub mod facet { impl Facet { /// Name of an additional bucket that counts documents returned from the operator that do /// not fall within the specified boundaries. - pub fn default(mut self, bucket: impl AsRef) -> Self { + pub fn default_bucket(mut self, bucket: impl AsRef) -> Self { self.inner.insert("default", bucket.as_ref()); self }