-
Notifications
You must be signed in to change notification settings - Fork 176
RUST-1667 Add search index management helpers #989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
34b73a1
ee9f500
7bd3649
18f0a80
34c98f5
fdb8a41
0587f6d
7f5ac5f
75e1325
1b94f33
6daaffc
e35036e
c276608
3d6ee3e
161ceae
3f9c6cb
fd6c080
fe8e6a1
9be5536
00202d2
8f54a8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
use bson::{doc, Document}; | ||
use serde::Deserialize; | ||
|
||
use crate::{cmap::Command, error::Result, Namespace, SearchIndexModel}; | ||
|
||
use super::OperationWithDefaults; | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct CreateSearchIndexes { | ||
ns: Namespace, | ||
indexes: Vec<SearchIndexModel>, | ||
} | ||
|
||
impl CreateSearchIndexes { | ||
pub(crate) fn new(ns: Namespace, indexes: Vec<SearchIndexModel>) -> Self { | ||
Self { ns, indexes } | ||
} | ||
} | ||
|
||
impl OperationWithDefaults for CreateSearchIndexes { | ||
type O = Vec<String>; | ||
type Command = Document; | ||
const NAME: &'static str = "createSearchIndexes"; | ||
|
||
fn build(&mut self, _description: &crate::cmap::StreamDescription) -> Result<Command> { | ||
Ok(Command::new( | ||
Self::NAME.to_string(), | ||
self.ns.db.clone(), | ||
doc! { | ||
Self::NAME: self.ns.coll.clone(), | ||
"indexes": bson::to_bson(&self.indexes)?, | ||
}, | ||
)) | ||
} | ||
|
||
fn handle_response( | ||
&self, | ||
response: crate::cmap::RawCommandResponse, | ||
_description: &crate::cmap::StreamDescription, | ||
) -> Result<Self::O> { | ||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct Response { | ||
indexes_created: Vec<CreatedIndex>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
struct CreatedIndex { | ||
#[allow(unused)] | ||
id: String, | ||
name: String, | ||
} | ||
|
||
let response: Response = response.body()?; | ||
Ok(response | ||
.indexes_created | ||
.into_iter() | ||
.map(|ci| ci.name) | ||
.collect()) | ||
} | ||
|
||
fn supports_sessions(&self) -> bool { | ||
false | ||
} | ||
|
||
fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { | ||
false | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct UpdateSearchIndex { | ||
ns: Namespace, | ||
name: String, | ||
definition: Document, | ||
} | ||
|
||
impl UpdateSearchIndex { | ||
pub(crate) fn new(ns: Namespace, name: String, definition: Document) -> Self { | ||
Self { | ||
ns, | ||
name, | ||
definition, | ||
} | ||
} | ||
} | ||
|
||
impl OperationWithDefaults for UpdateSearchIndex { | ||
type O = (); | ||
type Command = Document; | ||
const NAME: &'static str = "updateSearchIndex"; | ||
|
||
fn build( | ||
&mut self, | ||
_description: &crate::cmap::StreamDescription, | ||
) -> crate::error::Result<crate::cmap::Command<Self::Command>> { | ||
Ok(Command::new( | ||
Self::NAME.to_string(), | ||
self.ns.db.clone(), | ||
doc! { | ||
Self::NAME: self.ns.coll.clone(), | ||
"name": &self.name, | ||
"definition": &self.definition, | ||
}, | ||
)) | ||
} | ||
|
||
fn handle_response( | ||
&self, | ||
response: crate::cmap::RawCommandResponse, | ||
_description: &crate::cmap::StreamDescription, | ||
) -> crate::error::Result<Self::O> { | ||
response.body() | ||
} | ||
|
||
fn supports_sessions(&self) -> bool { | ||
false | ||
} | ||
|
||
fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { | ||
false | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct DropSearchIndex { | ||
ns: Namespace, | ||
name: String, | ||
} | ||
|
||
impl DropSearchIndex { | ||
pub(crate) fn new(ns: Namespace, name: String) -> Self { | ||
Self { ns, name } | ||
} | ||
} | ||
|
||
impl OperationWithDefaults for DropSearchIndex { | ||
type O = (); | ||
type Command = Document; | ||
const NAME: &'static str = "dropSearchIndex"; | ||
|
||
fn build( | ||
&mut self, | ||
_description: &crate::cmap::StreamDescription, | ||
) -> Result<Command<Self::Command>> { | ||
Ok(Command::new( | ||
Self::NAME.to_string(), | ||
self.ns.db.clone(), | ||
doc! { | ||
Self::NAME: self.ns.coll.clone(), | ||
"name": &self.name, | ||
}, | ||
)) | ||
} | ||
|
||
fn handle_response( | ||
&self, | ||
response: crate::cmap::RawCommandResponse, | ||
_description: &crate::cmap::StreamDescription, | ||
) -> Result<Self::O> { | ||
response.body() | ||
} | ||
|
||
fn handle_error(&self, error: crate::error::Error) -> Result<Self::O> { | ||
if error.is_ns_not_found() { | ||
Ok(()) | ||
} else { | ||
Err(error) | ||
} | ||
} | ||
|
||
fn supports_sessions(&self) -> bool { | ||
false | ||
} | ||
|
||
fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { | ||
false | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
use self::options::*; | ||
use crate::{ | ||
bson::Document, | ||
coll::options::AggregateOptions, | ||
error::{Error, Result}, | ||
operation::{CreateSearchIndexes, DropSearchIndex, UpdateSearchIndex}, | ||
Collection, | ||
Cursor, | ||
}; | ||
|
||
use bson::doc; | ||
use serde::{Deserialize, Serialize}; | ||
use typed_builder::TypedBuilder; | ||
|
||
impl<T> Collection<T> { | ||
/// Convenience method for creating a single search index. | ||
pub async fn create_search_index( | ||
&self, | ||
model: SearchIndexModel, | ||
options: impl Into<Option<CreateSearchIndexOptions>>, | ||
) -> Result<String> { | ||
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<Item = SearchIndexModel>, | ||
_options: impl Into<Option<CreateSearchIndexOptions>>, | ||
) -> Result<Vec<String>> { | ||
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<str>, | ||
definition: Document, | ||
_options: impl Into<Option<UpdateSearchIndexOptions>>, | ||
) -> 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<str>, | ||
_options: impl Into<Option<DropSearchIndexOptions>>, | ||
) -> 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<Option<&str>>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bummer that nested I wonder if it would be more ergonomic to make this type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went down a rabbit hole here - I originally tried having both types be full parameters with I'd lean towards keeping the |
||
aggregation_options: impl Into<Option<AggregateOptions>>, | ||
_list_index_options: impl Into<Option<ListSearchIndexOptions>>, | ||
) -> Result<Cursor<Document>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could be nice to return a concrete type in this cursor like we do for the regular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec doesn't actually say, it just gives the return as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An update here - the document returned isn't at all in the shape of
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh interesting, I was going to say that I would prefer consistency with our existing API but that's not much of a concern if the return types are so different. Sticking with |
||
let mut inner = doc! {}; | ||
if let Some(name) = name.into() { | ||
inner.insert("name", name.to_string()); | ||
} | ||
self.clone_unconcerned() | ||
.aggregate( | ||
vec![doc! { | ||
"$listSearchIndexes": inner, | ||
}], | ||
aggregation_options, | ||
) | ||
.await | ||
} | ||
} | ||
|
||
/// Specifies the options for a search index. | ||
#[derive(Debug, Clone, Default, TypedBuilder, Serialize, Deserialize)] | ||
#[builder(field_defaults(default, setter(into)))] | ||
#[non_exhaustive] | ||
isabelatkinson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub struct SearchIndexModel { | ||
/// The definition for this index. | ||
pub definition: Document, | ||
|
||
/// The name for this index, if present. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub name: Option<String>, | ||
} | ||
|
||
pub mod options { | ||
#[cfg(docsrs)] | ||
use crate::Collection; | ||
use serde::Deserialize; | ||
use typed_builder::TypedBuilder; | ||
|
||
/// Options for [Collection::create_search_index]. Present to allow additional options to be | ||
/// added in the future as a non-breaking change. | ||
#[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] | ||
#[builder(field_defaults(default, setter(into)))] | ||
#[non_exhaustive] | ||
pub struct CreateSearchIndexOptions {} | ||
|
||
/// Options for [Collection::update_search_index]. Present to allow additional options to be | ||
/// added in the future as a non-breaking change. | ||
#[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] | ||
#[builder(field_defaults(default, setter(into)))] | ||
#[non_exhaustive] | ||
pub struct UpdateSearchIndexOptions {} | ||
|
||
/// Options for [Collection::list_search_indexes]. Present to allow additional options to be | ||
/// added in the future as a non-breaking change. | ||
#[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] | ||
#[builder(field_defaults(default, setter(into)))] | ||
#[non_exhaustive] | ||
pub struct ListSearchIndexOptions {} | ||
|
||
/// Options for [Collection::drop_search_index]. Present to allow additional options to be | ||
/// added in the future as a non-breaking change. | ||
#[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] | ||
#[builder(field_defaults(default, setter(into)))] | ||
#[non_exhaustive] | ||
pub struct DropSearchIndexOptions {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
use crate::test::spec::unified_runner::run_unified_tests; | ||
|
||
#[cfg_attr(feature = "tokio-runtime", tokio::test)] | ||
#[cfg_attr(feature = "async-std-runtime", async_std::test)] | ||
async fn run() { | ||
run_unified_tests(&["index-management"]).await; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? IIRC these options are propagated from the client when a db/coll is instantiated and then resolved with the
resolve_options!
macro, so I think they will only get applied if they're explicitly folded into the options provided to a method (which we're not doing in these helpers)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's needed for
list_search_indexes
, which is implemented as anaggregate
call.