From 03663378c499c31f95d126e389736eca2b596c7c Mon Sep 17 00:00:00 2001 From: Isabel Atkinson Date: Wed, 15 May 2024 16:26:36 -0600 Subject: [PATCH] RUST-1858 Add builders for bulk write models --- src/action/bulk_write.rs | 8 +- src/client/options/bulk_write.rs | 299 +++++++++++++----- src/test/bulk_write.rs | 186 +++++------ .../unified_runner/operation/bulk_write.rs | 157 ++------- 4 files changed, 338 insertions(+), 312 deletions(-) diff --git a/src/action/bulk_write.rs b/src/action/bulk_write.rs index 4c8460a70..a5844fcee 100644 --- a/src/action/bulk_write.rs +++ b/src/action/bulk_write.rs @@ -17,9 +17,13 @@ use super::{action_impl, option_setters}; impl Client { pub fn bulk_write( &self, - models: impl IntoIterator, + models: impl IntoIterator>, ) -> BulkWrite { - BulkWrite::new(self, models.into_iter().collect()) + let mut models_vec = Vec::new(); + for model in models.into_iter() { + models_vec.push(model.into()); + } + BulkWrite::new(self, models_vec) } } diff --git a/src/client/options/bulk_write.rs b/src/client/options/bulk_write.rs index b1d965a6d..6380fb912 100644 --- a/src/client/options/bulk_write.rs +++ b/src/client/options/bulk_write.rs @@ -1,7 +1,10 @@ #![allow(missing_docs)] +use std::borrow::Borrow; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use typed_builder::TypedBuilder; use crate::{ bson::{rawdoc, Array, Bson, Document, RawDocumentBuf}, @@ -9,6 +12,7 @@ use crate::{ error::Result, options::{UpdateModifications, WriteConcern}, serde_util::serialize_bool_or_true, + Collection, Namespace, }; @@ -32,66 +36,205 @@ pub struct BulkWriteOptions { #[serde(untagged)] #[non_exhaustive] pub enum WriteModel { - #[non_exhaustive] - InsertOne { - #[serde(skip)] - namespace: Namespace, - document: Document, - }, - #[non_exhaustive] - #[serde(rename_all = "camelCase")] - UpdateOne { - #[serde(skip)] - namespace: Namespace, - filter: Document, - #[serde(rename = "updateMods")] - update: UpdateModifications, - array_filters: Option, - collation: Option, - hint: Option, - upsert: Option, - }, - #[non_exhaustive] - #[serde(rename_all = "camelCase")] - UpdateMany { - #[serde(skip)] - namespace: Namespace, - filter: Document, - #[serde(rename = "updateMods")] - update: UpdateModifications, - array_filters: Option, - collation: Option, - hint: Option, - upsert: Option, - }, - #[non_exhaustive] - #[serde(rename_all = "camelCase")] - ReplaceOne { - #[serde(skip)] - namespace: Namespace, - filter: Document, - #[serde(rename = "updateMods")] - replacement: Document, - collation: Option, - hint: Option, - upsert: Option, - }, - #[non_exhaustive] - DeleteOne { - #[serde(skip)] - namespace: Namespace, - filter: Document, - collation: Option, - hint: Option, - }, - #[non_exhaustive] - DeleteMany { - #[serde(skip)] - namespace: Namespace, + InsertOne(InsertOneModel), + UpdateOne(UpdateOneModel), + UpdateMany(UpdateManyModel), + ReplaceOne(ReplaceOneModel), + DeleteOne(DeleteOneModel), + DeleteMany(DeleteManyModel), +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct InsertOneModel { + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + #[builder(!default)] + pub document: Document, +} + +impl From for WriteModel { + fn from(model: InsertOneModel) -> Self { + Self::InsertOne(model) + } +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct UpdateOneModel { + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + #[builder(!default)] + pub filter: Document, + + #[serde(rename(serialize = "updateMods"))] + #[builder(!default)] + pub update: UpdateModifications, + + pub array_filters: Option, + + pub collation: Option, + + pub hint: Option, + + pub upsert: Option, +} + +impl From for WriteModel { + fn from(model: UpdateOneModel) -> Self { + Self::UpdateOne(model) + } +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct UpdateManyModel { + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + #[builder(!default)] + pub filter: Document, + + #[serde(rename(serialize = "updateMods"))] + #[builder(!default)] + pub update: UpdateModifications, + + pub array_filters: Option, + + pub collation: Option, + + pub hint: Option, + + pub upsert: Option, +} + +impl From for WriteModel { + fn from(model: UpdateManyModel) -> Self { + Self::UpdateMany(model) + } +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct ReplaceOneModel { + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + #[builder(!default)] + pub filter: Document, + + #[serde(rename(serialize = "updateMods"))] + #[builder(!default)] + pub replacement: Document, + + pub collation: Option, + + pub hint: Option, + + pub upsert: Option, +} + +impl From for WriteModel { + fn from(model: ReplaceOneModel) -> Self { + Self::ReplaceOne(model) + } +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct DeleteOneModel { + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + #[builder(!default)] + pub filter: Document, + + pub collation: Option, + + pub hint: Option, +} + +impl From for WriteModel { + fn from(model: DeleteOneModel) -> Self { + Self::DeleteOne(model) + } +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct DeleteManyModel { + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + pub filter: Document, + + pub collation: Option, + + pub hint: Option, +} + +impl From for WriteModel { + fn from(model: DeleteManyModel) -> Self { + Self::DeleteMany(model) + } +} + +impl Collection +where + T: Send + Sync + Serialize, +{ + pub fn insert_one_model(&self, document: impl Borrow) -> Result { + let document = bson::to_document(document.borrow())?; + Ok(InsertOneModel::builder() + .namespace(self.namespace()) + .document(document) + .build()) + } + + pub fn replace_one_model( + &self, filter: Document, - collation: Option, - hint: Option, - }, + replacement: impl Borrow, + ) -> Result { + let replacement = bson::to_document(replacement.borrow())?; + Ok(ReplaceOneModel::builder() + .namespace(self.namespace()) + .filter(filter) + .replacement(replacement) + .build()) + } } pub(crate) enum OperationType { @@ -103,22 +246,20 @@ pub(crate) enum OperationType { impl WriteModel { pub(crate) fn namespace(&self) -> &Namespace { match self { - Self::InsertOne { namespace, .. } => namespace, - Self::UpdateOne { namespace, .. } => namespace, - Self::UpdateMany { namespace, .. } => namespace, - Self::ReplaceOne { namespace, .. } => namespace, - Self::DeleteOne { namespace, .. } => namespace, - Self::DeleteMany { namespace, .. } => namespace, + Self::InsertOne(model) => &model.namespace, + Self::UpdateOne(model) => &model.namespace, + Self::UpdateMany(model) => &model.namespace, + Self::ReplaceOne(model) => &model.namespace, + Self::DeleteOne(model) => &model.namespace, + Self::DeleteMany(model) => &model.namespace, } } pub(crate) fn operation_type(&self) -> OperationType { match self { - Self::InsertOne { .. } => OperationType::Insert, - Self::UpdateOne { .. } | Self::UpdateMany { .. } | Self::ReplaceOne { .. } => { - OperationType::Update - } - Self::DeleteOne { .. } | Self::DeleteMany { .. } => OperationType::Delete, + Self::InsertOne(_) => OperationType::Insert, + Self::UpdateOne(_) | Self::UpdateMany(_) | Self::ReplaceOne(_) => OperationType::Update, + Self::DeleteOne(_) | Self::DeleteMany(_) => OperationType::Delete, } } @@ -126,11 +267,9 @@ impl WriteModel { /// the operation does not use a filter. pub(crate) fn multi(&self) -> Option { match self { - Self::UpdateMany { .. } | Self::DeleteMany { .. } => Some(true), - Self::UpdateOne { .. } | Self::ReplaceOne { .. } | Self::DeleteOne { .. } => { - Some(false) - } - Self::InsertOne { .. } => None, + Self::UpdateMany(_) | Self::DeleteMany(_) => Some(true), + Self::UpdateOne(_) | Self::ReplaceOne(_) | Self::DeleteOne(_) => Some(false), + Self::InsertOne(_) => None, } } @@ -145,17 +284,19 @@ impl WriteModel { /// Returns the operation-specific fields that should be included in this model's entry in the /// ops array. Also returns an inserted ID if this is an insert operation. pub(crate) fn get_ops_document_contents(&self) -> Result<(RawDocumentBuf, Option)> { - if let Self::UpdateOne { update, .. } | Self::UpdateMany { update, .. } = self { + if let Self::UpdateOne(UpdateOneModel { update, .. }) + | Self::UpdateMany(UpdateManyModel { update, .. }) = self + { if let UpdateModifications::Document(update_document) = update { update_document_check(update_document)?; } - } else if let Self::ReplaceOne { replacement, .. } = self { + } else if let Self::ReplaceOne(ReplaceOneModel { replacement, .. }) = self { replacement_document_check(replacement)?; } let (mut model_document, inserted_id) = match self { - Self::InsertOne { document, .. } => { - let mut insert_document = RawDocumentBuf::from_document(document)?; + Self::InsertOne(model) => { + let mut insert_document = RawDocumentBuf::from_document(&model.document)?; let inserted_id = get_or_prepend_id_field(&mut insert_document)?; (rawdoc! { "document": insert_document }, Some(inserted_id)) } diff --git a/src/test/bulk_write.rs b/src/test/bulk_write.rs index a8ed3ea1c..0c755c517 100644 --- a/src/test/bulk_write.rs +++ b/src/test/bulk_write.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::{ bson::{doc, Document}, error::{bulk_write::PartialBulkWriteResult, BulkWriteError, ErrorKind}, - options::WriteModel, + options::{InsertOneModel, UpdateOneModel}, results::UpdateResult, test::{ get_client_options, @@ -60,10 +60,10 @@ async fn max_write_batch_size_batching() { let max_write_batch_size = client.server_info.max_write_batch_size.unwrap() as usize; - let model = WriteModel::InsertOne { - namespace: Namespace::new("db", "coll"), - document: doc! { "a": "b" }, - }; + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build(); let models = vec![model; max_write_batch_size + 1]; let result = client.bulk_write(models).await.unwrap(); @@ -101,10 +101,10 @@ async fn max_message_size_bytes_batching() { let max_message_size_bytes = client.server_info.max_message_size_bytes as usize; let document = doc! { "a": "b".repeat(max_bson_object_size - 500) }; - let model = WriteModel::InsertOne { - namespace: Namespace::new("db", "coll"), - document, - }; + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(document) + .build(); let num_models = max_message_size_bytes / max_bson_object_size + 1; let models = vec![model; num_models]; @@ -155,10 +155,10 @@ async fn write_concern_error_batches() { let _guard = client.enable_fail_point(fail_point).await.unwrap(); let models = vec![ - WriteModel::InsertOne { - namespace: Namespace::new("db", "coll"), - document: doc! { "a": "b" } - }; + InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build(); max_write_batch_size + 1 ]; let error = client.bulk_write(models).ordered(false).await.unwrap_err(); @@ -197,10 +197,10 @@ async fn write_error_batches() { collection.insert_one(document.clone()).await.unwrap(); let models = vec![ - WriteModel::InsertOne { - namespace: collection.namespace(), - document, - }; + InsertOneModel::builder() + .namespace(collection.namespace()) + .document(document) + .build(); max_write_batch_size + 1 ]; @@ -252,24 +252,18 @@ async fn successful_cursor_iteration() { collection.drop().await.unwrap(); let models = vec![ - WriteModel::UpdateOne { - namespace: collection.namespace(), - filter: doc! { "_id": "a".repeat(max_bson_object_size / 2) }, - update: doc! { "$set": { "x": 1 } }.into(), - array_filters: None, - collation: None, - hint: None, - upsert: Some(true), - }, - WriteModel::UpdateOne { - namespace: collection.namespace(), - filter: doc! { "_id": "b".repeat(max_bson_object_size / 2) }, - update: doc! { "$set": { "x": 1 } }.into(), - array_filters: None, - collation: None, - hint: None, - upsert: Some(true), - }, + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "a".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "b".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), ]; let result = client.bulk_write(models).verbose_results().await.unwrap(); @@ -302,24 +296,18 @@ async fn cursor_iteration_in_a_transaction() { session.start_transaction().await.unwrap(); let models = vec![ - WriteModel::UpdateOne { - namespace: collection.namespace(), - filter: doc! { "_id": "a".repeat(max_bson_object_size / 2) }, - update: doc! { "$set": { "x": 1 } }.into(), - array_filters: None, - collation: None, - hint: None, - upsert: Some(true), - }, - WriteModel::UpdateOne { - namespace: collection.namespace(), - filter: doc! { "_id": "b".repeat(max_bson_object_size / 2) }, - update: doc! { "$set": { "x": 1 } }.into(), - array_filters: None, - collation: None, - hint: None, - upsert: Some(true), - }, + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "a".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "b".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), ]; let result = client @@ -362,24 +350,18 @@ async fn failed_cursor_iteration() { collection.drop().await.unwrap(); let models = vec![ - WriteModel::UpdateOne { - namespace: collection.namespace(), - filter: doc! { "_id": "a".repeat(max_bson_object_size / 2) }, - update: doc! { "$set": { "x": 1 } }.into(), - array_filters: None, - collation: None, - hint: None, - upsert: Some(true), - }, - WriteModel::UpdateOne { - namespace: collection.namespace(), - filter: doc! { "_id": "b".repeat(max_bson_object_size / 2) }, - update: doc! { "$set": { "x": 1 } }.into(), - array_filters: None, - collation: None, - hint: None, - upsert: Some(true), - }, + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "a".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "b".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), ]; let error = client @@ -432,27 +414,31 @@ async fn namespace_batch_splitting() { let ops_bytes = max_message_size_bytes - 1122; let num_models = ops_bytes / max_bson_object_size; - let model = WriteModel::InsertOne { - namespace: first_namespace.clone(), - document: doc! { "a": "b".repeat(max_bson_object_size - 57) }, - }; + let model = InsertOneModel::builder() + .namespace(first_namespace.clone()) + .document(doc! { "a": "b".repeat(max_bson_object_size - 57) }) + .build(); let mut models = vec![model; num_models]; let remainder_bytes = ops_bytes % max_bson_object_size; if remainder_bytes >= 217 { - models.push(WriteModel::InsertOne { - namespace: first_namespace.clone(), - document: doc! { "a": "b".repeat(remainder_bytes - 57) }, - }); + models.push( + InsertOneModel::builder() + .namespace(first_namespace.clone()) + .document(doc! { "a": "b".repeat(remainder_bytes - 57) }) + .build(), + ); } // Case 1: no batch-splitting required let mut first_models = models.clone(); - first_models.push(WriteModel::InsertOne { - namespace: first_namespace.clone(), - document: doc! { "a": "b" }, - }); + first_models.push( + InsertOneModel::builder() + .namespace(first_namespace.clone()) + .document(doc! { "a": "b" }) + .build(), + ); let num_models = first_models.len(); let result = client.bulk_write(first_models).await.unwrap(); @@ -478,10 +464,12 @@ async fn namespace_batch_splitting() { let second_namespace = Namespace::new("db", "c".repeat(200)); let mut second_models = models.clone(); - second_models.push(WriteModel::InsertOne { - namespace: second_namespace.clone(), - document: doc! { "a": "b" }, - }); + second_models.push( + InsertOneModel::builder() + .namespace(second_namespace.clone()) + .document(doc! { "a": "b" }) + .build(), + ); let num_models = second_models.len(); let result = client.bulk_write(second_models).await.unwrap(); @@ -531,19 +519,19 @@ async fn too_large_client_error() { } // Case 1: document too large - let model = WriteModel::InsertOne { - namespace: Namespace::new("db", "coll"), - document: doc! { "a": "b".repeat(max_message_size_bytes) }, - }; + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b".repeat(max_message_size_bytes) }) + .build(); let error = client.bulk_write(vec![model]).await.unwrap_err(); assert!(!error.is_server_error()); // Case 2: namespace too large - let model = WriteModel::InsertOne { - namespace: Namespace::new("db", "c".repeat(max_message_size_bytes)), - document: doc! { "a": "b" }, - }; + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "c".repeat(max_message_size_bytes))) + .document(doc! { "a": "b" }) + .build(); let error = client.bulk_write(vec![model]).await.unwrap_err(); assert!(!error.is_server_error()); @@ -570,10 +558,10 @@ async fn encryption_error() { .build() .await; - let model = WriteModel::InsertOne { - namespace: Namespace::new("db", "coll"), - document: doc! { "a": "b" }, - }; + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build(); let error = encrypted_client.bulk_write(vec![model]).await.unwrap_err(); let ErrorKind::Encryption(encryption_error) = *error.kind else { diff --git a/src/test/spec/unified_runner/operation/bulk_write.rs b/src/test/spec/unified_runner/operation/bulk_write.rs index 3e1eb311d..83d8b5750 100644 --- a/src/test/spec/unified_runner/operation/bulk_write.rs +++ b/src/test/spec/unified_runner/operation/bulk_write.rs @@ -3,12 +3,19 @@ use futures_util::FutureExt; use serde::Deserialize; use crate::{ - bson::{to_bson, Array, Bson, Document}, - coll::options::UpdateModifications, + bson::to_bson, error::Result, - options::{BulkWriteOptions, WriteModel}, + options::{ + BulkWriteOptions, + DeleteManyModel, + DeleteOneModel, + InsertOneModel, + ReplaceOneModel, + UpdateManyModel, + UpdateOneModel, + WriteModel, + }, test::spec::unified_runner::{Entity, TestRunner}, - Namespace, }; use super::{with_mut_session, with_opt_session, TestOperation}; @@ -31,136 +38,22 @@ impl<'de> Deserialize<'de> for WriteModel { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] enum WriteModelHelper { - InsertOne { - namespace: Namespace, - document: Document, - }, - #[serde(rename_all = "camelCase")] - UpdateOne { - namespace: Namespace, - filter: Document, - update: UpdateModifications, - array_filters: Option, - collation: Option, - hint: Option, - upsert: Option, - }, - #[serde(rename_all = "camelCase")] - UpdateMany { - namespace: Namespace, - filter: Document, - update: UpdateModifications, - array_filters: Option, - collation: Option, - hint: Option, - upsert: Option, - }, - #[serde(rename_all = "camelCase")] - ReplaceOne { - namespace: Namespace, - filter: Document, - replacement: Document, - collation: Option, - hint: Option, - upsert: Option, - }, - DeleteOne { - namespace: Namespace, - filter: Document, - collation: Option, - hint: Option, - }, - DeleteMany { - namespace: Namespace, - filter: Document, - collation: Option, - hint: Option, - }, + InsertOne(InsertOneModel), + UpdateOne(UpdateOneModel), + UpdateMany(UpdateManyModel), + ReplaceOne(ReplaceOneModel), + DeleteOne(DeleteOneModel), + DeleteMany(DeleteManyModel), } - let helper = WriteModelHelper::deserialize(deserializer)?; - let model = match helper { - WriteModelHelper::InsertOne { - namespace, - document, - } => WriteModel::InsertOne { - namespace, - document, - }, - WriteModelHelper::UpdateOne { - namespace, - filter, - update, - array_filters, - collation, - hint, - upsert, - } => WriteModel::UpdateOne { - namespace, - filter, - update, - array_filters, - collation, - hint, - upsert, - }, - WriteModelHelper::UpdateMany { - namespace, - filter, - update, - array_filters, - collation, - hint, - upsert, - } => WriteModel::UpdateMany { - namespace, - filter, - update, - array_filters, - collation, - hint, - upsert, - }, - WriteModelHelper::ReplaceOne { - namespace, - filter, - replacement, - collation, - hint, - upsert, - } => WriteModel::ReplaceOne { - namespace, - filter, - replacement, - collation, - hint, - upsert, - }, - WriteModelHelper::DeleteOne { - namespace, - filter, - collation, - hint, - } => WriteModel::DeleteOne { - namespace, - filter, - collation, - hint, - }, - WriteModelHelper::DeleteMany { - namespace, - filter, - collation, - hint, - } => WriteModel::DeleteMany { - namespace, - filter, - collation, - hint, - }, - }; - - Ok(model) + match WriteModelHelper::deserialize(deserializer)? { + WriteModelHelper::InsertOne(model) => Ok(Self::InsertOne(model)), + WriteModelHelper::UpdateOne(model) => Ok(Self::UpdateOne(model)), + WriteModelHelper::UpdateMany(model) => Ok(Self::UpdateMany(model)), + WriteModelHelper::ReplaceOne(model) => Ok(Self::ReplaceOne(model)), + WriteModelHelper::DeleteOne(model) => Ok(Self::DeleteOne(model)), + WriteModelHelper::DeleteMany(model) => Ok(Self::DeleteMany(model)), + } } }