Skip to content

Commit 8b6d3e4

Browse files
authored
RUST-2412 Provide an operation wrapper abstraction (#1738)
1 parent 9ae63f9 commit 8b6d3e4

27 files changed

Lines changed: 404 additions & 330 deletions

driver/src/operation.rs

Lines changed: 158 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,8 @@ impl Retryability {
126126
}
127127
}
128128

129-
/// A trait modeling the behavior of a server side operation.
130-
///
131-
/// No methods in this trait should have default behaviors to ensure that wrapper operations
132-
/// replicate all behavior. Default behavior is provided by the `OperationDefault` trait.
129+
/// A trait modeling the behavior of a server side operation. This should not be implemented
130+
/// directly; use either the `BaseOperation` or `WrappedOperation` traits.
133131
pub(crate) trait Operation {
134132
/// The output type of this operation.
135133
type O;
@@ -291,9 +289,9 @@ impl<T: Send + Sync> From<&Collection<T>> for OperationTarget {
291289
}
292290
}
293291

294-
// A mirror of the `Operation` trait, with default behavior where appropriate. Should only be
292+
// A mirror of the `Operation` trait, with default behavior where appropriate. Should be
295293
// implemented by operation types that do not delegate to other operations.
296-
pub(crate) trait OperationWithDefaults: Send + Sync {
294+
pub(crate) trait BaseOperation: Send + Sync {
297295
/// The output type of this operation.
298296
type O;
299297

@@ -411,66 +409,193 @@ pub(crate) trait OperationWithDefaults: Send + Sync {
411409
type Otel: crate::otel::OtelWitness<Op = Self>;
412410
}
413411

414-
impl<T: OperationWithDefaults> Operation for T
412+
/// We'd like both `BaseOperation` and `WrappedOperation` to automatically implement `Operation`.
413+
/// However, Rust only allows one blanket impl per trait, so we can't do both
414+
/// `impl<T: BaseOperation> Operation for T` and `impl<T: WrappedOperation> Operation for T`.
415+
///
416+
/// To work around this, `OperationDispatch` provides an indirection: the one blanket impl for
417+
/// `Operation` is a generic `OperationDispatch<K>`, and each specialized version of the trait
418+
/// has a blanket impl for `OperationDispatch` with a specific type parameter.
419+
///
420+
/// `OperationImpl` is a helper trait for this setup that binds specific impls to the appropriate
421+
/// dispatch type.
422+
pub(crate) trait OperationDispatch<Kind> {
423+
type O;
424+
const NAME: &'static CStr;
425+
const ZERO_COPY: bool;
426+
fn build(&mut self, description: &StreamDescription) -> Result<Command>;
427+
fn extract_at_cluster_time(&self, response: &RawDocument) -> Result<Option<Timestamp>>;
428+
fn handle_response<'a>(
429+
&'a self,
430+
response: Cow<'a, RawCommandResponse>,
431+
context: ExecutionContext<'a>,
432+
) -> BoxFuture<'a, Result<Self::O>>;
433+
fn handle_error(&self, error: Error) -> Result<Self::O>;
434+
fn selection_criteria(&self) -> Feature<&SelectionCriteria>;
435+
fn read_concern(&self) -> Feature<&ReadConcern>;
436+
fn write_concern(&self) -> Feature<&WriteConcern>;
437+
fn supports_sessions(&self) -> bool;
438+
fn retryability(&self, options: &ClientOptions) -> Retryability;
439+
fn is_backpressure_retryable(&self, options: &ClientOptions) -> bool;
440+
fn update_for_retry(&mut self, retry: Option<&Retry>);
441+
fn override_criteria(&self) -> OverrideCriteriaFn;
442+
fn pinned_connection(&self) -> Option<&PinnedConnectionHandle>;
443+
fn name(&self) -> &CStr;
444+
fn target(&self) -> OperationTarget;
445+
#[cfg(feature = "opentelemetry")]
446+
type Otel: crate::otel::OtelWitness<Op = Self>;
447+
}
448+
449+
macro_rules! operation_dispatch {
450+
($trait:path, $tag:ty, $handle_response:ident) => {
451+
impl<T: $trait> OperationDispatch<$tag> for T {
452+
operation_dispatch_body! { $trait, $handle_response }
453+
}
454+
};
455+
}
456+
457+
macro_rules! operation_dispatch_body {
458+
($trait:path, $handle_response:ident) => {
459+
type O = <T as $trait>::O;
460+
const NAME: &'static CStr = <T as $trait>::NAME;
461+
const ZERO_COPY: bool = <T as $trait>::ZERO_COPY;
462+
fn build(&mut self, description: &StreamDescription) -> Result<Command> {
463+
<T as $trait>::build(self, description)
464+
}
465+
fn extract_at_cluster_time(&self, response: &RawDocument) -> Result<Option<Timestamp>> {
466+
<T as $trait>::extract_at_cluster_time(self, response)
467+
}
468+
fn handle_response<'a>(
469+
&'a self,
470+
response: Cow<'a, RawCommandResponse>,
471+
context: ExecutionContext<'a>,
472+
) -> BoxFuture<'a, Result<Self::O>> {
473+
<T as $trait>::$handle_response(self, response, context)
474+
}
475+
fn handle_error(&self, error: Error) -> Result<Self::O> {
476+
<T as $trait>::handle_error(self, error)
477+
}
478+
fn selection_criteria(&self) -> Feature<&SelectionCriteria> {
479+
<T as $trait>::selection_criteria(self)
480+
}
481+
fn read_concern(&self) -> Feature<&ReadConcern> {
482+
<T as $trait>::read_concern(self)
483+
}
484+
fn write_concern(&self) -> Feature<&WriteConcern> {
485+
<T as $trait>::write_concern(self)
486+
}
487+
fn supports_sessions(&self) -> bool {
488+
<T as $trait>::supports_sessions(self)
489+
}
490+
fn retryability(&self, options: &ClientOptions) -> Retryability {
491+
<T as $trait>::retryability(self, options)
492+
}
493+
fn is_backpressure_retryable(&self, options: &ClientOptions) -> bool {
494+
<T as $trait>::is_backpressure_retryable(self, options)
495+
}
496+
fn update_for_retry(&mut self, retry: Option<&Retry>) {
497+
<T as $trait>::update_for_retry(self, retry)
498+
}
499+
fn override_criteria(&self) -> OverrideCriteriaFn {
500+
<T as $trait>::override_criteria(self)
501+
}
502+
fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> {
503+
<T as $trait>::pinned_connection(self)
504+
}
505+
fn name(&self) -> &CStr {
506+
<T as $trait>::name(self)
507+
}
508+
fn target(&self) -> OperationTarget {
509+
<T as $trait>::target(self)
510+
}
511+
#[cfg(feature = "opentelemetry")]
512+
type Otel = <T as $trait>::Otel;
513+
};
514+
}
515+
516+
pub(crate) trait OperationImpl {
517+
type Kind;
518+
}
519+
520+
impl<K, T> Operation for T
415521
where
416-
T: Send + Sync,
522+
T: OperationImpl<Kind = K> + OperationDispatch<K> + Send + Sync,
417523
{
418-
type O = T::O;
419-
const NAME: &'static CStr = T::NAME;
420-
const ZERO_COPY: bool = T::ZERO_COPY;
421-
fn build(&mut self, description: &StreamDescription) -> Result<Command> {
422-
self.build(description)
423-
}
424-
fn extract_at_cluster_time(&self, response: &RawDocument) -> Result<Option<Timestamp>> {
425-
self.extract_at_cluster_time(response)
426-
}
524+
operation_dispatch_body! { OperationDispatch<K>, handle_response }
525+
}
526+
527+
pub(crate) struct Base;
528+
529+
operation_dispatch! { BaseOperation, Base, handle_response_async }
530+
531+
/// A trait for operations that delegate most behavior to another wrapped operation.
532+
pub(crate) trait WrappedOperation {
533+
// Required
534+
type Wrapped: Operation;
535+
type O;
536+
#[cfg(feature = "opentelemetry")]
537+
type Otel: crate::otel::OtelWitness<Op = Self>;
538+
539+
fn wrapped(&self) -> &Self::Wrapped;
540+
fn wrapped_mut(&mut self) -> &mut Self::Wrapped;
541+
427542
fn handle_response<'a>(
428543
&'a self,
429544
response: Cow<'a, RawCommandResponse>,
430545
context: ExecutionContext<'a>,
431-
) -> BoxFuture<'a, Result<Self::O>> {
432-
self.handle_response_async(response, context)
546+
) -> BoxFuture<'a, Result<Self::O>>;
547+
548+
// Delegated to wrapped
549+
const NAME: &'static CStr = Self::Wrapped::NAME;
550+
const ZERO_COPY: bool = Self::Wrapped::ZERO_COPY;
551+
fn build(&mut self, description: &StreamDescription) -> Result<Command> {
552+
self.wrapped_mut().build(description)
433553
}
434554
fn handle_error(&self, error: Error) -> Result<Self::O> {
435-
self.handle_error(error)
555+
Err(error)
556+
}
557+
fn extract_at_cluster_time(&self, response: &RawDocument) -> Result<Option<Timestamp>> {
558+
self.wrapped().extract_at_cluster_time(response)
436559
}
437560
fn selection_criteria(&self) -> Feature<&SelectionCriteria> {
438-
self.selection_criteria()
561+
self.wrapped().selection_criteria()
439562
}
440563
fn read_concern(&self) -> Feature<&ReadConcern> {
441-
self.read_concern()
564+
self.wrapped().read_concern()
442565
}
443566
fn write_concern(&self) -> Feature<&WriteConcern> {
444-
self.write_concern()
567+
self.wrapped().write_concern()
445568
}
446569
fn supports_sessions(&self) -> bool {
447-
self.supports_sessions()
570+
self.wrapped().supports_sessions()
448571
}
449572
fn retryability(&self, options: &ClientOptions) -> Retryability {
450-
self.retryability(options)
573+
self.wrapped().retryability(options)
451574
}
452575
fn is_backpressure_retryable(&self, options: &ClientOptions) -> bool {
453-
self.is_backpressure_retryable(options)
576+
self.wrapped().is_backpressure_retryable(options)
454577
}
455578
fn update_for_retry(&mut self, retry: Option<&Retry>) {
456-
self.update_for_retry(retry)
579+
self.wrapped_mut().update_for_retry(retry);
457580
}
458581
fn override_criteria(&self) -> OverrideCriteriaFn {
459-
self.override_criteria()
582+
self.wrapped().override_criteria()
460583
}
461584
fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> {
462-
self.pinned_connection()
585+
self.wrapped().pinned_connection()
463586
}
464587
fn name(&self) -> &CStr {
465-
self.name()
588+
self.wrapped().name()
466589
}
467590
fn target(&self) -> OperationTarget {
468-
self.target()
591+
self.wrapped().target()
469592
}
470-
#[cfg(feature = "opentelemetry")]
471-
type Otel = <Self as OperationWithDefaults>::Otel;
472593
}
473594

