diff --git a/src/action.rs b/src/action.rs index 7ca30e95a..b6e5d31c3 100644 --- a/src/action.rs +++ b/src/action.rs @@ -21,6 +21,7 @@ mod list_indexes; mod perf; mod replace_one; mod run_command; +mod search_index; mod session; mod shutdown; mod update; @@ -47,6 +48,7 @@ pub use list_indexes::ListIndexes; pub use perf::WarmConnectionPool; pub use replace_one::ReplaceOne; pub use run_command::{RunCommand, RunCursorCommand}; +pub use search_index::{CreateSearchIndex, DropSearchIndex, ListSearchIndexes, UpdateSearchIndex}; pub use session::StartSession; pub use shutdown::Shutdown; pub use update::Update; @@ -76,6 +78,7 @@ macro_rules! option_setters { $opt_name:ident: $opt_ty:ty, )* ) => { + #[allow(unused)] fn options(&mut self) -> &mut $opt_field_ty { self.$opt_field.get_or_insert_with(<$opt_field_ty>::default) } diff --git a/src/action/create_collection.rs b/src/action/create_collection.rs index 69423f531..8fb90b9c6 100644 --- a/src/action/create_collection.rs +++ b/src/action/create_collection.rs @@ -12,10 +12,10 @@ impl Database { /// /// `await` will return d[`Result<()>`]. #[deeplink] - pub fn create_collection(&self, name: impl AsRef) -> CreateCollection { + pub fn create_collection(&self, name: impl Into) -> CreateCollection { CreateCollection { db: self, - name: name.as_ref().to_owned(), + name: name.into(), options: None, session: None, } @@ -31,7 +31,7 @@ impl crate::sync::Database { /// /// [`run`](CreateCollection::run) will return d[`Result<()>`]. #[deeplink] - pub fn create_collection(&self, name: impl AsRef) -> CreateCollection { + pub fn create_collection(&self, name: impl Into) -> CreateCollection { self.async_database.create_collection(name) } } diff --git a/src/action/gridfs/download.rs b/src/action/gridfs/download.rs index 7e4ae0e55..884156637 100644 --- a/src/action/gridfs/download.rs +++ b/src/action/gridfs/download.rs @@ -30,11 +30,11 @@ impl GridFsBucket { #[deeplink] pub fn open_download_stream_by_name( &self, - filename: impl AsRef, + filename: impl Into, ) -> OpenDownloadStreamByName { OpenDownloadStreamByName { bucket: self, - filename: filename.as_ref().to_owned(), + filename: filename.into(), options: None, } } @@ -113,7 +113,7 @@ impl crate::sync::gridfs::GridFsBucket { #[deeplink] pub fn open_download_stream_by_name( &self, - filename: impl AsRef, + filename: impl Into, ) -> OpenDownloadStreamByName { self.async_bucket.open_download_stream_by_name(filename) } diff --git a/src/action/gridfs/rename.rs b/src/action/gridfs/rename.rs index aa9f6601d..18ec58d58 100644 --- a/src/action/gridfs/rename.rs +++ b/src/action/gridfs/rename.rs @@ -7,11 +7,11 @@ impl GridFsBucket { /// error if the `id` does not match any files in the bucket. /// /// `await` will return [`Result<()>`]. - pub fn rename(&self, id: Bson, new_filename: impl AsRef) -> Rename { + pub fn rename(&self, id: Bson, new_filename: impl Into) -> Rename { Rename { bucket: self, id, - new_filename: new_filename.as_ref().to_owned(), + new_filename: new_filename.into(), } } } @@ -22,7 +22,7 @@ impl crate::sync::gridfs::GridFsBucket { /// error if the `id` does not match any files in the bucket. /// /// [`run`](Rename::run) will return [`Result<()>`]. - pub fn rename(&self, id: Bson, new_filename: impl AsRef) -> Rename { + pub fn rename(&self, id: Bson, new_filename: impl Into) -> Rename { self.async_bucket.rename(id, new_filename) } } diff --git a/src/action/search_index.rs b/src/action/search_index.rs new file mode 100644 index 000000000..f32392bfc --- /dev/null +++ b/src/action/search_index.rs @@ -0,0 +1,296 @@ +use std::marker::PhantomData; + +use bson::{doc, Document}; + +use super::{action_impl, deeplink, option_setters, CollRef, Multiple, Single}; +use crate::{ + coll::options::AggregateOptions, + error::{Error, Result}, + operation, + search_index::options::{ + CreateSearchIndexOptions, + DropSearchIndexOptions, + ListSearchIndexOptions, + UpdateSearchIndexOptions, + }, + Collection, + Cursor, + SearchIndexModel, +}; + +impl Collection +where + T: Send + Sync, +{ + /// Creates multiple search indexes on the collection. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + pub fn create_search_indexes( + &self, + models: impl IntoIterator, + ) -> CreateSearchIndex { + CreateSearchIndex { + coll: CollRef::new(self), + models: models.into_iter().collect(), + options: None, + _mode: PhantomData, + } + } + + /// Convenience method for creating a single search index. + /// + /// `await` will return d[`Result`]. + #[deeplink] + pub fn create_search_index(&self, model: SearchIndexModel) -> CreateSearchIndex { + CreateSearchIndex { + coll: CollRef::new(self), + models: vec![model], + options: None, + _mode: PhantomData, + } + } + + /// Updates the search index with the given name to use the provided definition. + /// + /// `await` will return [`Result<()>`]. + pub fn update_search_index( + &self, + name: impl Into, + definition: Document, + ) -> UpdateSearchIndex { + UpdateSearchIndex { + coll: CollRef::new(self), + name: name.into(), + definition, + options: None, + } + } + + /// Drops the search index with the given name. + /// + /// `await` will return [`Result<()>`]. + pub fn drop_search_index(&self, name: impl Into) -> DropSearchIndex { + DropSearchIndex { + coll: CollRef::new(self), + name: name.into(), + options: None, + } + } + + /// Gets index information for one or more search indexes in the collection. + /// + /// If name is not specified, information for all indexes on the specified collection will be + /// returned. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + pub fn list_search_indexes(&self) -> ListSearchIndexes { + ListSearchIndexes { + coll: CollRef::new(self), + name: None, + agg_options: None, + options: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Creates multiple search indexes on the collection. + /// + /// [`run`](CreateSearchIndex::run) will return d[`Result>`]. + #[deeplink] + pub fn create_search_indexes( + &self, + models: impl IntoIterator, + ) -> CreateSearchIndex { + self.async_collection.create_search_indexes(models) + } + + /// Convenience method for creating a single search index. + /// + /// [`run`](CreateSearchIndex::run) will return d[`Result`]. + #[deeplink] + pub fn create_search_index(&self, model: SearchIndexModel) -> CreateSearchIndex { + self.async_collection.create_search_index(model) + } + + /// Updates the search index with the given name to use the provided definition. + /// + /// [`run`](UpdateSearchIndex::run) will return [`Result<()>`]. + pub fn update_search_index( + &self, + name: impl Into, + definition: Document, + ) -> UpdateSearchIndex { + self.async_collection.update_search_index(name, definition) + } + + /// Drops the search index with the given name. + /// + /// [`run`](DropSearchIndex::run) will return [`Result<()>`]. + pub fn drop_search_index(&self, name: impl Into) -> DropSearchIndex { + self.async_collection.drop_search_index(name) + } + + /// Gets index information for one or more search indexes in the collection. + /// + /// If name is not specified, information for all indexes on the specified collection will be + /// returned. + /// + /// [`run`](ListSearchIndexes::run) will return d[`Result>`]. + #[deeplink] + pub fn list_search_indexes(&self) -> ListSearchIndexes { + self.async_collection.list_search_indexes() + } +} + +/// Create search indexes on a collection. Construct with [`Collection::create_search_index`] or +/// [`Collection::create_search_indexes`]. +#[must_use] +pub struct CreateSearchIndex<'a, Mode> { + coll: CollRef<'a>, + models: Vec, + options: Option, + _mode: PhantomData, +} + +impl<'a, Mode> CreateSearchIndex<'a, Mode> { + option_setters! { options: CreateSearchIndexOptions; + } +} + +action_impl! { + impl<'a> Action for CreateSearchIndex<'a, Multiple> { + type Future = CreateSearchIndexesFuture; + + async fn execute(self) -> Result> { + let op = operation::CreateSearchIndexes::new(self.coll.namespace(), self.models); + self.coll.client().execute_operation(op, None).await + } + } +} + +action_impl! { + impl<'a> Action for CreateSearchIndex<'a, Single> { + type Future = CreateSearchIndexFuture; + + async fn execute(self) -> Result { + let mut names = self.coll + .create_search_indexes(self.models) + .with_options(self.options).await?; + match names.len() { + 1 => Ok(names.pop().unwrap()), + n => Err(Error::internal(format!("expected 1 index name, got {}", n))), + } + } + } +} + +/// Updates a specific search index to use a new definition. Construct with +/// [`Collection::update_search_index`]. +#[must_use] +pub struct UpdateSearchIndex<'a> { + coll: CollRef<'a>, + name: String, + definition: Document, + options: Option, +} + +impl<'a> UpdateSearchIndex<'a> { + option_setters! { options: UpdateSearchIndexOptions; } +} + +action_impl! { + impl<'a> Action for UpdateSearchIndex<'a> { + type Future = UpdateSearchIndexFuture; + + async fn execute(self) -> Result<()> { + let op = operation::UpdateSearchIndex::new( + self.coll.namespace(), + self.name, + self.definition, + ); + self.coll.client().execute_operation(op, None).await + } + } +} + +/// Drops a specific search index. Construct with [`Collection::drop_search_index`]. +#[must_use] +pub struct DropSearchIndex<'a> { + coll: CollRef<'a>, + name: String, + options: Option, +} + +impl<'a> DropSearchIndex<'a> { + option_setters! { options: DropSearchIndexOptions; } +} + +action_impl! { + impl<'a> Action for DropSearchIndex<'a> { + type Future = DropSearchIndexFuture; + + async fn execute(self) -> Result<()> { + let op = operation::DropSearchIndex::new( + self.coll.namespace(), + self.name, + ); + self.coll.client().execute_operation(op, None).await + } + } +} + +/// Gets index information for one or more search indexes in a collection. +#[must_use] +pub struct ListSearchIndexes<'a> { + coll: CollRef<'a>, + name: Option, + agg_options: Option, + options: Option, +} + +impl<'a> ListSearchIndexes<'a> { + option_setters! { options: ListSearchIndexOptions; } + + /// Get information for the named index. + pub fn name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + /// Set aggregation options. + pub fn aggregate_options(mut self, value: AggregateOptions) -> Self { + self.agg_options = Some(value); + self + } +} + +action_impl! { + impl<'a> Action for ListSearchIndexes<'a> { + type Future = ListSearchIndexesFuture; + + async fn execute(self) -> Result> { + let mut inner = doc! {}; + if let Some(name) = self.name { + inner.insert("name", name); + } + self.coll + .clone_unconcerned() + .aggregate(vec![doc! { + "$listSearchIndexes": inner, + }]) + .with_options(self.agg_options) + .await + } + + fn sync_wrap(out) -> Result> { + out.map(crate::sync::Cursor::new) + } + } +} diff --git a/src/search_index.rs b/src/search_index.rs index e36156cf5..8c121b32d 100644 --- a/src/search_index.rs +++ b/src/search_index.rs @@ -1,92 +1,9 @@ -use self::options::*; -use crate::{ - bson::Document, - coll::options::AggregateOptions, - error::{Error, Result}, - operation::{CreateSearchIndexes, DropSearchIndex, UpdateSearchIndex}, - Collection, - Cursor, -}; +use crate::bson::Document; use bson::doc; use serde::{Deserialize, Serialize}; use typed_builder::TypedBuilder; -impl Collection -where - T: Send + Sync, -{ - /// Convenience method for creating a single search index. - pub async fn create_search_index( - &self, - model: SearchIndexModel, - options: impl Into>, - ) -> Result { - let mut names = self.create_search_indexes(Some(model), options).await?; - match names.len() { - 1 => Ok(names.pop().unwrap()), - n => Err(Error::internal(format!("expected 1 index name, got {}", n))), - } - } - - /// Creates multiple search indexes on the collection. - pub async fn create_search_indexes( - &self, - models: impl IntoIterator, - _options: impl Into>, - ) -> Result> { - let op = CreateSearchIndexes::new(self.namespace(), models.into_iter().collect()); - self.client().execute_operation(op, None).await - } - - /// Updates the search index with the given name to use the provided definition. - pub async fn update_search_index( - &self, - name: impl AsRef, - definition: Document, - _options: impl Into>, - ) -> Result<()> { - let op = UpdateSearchIndex::new( - self.namespace(), - name.as_ref().to_string(), - definition.clone(), - ); - self.client().execute_operation(op, None).await - } - - /// Drops the search index with the given name. - pub async fn drop_search_index( - &self, - name: impl AsRef, - _options: impl Into>, - ) -> Result<()> { - let op = DropSearchIndex::new(self.namespace(), name.as_ref().to_string()); - self.client().execute_operation(op, None).await - } - - /// Gets index information for one or more search indexes in the collection. - /// - /// If name is not specified, information for all indexes on the specified collection will be - /// returned. - pub async fn list_search_indexes( - &self, - name: impl Into>, - aggregation_options: impl Into>, - _list_index_options: impl Into>, - ) -> Result> { - let mut inner = doc! {}; - if let Some(name) = name.into() { - inner.insert("name", name.to_string()); - } - self.clone_unconcerned() - .aggregate(vec![doc! { - "$listSearchIndexes": inner, - }]) - .with_options(aggregation_options) - .await - } -} - /// Specifies the options for a search index. #[derive(Debug, Clone, Default, TypedBuilder, Serialize, Deserialize)] #[builder(field_defaults(default, setter(into)))] diff --git a/src/test/spec/index_management.rs b/src/test/spec/index_management.rs index 1f223550e..2b1967d0d 100644 --- a/src/test/spec/index_management.rs +++ b/src/test/spec/index_management.rs @@ -45,14 +45,13 @@ async fn search_index_create_list() { .name(String::from("test-search-index")) .definition(doc! { "mappings": { "dynamic": false } }) .build(), - None, ) .await .unwrap(); assert_eq!(name, "test-search-index"); let found = 'outer: loop { - let mut cursor = coll0.list_search_indexes(None, None, None).await.unwrap(); + let mut cursor = coll0.list_search_indexes().await.unwrap(); while let Some(d) = cursor.try_next().await.unwrap() { if d.get_str("name") == Ok("test-search-index") && d.get_bool("queryable") == Ok(true) { break 'outer d; @@ -87,19 +86,16 @@ async fn search_index_create_multiple() { let coll0 = db.collection::(&coll_name); let names = coll0 - .create_search_indexes( - [ - SearchIndexModel::builder() - .name(String::from("test-search-index-1")) - .definition(doc! { "mappings": { "dynamic": false } }) - .build(), - SearchIndexModel::builder() - .name(String::from("test-search-index-2")) - .definition(doc! { "mappings": { "dynamic": false } }) - .build(), - ], - None, - ) + .create_search_indexes([ + SearchIndexModel::builder() + .name(String::from("test-search-index-1")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + SearchIndexModel::builder() + .name(String::from("test-search-index-2")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ]) .await .unwrap(); assert_eq!(names, ["test-search-index-1", "test-search-index-2"]); @@ -107,7 +103,7 @@ async fn search_index_create_multiple() { let mut index1 = None; let mut index2 = None; loop { - let mut cursor = coll0.list_search_indexes(None, None, None).await.unwrap(); + let mut cursor = coll0.list_search_indexes().await.unwrap(); while let Some(d) = cursor.try_next().await.unwrap() { if d.get_str("name") == Ok("test-search-index-1") && d.get_bool("queryable") == Ok(true) { @@ -159,14 +155,13 @@ async fn search_index_drop() { .name(String::from("test-search-index")) .definition(doc! { "mappings": { "dynamic": false } }) .build(), - None, ) .await .unwrap(); assert_eq!(name, "test-search-index"); 'outer: loop { - let mut cursor = coll0.list_search_indexes(None, None, None).await.unwrap(); + let mut cursor = coll0.list_search_indexes().await.unwrap(); while let Some(d) = cursor.try_next().await.unwrap() { if d.get_str("name") == Ok("test-search-index") && d.get_bool("queryable") == Ok(true) { break 'outer; @@ -178,13 +173,10 @@ async fn search_index_drop() { } } - coll0 - .drop_search_index("test-search-index", None) - .await - .unwrap(); + coll0.drop_search_index("test-search-index").await.unwrap(); loop { - let cursor = coll0.list_search_indexes(None, None, None).await.unwrap(); + let cursor = coll0.list_search_indexes().await.unwrap(); if !cursor.has_next() { break; } @@ -217,14 +209,13 @@ async fn search_index_update() { .name(String::from("test-search-index")) .definition(doc! { "mappings": { "dynamic": false } }) .build(), - None, ) .await .unwrap(); assert_eq!(name, "test-search-index"); 'outer: loop { - let mut cursor = coll0.list_search_indexes(None, None, None).await.unwrap(); + let mut cursor = coll0.list_search_indexes().await.unwrap(); while let Some(d) = cursor.try_next().await.unwrap() { if d.get_str("name") == Ok("test-search-index") && d.get_bool("queryable") == Ok(true) { break 'outer; @@ -240,13 +231,12 @@ async fn search_index_update() { .update_search_index( "test-search-index", doc! { "mappings": { "dynamic": true } }, - None, ) .await .unwrap(); let found = 'find: loop { - let mut cursor = coll0.list_search_indexes(None, None, None).await.unwrap(); + let mut cursor = coll0.list_search_indexes().await.unwrap(); while let Some(d) = cursor.try_next().await.unwrap() { if d.get_str("name") == Ok("test-search-index") && d.get_bool("queryable") == Ok(true) @@ -280,8 +270,5 @@ async fn search_index_drop_not_found() { .database("search_index_test") .collection::(&coll_name); - coll0 - .drop_search_index("test-search-index", None) - .await - .unwrap(); + coll0.drop_search_index("test-search-index").await.unwrap(); } diff --git a/src/test/spec/unified_runner/operation/search_index.rs b/src/test/spec/unified_runner/operation/search_index.rs index 461d53df5..e09825865 100644 --- a/src/test/spec/unified_runner/operation/search_index.rs +++ b/src/test/spec/unified_runner/operation/search_index.rs @@ -4,6 +4,7 @@ use futures_util::{FutureExt, TryStreamExt}; use serde::Deserialize; use crate::{ + action::Action, coll::options::AggregateOptions, error::Result, search_index::options::{ @@ -35,7 +36,8 @@ impl TestOperation for CreateSearchIndex { async move { let collection = test_runner.get_collection(id).await; let name = collection - .create_search_index(self.model.clone(), self.options.clone()) + .create_search_index(self.model.clone()) + .with_options(self.options.clone()) .await?; Ok(Some(Bson::String(name).into())) } @@ -60,7 +62,8 @@ impl TestOperation for CreateSearchIndexes { async move { let collection = test_runner.get_collection(id).await; let names = collection - .create_search_indexes(self.models.clone(), self.options.clone()) + .create_search_indexes(self.models.clone()) + .with_options(self.options.clone()) .await?; Ok(Some(to_bson(&names)?.into())) } @@ -85,7 +88,8 @@ impl TestOperation for DropSearchIndex { async move { let collection = test_runner.get_collection(id).await; collection - .drop_search_index(&self.name, self.options.clone()) + .drop_search_index(&self.name) + .with_options(self.options.clone()) .await?; Ok(None) } @@ -111,11 +115,12 @@ impl TestOperation for ListSearchIndexes { async move { let collection = test_runner.get_collection(id).await; let cursor = collection - .list_search_indexes( - self.name.as_deref(), - self.aggregation_options.clone(), - self.options.clone(), - ) + .list_search_indexes() + .optional(self.name.clone(), |a, n| a.name(n)) + .optional(self.aggregation_options.clone(), |a, o| { + a.aggregate_options(o) + }) + .with_options(self.options.clone()) .await?; let values: Vec<_> = cursor.try_collect().await?; Ok(Some(to_bson(&values)?.into())) @@ -142,7 +147,8 @@ impl TestOperation for UpdateSearchIndex { async move { let collection = test_runner.get_collection(id).await; collection - .update_search_index(&self.name, self.definition.clone(), self.options.clone()) + .update_search_index(&self.name, self.definition.clone()) + .with_options(self.options.clone()) .await?; Ok(None) }