595+
pub(crate) struct Wrapped;
596+
597+
operation_dispatch! { WrappedOperation, Wrapped, handle_response }
598+
474599
fn should_redact_body(body: &RawDocumentBuf) -> bool {
475600
if let Some(Ok((command_name, _))) = body.into_iter().next() {
476601
HELLO_COMMAND_NAMES.contains(command_name.to_lowercase().as_str())

driver/src/operation/abort_transaction.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ use crate::{
44
client::{session::TransactionPin, Retry},
55
cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription},
66
error::Result,
7-
operation::Retryability,
7+
operation::{Base, OperationImpl, Retryability},
88
options::{ClientOptions, SelectionCriteria, WriteConcern},
99
Client,
1010
};
1111

12-
use super::{ExecutionContext, OperationWithDefaults, WriteConcernOnlyBody};
12+
use super::{BaseOperation, ExecutionContext, WriteConcernOnlyBody};
1313

1414
pub(crate) struct AbortTransaction {
1515
write_concern: Option<WriteConcern>,
@@ -31,7 +31,7 @@ impl AbortTransaction {
3131
}
3232
}
3333

34-
impl OperationWithDefaults for AbortTransaction {
34+
impl BaseOperation for AbortTransaction {
3535
type O = ();
3636

3737
const NAME: &'static CStr = cstr!("abortTransaction");
@@ -89,5 +89,9 @@ impl OperationWithDefaults for AbortTransaction {
8989
type Otel = crate::otel::Witness<Self>;
9090
}
9191

92+
impl OperationImpl for AbortTransaction {
93+
type Kind = Base;
94+
}
95+
9296
#[cfg(feature = "opentelemetry")]
9397
impl crate::otel::OtelInfoDefaults for AbortTransaction {}

driver/src/operation/aggregate.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@ use crate::{
77
cmap::{Command, RawCommandResponse, StreamDescription},
88
cursor::common::CursorSpecification,
99
error::Result,
10-
operation::{append_options, OperationTarget, Retryability},
10+
operation::{append_options, Base, OperationImpl, OperationTarget, Retryability},
1111
options::{AggregateOptions, ClientOptions, ReadPreference, SelectionCriteria, WriteConcern},
1212
};
1313

14-
use super::{
15-
ExecutionContext,
16-
OperationWithDefaults,
17-
WriteConcernOnlyBody,
18-
SERVER_4_4_0_WIRE_VERSION,
19-
};
14+
use super::{BaseOperation, ExecutionContext, WriteConcernOnlyBody, SERVER_4_4_0_WIRE_VERSION};
2015

2116
#[derive(Debug)]
2217
pub(crate) struct Aggregate {
@@ -51,7 +46,7 @@ impl Aggregate {
5146

5247
// IMPORTANT: If new method implementations are added here, make sure `ChangeStreamAggregate` has
5348
// the equivalent delegations.
54-
impl OperationWithDefaults for Aggregate {
49+
impl BaseOperation for Aggregate {
5550
type O = CursorSpecification;
5651

5752
const NAME: &'static CStr = cstr!("aggregate");
@@ -177,6 +172,10 @@ impl OperationWithDefaults for Aggregate {
177172
type Otel = crate::otel::Witness<Self>;
178173
}
179174

175+
impl OperationImpl for Aggregate {
176+
type Kind = Base;
177+
}
178+
180179
#[cfg(feature = "opentelemetry")]
181180
impl crate::otel::OtelInfoDefaults for Aggregate {
182181
fn output_cursor_id(output: &Self::O) -> Option<i64> {

0 commit comments

Comments
 (0)