Skip to main content

pg_proto/
pipeline.rs

1//! Bounded, payload-free orchestration for proxy request pipelines.
2//!
3//! The ledger in this module records protocol obligations, not wire messages.
4//! Applications retain ownership of decoded messages until [`FrontendAction`] or
5//! [`BackendAction`] tells them to forward, emit, retry, or discard the value.
6//! Upstream transports may continue using [`Demux`]: drain its ordered async
7//! events before passing each returned [`SessionItem`] to
8//! [`Pipeline::accept_session_item`]. This retains the existing notice tagging,
9//! parameter map, notification queue, cancellation key, and transaction evidence.
10
11use std::{collections::VecDeque, convert::Infallible, sync::Arc};
12
13use tokio::sync::Notify;
14
15use crate::{
16    codec::{BackendMessage, FrontendMessage},
17    demux::{Demux, SessionItem},
18    grammar::backend,
19    middleware::{
20        AsynchronousBackendMessage, ChainError, MessageMiddleware, Middleware,
21        ReconstructableMessage as _, Then,
22    },
23};
24
25/// Stable identity of an accepted frontend operation.
26#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
27pub struct OperationId(u64);
28
29/// Pipeline policy which preserves the historical lock-step behaviour.
30#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
31pub struct NoPipeline;
32
33/// Configuration for a bounded frontend operation pipeline.
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub struct BoundedPipeline {
36    max_operations: usize,
37}
38
39impl BoundedPipeline {
40    /// Creates a pipeline with a non-zero operation-count limit.
41    ///
42    /// # Errors
43    ///
44    /// Returns an error when `max_operations` is zero.
45    pub fn new(max_operations: usize) -> Result<Self, PipelineConfigError> {
46        if max_operations == 0 {
47            return Err(PipelineConfigError);
48        }
49        Ok(Self { max_operations })
50    }
51
52    /// Returns the maximum number of incomplete operations.
53    #[must_use]
54    pub const fn max_operations(self) -> usize {
55        self.max_operations
56    }
57}
58
59/// A zero operation-count limit is not a usable pipeline configuration.
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub struct PipelineConfigError;
62
63impl std::fmt::Display for PipelineConfigError {
64    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        formatter.write_str("pipeline operation limit must be non-zero")
66    }
67}
68
69impl std::error::Error for PipelineConfigError {}
70
71mod private {
72    pub trait Sealed {}
73}
74
75/// Configuration accepted by [`Pipeline`].
76pub trait PipelinePolicy: private::Sealed + Copy {
77    /// Maximum number of incomplete operation records.
78    fn operation_limit(self) -> usize;
79}
80
81impl private::Sealed for NoPipeline {}
82impl PipelinePolicy for NoPipeline {
83    fn operation_limit(self) -> usize {
84        1
85    }
86}
87
88impl private::Sealed for BoundedPipeline {}
89impl PipelinePolicy for BoundedPipeline {
90    fn operation_limit(self) -> usize {
91        self.max_operations
92    }
93}
94
95/// Whether an accepted frontend operation is locally handled or forwarded.
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub enum FrontendHandling {
98    /// Send the returned message to the upstream connection.
99    Forward,
100    /// Do not send the request upstream; application code will synthesize its response.
101    Local,
102}
103
104/// Application action for one successfully projected frontend message.
105#[derive(Debug, Eq, PartialEq)]
106pub enum FrontendAction {
107    /// Forward the owned message upstream.
108    Forward {
109        /// Accepted operation identity.
110        id: OperationId,
111        /// Original, unretained frontend message.
112        message: FrontendMessage,
113    },
114    /// The operation is locally handled and the message can be discarded.
115    Discard {
116        /// Accepted operation identity.
117        id: OperationId,
118    },
119    /// Capacity is exhausted; pause reads and retry this unchanged message.
120    Backpressure(FrontendMessage),
121}
122
123/// Position of a successfully accepted operation.
124#[derive(Debug, Eq, PartialEq)]
125pub enum FrontendAdmission {
126    /// Nothing earlier prevents this operation's response from being emitted.
127    Immediate(FrontendAction),
128    /// The operation was accepted but an earlier response must be emitted first.
129    Waiting(FrontendAction),
130}
131
132impl FrontendAdmission {
133    /// Returns the application action, discarding only the positional annotation.
134    #[must_use]
135    pub fn into_action(self) -> FrontendAction {
136        match self {
137            Self::Immediate(action) | Self::Waiting(action) => action,
138        }
139    }
140}
141
142/// Why a frontend message could not be accepted.
143#[derive(Debug, Eq, PartialEq)]
144pub enum FrontendProjectionError {
145    /// The bounded ledger is full; the unchanged message may be retried.
146    Capacity(Box<FrontendMessage>),
147    /// The message is not legal in the projected frontend protocol state.
148    Illegal {
149        /// Projected state at rejection.
150        state: PipelineState,
151        /// Unchanged illegal message.
152        message: Box<FrontendMessage>,
153    },
154}
155
156/// Application action for a backend message.
157#[derive(Debug, Eq, PartialEq)]
158pub enum BackendAction {
159    /// Emit this owned message to the downstream client now.
160    Emit(BackendMessage),
161    /// An earlier operation must complete; retry this unchanged message later.
162    Deferred(BackendMessage),
163}
164
165/// A backend message was not legal for any outstanding operation.
166#[derive(Debug, Eq, PartialEq)]
167pub struct BackendProjectionError {
168    /// Current response-side state.
169    pub state: PipelineState,
170    /// Unchanged illegal message.
171    pub message: BackendMessage,
172}
173
174/// Public summary of the pipeline's projected frontend state.
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum PipelineState {
177    /// A simple or extended cycle may begin.
178    Ready,
179    /// Extended-query messages are being accepted.
180    Extended,
181    /// An extended error discards messages through `Sync`.
182    ExtendedError,
183    /// COPY IN accepts frontend data.
184    CopyIn,
185    /// COPY OUT accepts only backend data.
186    CopyOut,
187    /// COPY BOTH accepts data in both directions.
188    CopyBoth,
189    /// The connection has terminated.
190    Terminated,
191}
192
193/// Error returned while dispatching a pipeline message through typed middleware.
194#[derive(Debug)]
195pub enum PipelineMiddlewareError<MiddlewareError, ProjectionError> {
196    /// Middleware rejected the phase-typed message.
197    Middleware(MiddlewareError),
198    /// The pipeline rejected the original or rewritten message.
199    Projection(ProjectionError),
200}
201
202macro_rules! frontend_pipeline_phases {
203    ($consumer:ident) => {
204        $consumer! {
205            Ready => frontend_ready => backend::ReadyExternalMessage,
206            Building => frontend_building => backend::BuildingExternalMessage,
207            ExtendedError => frontend_extended_error => backend::ExtendedErrorExternalMessage,
208            SimpleCopyIn => frontend_simple_copy_in => backend::SimpleCopyInExternalMessage,
209            ExtendedCopyIn => frontend_extended_copy_in => backend::ExtendedCopyInExternalMessage,
210            SimpleCopyBoth => frontend_simple_copy_both => backend::SimpleCopyBothExternalMessage,
211            ExtendedCopyBoth => frontend_extended_copy_both => backend::ExtendedCopyBothExternalMessage,
212        }
213    };
214}
215
216macro_rules! backend_pipeline_phases {
217    ($consumer:ident) => {
218        $consumer! {
219            Asynchronous => backend_asynchronous => AsynchronousBackendMessage,
220            Simple => backend_simple => backend::SimpleInternalMessage,
221            SimpleError => backend_simple_error => backend::SimpleErrorInternalMessage,
222            ParseResponse => backend_parse_response => backend::ParseResponseInternalMessage,
223            BindResponse => backend_bind_response => backend::BindResponseInternalMessage,
224            DescribeResponse => backend_describe_response => backend::DescribeResponseInternalMessage,
225            ExecuteResponse => backend_execute_response => backend::ExecuteResponseInternalMessage,
226            CloseResponse => backend_close_response => backend::CloseResponseInternalMessage,
227            SyncResponse => backend_sync_response => backend::SyncResponseInternalMessage,
228            FunctionResponse => backend_function_response => backend::FunctionResponseInternalMessage,
229            FunctionReady => backend_function_ready => backend::FunctionReadyInternalMessage,
230            SimpleCopyInDone => backend_simple_copy_in_done => backend::SimpleCopyInDoneInternalMessage,
231            SimpleCopyInFailed => backend_simple_copy_in_failed => backend::SimpleCopyInFailedInternalMessage,
232            SimpleCopyOut => backend_simple_copy_out => backend::SimpleCopyOutInternalMessage,
233            SimpleCopyOutDone => backend_simple_copy_out_done => backend::SimpleCopyOutDoneInternalMessage,
234            SimpleCopyReady => backend_simple_copy_ready => backend::SimpleCopyReadyInternalMessage,
235            ExtendedCopyInDone => backend_extended_copy_in_done => backend::ExtendedCopyInDoneInternalMessage,
236            ExtendedCopyInFailed => backend_extended_copy_in_failed => backend::ExtendedCopyInFailedInternalMessage,
237            ExtendedCopyOut => backend_extended_copy_out => backend::ExtendedCopyOutInternalMessage,
238            ExtendedCopyOutDone => backend_extended_copy_out_done => backend::ExtendedCopyOutDoneInternalMessage,
239            SimpleCopyBoth => backend_simple_copy_both => backend::SimpleCopyBothInternalMessage,
240            SimpleCopyBothClientDone => backend_simple_copy_both_client_done => backend::SimpleCopyBothClientDoneInternalMessage,
241            SimpleCopyBothDone => backend_simple_copy_both_done => backend::SimpleCopyBothDoneInternalMessage,
242            SimpleCopyBothFailed => backend_simple_copy_both_failed => backend::SimpleCopyBothFailedInternalMessage,
243            ExtendedCopyBoth => backend_extended_copy_both => backend::ExtendedCopyBothInternalMessage,
244            ExtendedCopyBothClientDone => backend_extended_copy_both_client_done => backend::ExtendedCopyBothClientDoneInternalMessage,
245            ExtendedCopyBothDone => backend_extended_copy_both_done => backend::ExtendedCopyBothDoneInternalMessage,
246            ExtendedCopyBothFailed => backend_extended_copy_both_failed => backend::ExtendedCopyBothFailedInternalMessage,
247        }
248    };
249}
250
251macro_rules! declare_pipeline_hooks {
252    ($($phase:ident => $method:ident => $message:path),+ $(,)?) => {
253        $(
254            #[doc = concat!("Intercepts backend messages in generated `", stringify!($message), "` phase.")]
255            async fn $method(
256                &mut self,
257                _state: &mut State,
258                message: $message,
259            ) -> Result<$message, Self::Error> {
260                Ok(message)
261            }
262        )+
263    };
264}
265
266/// Async middleware for frontend messages selected from the runtime ledger phase.
267#[allow(async_fn_in_trait)]
268pub trait FrontendPipelineMiddleware<State> {
269    /// An error which prevents the message from continuing through the pipeline.
270    type Error;
271    frontend_pipeline_phases!(declare_pipeline_hooks);
272}
273
274/// Async middleware for backend messages selected from the runtime ledger phase.
275#[allow(async_fn_in_trait)]
276pub trait BackendPipelineMiddleware<State> {
277    /// An error which prevents the message from continuing through the pipeline.
278    type Error;
279    backend_pipeline_phases!(declare_pipeline_hooks);
280}
281
282impl<State> FrontendPipelineMiddleware<State> for crate::middleware::Identity {
283    type Error = Infallible;
284}
285
286impl<State> BackendPipelineMiddleware<State> for crate::middleware::Identity {
287    type Error = Infallible;
288}
289
290macro_rules! chained_pipeline_hooks {
291    ($($phase:ident => $method:ident => $message:ty),+ $(,)?) => {
292        $(
293            async fn $method(
294                &mut self,
295                state: &mut State,
296                message: $message,
297            ) -> Result<$message, Self::Error> {
298                let (first, second) = self.parts_mut();
299                let message = first
300                    .$method(state, message)
301                    .await
302                    .map_err(ChainError::First)?;
303                second
304                    .$method(state, message)
305                    .await
306                    .map_err(ChainError::Second)
307            }
308        )+
309    };
310}
311
312impl<State, First, Second> FrontendPipelineMiddleware<State> for Then<First, Second>
313where
314    First: FrontendPipelineMiddleware<State>,
315    Second: FrontendPipelineMiddleware<State>,
316{
317    type Error = ChainError<First::Error, Second::Error>;
318
319    frontend_pipeline_phases!(chained_pipeline_hooks);
320}
321
322impl<State, First, Second> BackendPipelineMiddleware<State> for Then<First, Second>
323where
324    First: BackendPipelineMiddleware<State>,
325    Second: BackendPipelineMiddleware<State>,
326{
327    type Error = ChainError<First::Error, Second::Error>;
328    backend_pipeline_phases!(chained_pipeline_hooks);
329}
330
331/// Adapts direction-wide async middleware to every typed pipeline hook.
332pub struct PipelineWireAdapter<Handler> {
333    handler: Handler,
334}
335
336impl<Handler> PipelineWireAdapter<Handler> {
337    /// Wraps direction-wide middleware for runtime phase dispatch.
338    pub const fn new(handler: Handler) -> Self {
339        Self { handler }
340    }
341
342    /// Returns the wrapped direction-wide middleware.
343    pub fn into_inner(self) -> Handler {
344        self.handler
345    }
346}
347
348/// Failure from direction-wide middleware adapted to typed pipeline dispatch.
349#[derive(Debug)]
350pub enum FrontendPipelineWireAdapterError<Error> {
351    /// The wrapped middleware rejected a message.
352    Middleware(Error),
353    /// The wrapped middleware returned a frontend message illegal in the selected phase.
354    IllegalFrontend(FrontendMessage),
355}
356
357/// Failure from backend wire middleware adapted to typed pipeline dispatch.
358#[derive(Debug)]
359pub enum BackendPipelineWireAdapterError<Error> {
360    /// The wrapped middleware rejected a message.
361    Middleware(Error),
362    /// The wrapped middleware returned a message illegal in the selected phase.
363    Illegal(BackendMessage),
364}
365
366macro_rules! pipeline_adapter_frontend_hooks {
367    ($($phase:ident => $method:ident => $message:ty),+ $(,)?) => {
368        $(
369            async fn $method(
370                &mut self,
371                state: &mut State,
372                message: $message,
373            ) -> Result<$message, Self::Error> {
374                let message: FrontendMessage = message.into();
375                let message = self
376                    .handler
377                    .intercept(state, message)
378                    .await
379                    .map_err(FrontendPipelineWireAdapterError::Middleware)?;
380                <$message>::try_from(message)
381                    .map_err(FrontendPipelineWireAdapterError::IllegalFrontend)
382            }
383        )+
384    };
385}
386
387macro_rules! pipeline_adapter_backend_hooks {
388    ($ignored:ident => $async_method:ident => AsynchronousBackendMessage, $($phase:ident => $method:ident => $message:ty),+ $(,)?) => {
389        async fn $async_method(
390            &mut self,
391            state: &mut State,
392            message: AsynchronousBackendMessage,
393        ) -> Result<AsynchronousBackendMessage, Self::Error> {
394            let message = self.handler.intercept(state, message.into_wire()).await
395                .map_err(BackendPipelineWireAdapterError::Middleware)?;
396            AsynchronousBackendMessage::try_from(message)
397                .map_err(BackendPipelineWireAdapterError::Illegal)
398        }
399        $(
400            async fn $method(
401                &mut self,
402                state: &mut State,
403                message: $message,
404            ) -> Result<$message, Self::Error> {
405                let message: BackendMessage = message.into();
406                let message = self
407                    .handler
408                    .intercept(state, message)
409                    .await
410                    .map_err(BackendPipelineWireAdapterError::Middleware)?;
411                <$message>::try_from(message).map_err(BackendPipelineWireAdapterError::Illegal)
412            }
413        )+
414    };
415}
416
417impl<State, Handler> FrontendPipelineMiddleware<State> for PipelineWireAdapter<Handler>
418where
419    Handler: MessageMiddleware<FrontendMessage, State>,
420{
421    type Error = FrontendPipelineWireAdapterError<Handler::Error>;
422    frontend_pipeline_phases!(pipeline_adapter_frontend_hooks);
423}
424
425impl<State, Handler> BackendPipelineMiddleware<State> for PipelineWireAdapter<Handler>
426where
427    Handler: MessageMiddleware<BackendMessage, State>,
428{
429    type Error = BackendPipelineWireAdapterError<Handler::Error>;
430    backend_pipeline_phases!(pipeline_adapter_backend_hooks);
431}
432
433#[derive(Clone, Copy, Debug, Eq, PartialEq)]
434enum RequestState {
435    Ready,
436    Extended { bound: bool },
437    ExtendedError,
438    CopyIn,
439    CopyOut,
440    CopyBoth,
441    Terminated,
442}
443
444impl RequestState {
445    const fn public(self) -> PipelineState {
446        match self {
447            Self::Ready => PipelineState::Ready,
448            Self::Extended { .. } => PipelineState::Extended,
449            Self::ExtendedError => PipelineState::ExtendedError,
450            Self::CopyIn => PipelineState::CopyIn,
451            Self::CopyOut => PipelineState::CopyOut,
452            Self::CopyBoth => PipelineState::CopyBoth,
453            Self::Terminated => PipelineState::Terminated,
454        }
455    }
456}
457
458#[derive(Clone, Copy, Debug, Eq, PartialEq)]
459enum Origin {
460    Forwarded,
461    Local,
462}
463
464#[derive(Clone, Copy, Debug, Eq, PartialEq)]
465enum OperationKind {
466    Query,
467    FunctionCall,
468    Parse,
469    Bind,
470    Describe,
471    Execute,
472    Close,
473    Flush,
474    Sync,
475    CopyData,
476    CopyDone,
477    CopyFail,
478    Terminate,
479}
480
481#[derive(Clone, Copy, Debug, Eq, PartialEq)]
482enum PreparedResponse {
483    Asynchronous,
484    Emit {
485        head: Operation,
486        response_state: backend::RuntimeState,
487    },
488    Deferred,
489    Illegal,
490}
491
492#[derive(Clone, Copy, Debug, Eq, PartialEq)]
493enum FrontendPhase {
494    Ready,
495    Building,
496    ExtendedError,
497    SimpleCopyIn,
498    ExtendedCopyIn,
499    SimpleCopyBoth,
500    ExtendedCopyBoth,
501}
502
503#[derive(Clone, Copy, Debug)]
504struct PreparedFrontend {
505    phase: FrontendPhase,
506    request_state: RequestState,
507}
508
509#[derive(Clone, Copy, Debug, Eq, PartialEq)]
510struct Operation {
511    id: OperationId,
512    kind: OperationKind,
513    origin: Origin,
514    discarded: bool,
515    response_state: backend::RuntimeState,
516}
517
518/// A bounded ledger coordinating independently owned frontend and backend values.
519#[derive(Debug)]
520pub struct Pipeline<P = NoPipeline> {
521    policy: P,
522    operations: VecDeque<Operation>,
523    request_state: RequestState,
524    response_state: Option<PipelineState>,
525    next_id: u64,
526    changed: Arc<Notify>,
527}
528
529impl Default for Pipeline<NoPipeline> {
530    fn default() -> Self {
531        Self::new(NoPipeline)
532    }
533}
534
535impl<P: PipelinePolicy> Pipeline<P> {
536    /// Creates an empty pipeline using `policy`.
537    #[must_use]
538    pub fn new(policy: P) -> Self {
539        Self {
540            policy,
541            operations: VecDeque::new(),
542            request_state: RequestState::Ready,
543            response_state: None,
544            next_id: 0,
545            changed: Arc::new(Notify::new()),
546        }
547    }
548
549    /// Returns the projected frontend protocol state.
550    #[must_use]
551    pub fn state(&self) -> PipelineState {
552        self.response_state
553            .unwrap_or_else(|| self.request_state.public())
554    }
555
556    /// Returns the number of incomplete lightweight operation records.
557    #[must_use]
558    pub fn len(&self) -> usize {
559        self.operations.len()
560    }
561
562    /// Reports whether no operations remain outstanding.
563    #[must_use]
564    pub fn is_empty(&self) -> bool {
565        self.operations.is_empty()
566    }
567
568    /// Projects and accepts one frontend message without retaining its payload.
569    ///
570    /// Capacity and legality failures return the original owned message. A
571    /// capacity failure does not mutate either projected state or the ledger.
572    ///
573    /// # Errors
574    ///
575    /// Returns the unchanged boxed message when capacity is exhausted or the
576    /// message is illegal in the projected state.
577    pub fn accept_frontend(
578        &mut self,
579        message: FrontendMessage,
580        handling: FrontendHandling,
581    ) -> Result<FrontendAdmission, FrontendProjectionError> {
582        let prepared = self.prepare_frontend(&message)?;
583        Ok(self.commit_frontend(prepared, message, handling))
584    }
585
586    fn prepare_frontend(
587        &self,
588        message: &FrontendMessage,
589    ) -> Result<PreparedFrontend, FrontendProjectionError> {
590        if self.operations.len() == self.policy.operation_limit() {
591            return Err(FrontendProjectionError::Capacity(Box::new(message.clone())));
592        }
593        if project_frontend(self.request_state, message).is_none() {
594            return Err(FrontendProjectionError::Illegal {
595                state: self.state(),
596                message: Box::new(message.clone()),
597            });
598        }
599        Ok(PreparedFrontend {
600            phase: frontend_phase(
601                self.request_state,
602                self.operations.front().map(|operation| operation.kind),
603            ),
604            request_state: self.request_state,
605        })
606    }
607
608    fn commit_frontend(
609        &mut self,
610        prepared: PreparedFrontend,
611        message: FrontendMessage,
612        handling: FrontendHandling,
613    ) -> FrontendAdmission {
614        let (kind, next_state) = classify_frontend(prepared.request_state, &message)
615            .expect("phase-typed frontend replacement has a ledger classification");
616        let waiting = !self.operations.is_empty();
617        let id = OperationId(self.next_id);
618        self.next_id = self.next_id.saturating_add(1);
619        self.request_state = next_state;
620        let origin = match handling {
621            FrontendHandling::Forward => Origin::Forwarded,
622            FrontendHandling::Local => Origin::Local,
623        };
624        if let Some(head) = self.operations.front_mut()
625            && let Some(event) = backend::project_external(head.response_state, &message)
626            && let Some(transition) = backend::transition(head.response_state, event)
627        {
628            head.response_state = transition.target;
629        }
630        self.operations.push_back(Operation {
631            id,
632            kind,
633            origin,
634            discarded: matches!(self.request_state, RequestState::ExtendedError)
635                && kind != OperationKind::Sync,
636            response_state: initial_response_state(kind),
637        });
638        let action = match handling {
639            FrontendHandling::Forward => FrontendAction::Forward { id, message },
640            FrontendHandling::Local => FrontendAction::Discard { id },
641        };
642        let admission = if waiting {
643            FrontendAdmission::Waiting(action)
644        } else {
645            FrontendAdmission::Immediate(action)
646        };
647        self.remove_inert_heads();
648        admission
649    }
650
651    /// Convenience projection which reports capacity as [`FrontendAction::Backpressure`].
652    ///
653    /// # Errors
654    ///
655    /// Returns the unchanged message for capacity or protocol illegality.
656    pub fn project_frontend(
657        &mut self,
658        message: FrontendMessage,
659        handling: FrontendHandling,
660    ) -> Result<FrontendAdmission, FrontendProjectionError> {
661        self.accept_frontend(message, handling)
662    }
663
664    /// Projects a frontend value into the compact application-action vocabulary.
665    ///
666    /// Use [`Self::accept_frontend`] when the caller also needs to distinguish an
667    /// immediately emittable operation from an accepted waiting operation.
668    ///
669    /// # Errors
670    ///
671    /// Returns an illegal message; capacity is represented as a successful
672    /// [`FrontendAction::Backpressure`] action.
673    pub fn frontend_action(
674        &mut self,
675        message: FrontendMessage,
676        handling: FrontendHandling,
677    ) -> Result<FrontendAction, FrontendProjectionError> {
678        match self.accept_frontend(message, handling) {
679            Ok(admission) => Ok(admission.into_action()),
680            Err(FrontendProjectionError::Capacity(message)) => {
681                Ok(FrontendAction::Backpressure(*message))
682            }
683            Err(error @ FrontendProjectionError::Illegal { .. }) => Err(error),
684        }
685    }
686
687    /// Projects, asynchronously intercepts, and accepts one frontend message.
688    ///
689    /// The ledger selects the phase-specific middleware hook at runtime. The
690    /// selected hook can only return a message legal in that same phase.
691    /// Middleware is not invoked when capacity is exhausted.
692    ///
693    /// # Errors
694    ///
695    /// Returns a middleware error, an illegal original or replacement message,
696    /// or the unchanged message when capacity is exhausted.
697    pub async fn accept_frontend_typed<State, Handler>(
698        &mut self,
699        middleware: &mut Middleware<State, Handler>,
700        message: FrontendMessage,
701        handling: FrontendHandling,
702    ) -> Result<FrontendAdmission, PipelineMiddlewareError<Handler::Error, FrontendProjectionError>>
703    where
704        Handler: FrontendPipelineMiddleware<State>,
705    {
706        let prepared = self
707            .prepare_frontend(&message)
708            .map_err(PipelineMiddlewareError::Projection)?;
709
710        let message = self
711            .intercept_frontend(prepared.phase, middleware, message)
712            .await
713            .map_err(PipelineMiddlewareError::Middleware)?;
714        if !message.is_reconstructable() {
715            return Err(PipelineMiddlewareError::Projection(
716                FrontendProjectionError::Illegal {
717                    state: self.state(),
718                    message: Box::new(message),
719                },
720            ));
721        }
722        Ok(self.commit_frontend(prepared, message, handling))
723    }
724
725    /// Typed-middleware counterpart to [`Self::project_frontend`].
726    ///
727    /// # Errors
728    ///
729    /// Returns the same errors as [`Self::accept_frontend_typed`].
730    pub async fn project_frontend_typed<State, Handler>(
731        &mut self,
732        middleware: &mut Middleware<State, Handler>,
733        message: FrontendMessage,
734        handling: FrontendHandling,
735    ) -> Result<FrontendAdmission, PipelineMiddlewareError<Handler::Error, FrontendProjectionError>>
736    where
737        Handler: FrontendPipelineMiddleware<State>,
738    {
739        self.accept_frontend_typed(middleware, message, handling)
740            .await
741    }
742
743    /// Typed-middleware counterpart to [`Self::frontend_action`].
744    ///
745    /// Capacity is returned as [`FrontendAction::Backpressure`] without running
746    /// middleware. Accepted messages are dispatched to their phase-specific hook.
747    ///
748    /// # Errors
749    ///
750    /// Returns a middleware error or an illegal original or replacement message.
751    pub async fn frontend_action_typed<State, Handler>(
752        &mut self,
753        middleware: &mut Middleware<State, Handler>,
754        message: FrontendMessage,
755        handling: FrontendHandling,
756    ) -> Result<FrontendAction, PipelineMiddlewareError<Handler::Error, FrontendProjectionError>>
757    where
758        Handler: FrontendPipelineMiddleware<State>,
759    {
760        match self
761            .accept_frontend_typed(middleware, message, handling)
762            .await
763        {
764            Ok(admission) => Ok(admission.into_action()),
765            Err(PipelineMiddlewareError::Projection(FrontendProjectionError::Capacity(
766                message,
767            ))) => Ok(FrontendAction::Backpressure(*message)),
768            Err(error) => Err(error),
769        }
770    }
771
772    /// Projects one upstream backend message and preserves response order.
773    ///
774    /// # Errors
775    ///
776    /// Returns an unchanged response which cannot belong to any outstanding operation.
777    pub fn accept_backend(
778        &mut self,
779        message: BackendMessage,
780    ) -> Result<BackendAction, BackendProjectionError> {
781        self.accept_response(None, message)
782    }
783
784    /// Intercepts an emittable backend response through its operation-typed hook.
785    ///
786    /// Responses belonging to a later operation are returned unchanged as
787    /// [`BackendAction::Deferred`] and are intercepted only when retried at the
788    /// response head. Asynchronous messages use their non-advancing hook.
789    ///
790    /// # Errors
791    ///
792    /// Returns a middleware error or an unchanged response which cannot belong
793    /// to any outstanding operation.
794    pub async fn accept_backend_typed<State, Handler>(
795        &mut self,
796        middleware: &mut Middleware<State, Handler>,
797        message: BackendMessage,
798    ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
799    where
800        Handler: BackendPipelineMiddleware<State>,
801    {
802        self.accept_response_typed(None, middleware, message).await
803    }
804
805    /// Projects one protocol-advancing item returned by the existing [`Demux`].
806    ///
807    /// Before calling this method, forward any values from
808    /// [`Demux::pop_async_event`] in queue order. Command notices remain available
809    /// through the demux's notice queue; the ledger itself stores no notice payload.
810    ///
811    /// # Errors
812    ///
813    /// Returns an unchanged reconstructed response which cannot belong to any
814    /// outstanding operation.
815    pub fn accept_session_item(
816        &mut self,
817        item: SessionItem,
818    ) -> Result<BackendAction, BackendProjectionError> {
819        let message = match item {
820            SessionItem::Message(message) => message,
821            SessionItem::ReadyForQuery { status, .. } => BackendMessage::ReadyForQuery(status),
822            SessionItem::CommandComplete { tag, .. } => BackendMessage::CommandComplete(tag),
823        };
824        self.accept_backend(message)
825    }
826
827    /// Typed-middleware counterpart to [`Self::accept_session_item`].
828    ///
829    /// # Errors
830    ///
831    /// Returns a middleware or backend projection error.
832    pub async fn accept_session_item_typed<State, Handler>(
833        &mut self,
834        middleware: &mut Middleware<State, Handler>,
835        item: SessionItem,
836    ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
837    where
838        Handler: BackendPipelineMiddleware<State>,
839    {
840        let message = match item {
841            SessionItem::Message(message) => message,
842            SessionItem::ReadyForQuery { status, .. } => BackendMessage::ReadyForQuery(status),
843            SessionItem::CommandComplete { tag, .. } => BackendMessage::CommandComplete(tag),
844        };
845        self.accept_backend_typed(middleware, message).await
846    }
847
848    /// Attempts to register and emit a locally synthesized response.
849    ///
850    /// The message is returned as [`BackendAction::Deferred`] when `id` has not
851    /// reached the head. No backend payload is retained by the ledger.
852    ///
853    /// # Errors
854    ///
855    /// Returns the unchanged message when it is illegal for the named operation.
856    pub fn try_emit_local(
857        &mut self,
858        id: OperationId,
859        message: BackendMessage,
860    ) -> Result<BackendAction, BackendProjectionError> {
861        self.accept_response(Some(id), message)
862    }
863
864    /// Typed-middleware counterpart to [`Self::try_emit_local`].
865    ///
866    /// Deferred local responses are not intercepted until their operation reaches
867    /// the response head.
868    ///
869    /// # Errors
870    ///
871    /// Returns a middleware error or an illegal response for the named operation.
872    pub async fn try_emit_local_typed<State, Handler>(
873        &mut self,
874        middleware: &mut Middleware<State, Handler>,
875        id: OperationId,
876        message: BackendMessage,
877    ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
878    where
879        Handler: BackendPipelineMiddleware<State>,
880    {
881        self.accept_response_typed(Some(id), middleware, message)
882            .await
883    }
884
885    /// Waits until a local operation reaches the response head.
886    ///
887    /// Cancellation is safe: polling this future never reserves or removes a
888    /// ledger entry. The caller should then invoke [`Self::try_emit_local`].
889    pub async fn wait_until_emittable(&self, id: OperationId) {
890        loop {
891            let notified = self.changed.notified();
892            if self
893                .operations
894                .front()
895                .is_some_and(|operation| operation.id == id)
896            {
897                return;
898            }
899            notified.await;
900        }
901    }
902
903    async fn intercept_frontend<State, Handler>(
904        &self,
905        phase: FrontendPhase,
906        middleware: &mut Middleware<State, Handler>,
907        message: FrontendMessage,
908    ) -> Result<FrontendMessage, Handler::Error>
909    where
910        Handler: FrontendPipelineMiddleware<State>,
911    {
912        let (state, handler) = middleware.parts_mut();
913        macro_rules! dispatch {
914            ($message:expr, $type:path, $handler:ident, $state:ident, $method:ident) => {{
915                let Ok(typed) = <$type>::try_from($message) else {
916                    unreachable!("frontend message was prevalidated for pipeline phase")
917                };
918                $handler.$method($state, typed).await?.into()
919            }};
920        }
921
922        macro_rules! dispatch_catalogue {
923            ($($catalogue_phase:ident => $method:ident => $message_type:path),+ $(,)?) => {
924                match phase {
925                    $(
926                        FrontendPhase::$catalogue_phase =>
927                            dispatch!(message, $message_type, handler, state, $method),
928                    )+
929                }
930            };
931        }
932
933        Ok(frontend_pipeline_phases!(dispatch_catalogue))
934    }
935
936    #[allow(clippy::too_many_lines)]
937    async fn accept_response_typed<State, Handler>(
938        &mut self,
939        local_id: Option<OperationId>,
940        middleware: &mut Middleware<State, Handler>,
941        message: BackendMessage,
942    ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
943    where
944        Handler: BackendPipelineMiddleware<State>,
945    {
946        let prepared = self.prepare_response(local_id, &message);
947        if matches!(prepared, PreparedResponse::Deferred) {
948            return Ok(BackendAction::Deferred(message));
949        }
950        if matches!(prepared, PreparedResponse::Illegal) {
951            return Err(PipelineMiddlewareError::Projection(
952                BackendProjectionError {
953                    state: self.state(),
954                    message,
955                },
956            ));
957        }
958
959        let (state, handler) = middleware.parts_mut();
960        let message = match prepared {
961            PreparedResponse::Asynchronous => {
962                let Ok(typed) = AsynchronousBackendMessage::try_from(message) else {
963                    unreachable!("asynchronous response was prevalidated")
964                };
965                handler
966                    .backend_asynchronous(state, typed)
967                    .await
968                    .map_err(PipelineMiddlewareError::Middleware)?
969                    .into_wire()
970            }
971            PreparedResponse::Emit { response_state, .. } => {
972                macro_rules! dispatch {
973                    ($message:ty, $method:ident) => {{
974                        let typed = match <$message>::try_from(message) {
975                            Ok(typed) => typed,
976                            Err(message) => {
977                                return Err(PipelineMiddlewareError::Projection(
978                                    BackendProjectionError {
979                                        state: self.state(),
980                                        message,
981                                    },
982                                ));
983                            }
984                        };
985                        handler
986                            .$method(state, typed)
987                            .await
988                            .map_err(PipelineMiddlewareError::Middleware)?
989                            .into_wire()
990                    }};
991                }
992
993                macro_rules! dispatch_catalogue {
994                    ($ignored:ident => $ignored_method:ident => AsynchronousBackendMessage,
995                     $($catalogue_phase:ident => $method:ident => $message_type:path),+ $(,)?) => {
996                        match response_state {
997                            $(
998                                backend::RuntimeState::$catalogue_phase =>
999                                    dispatch!($message_type, $method),
1000                            )+
1001                            _ => unreachable!("response phase has no backend-selected transition"),
1002                        }
1003                    };
1004                }
1005
1006                backend_pipeline_phases!(dispatch_catalogue)
1007            }
1008            PreparedResponse::Deferred | PreparedResponse::Illegal => unreachable!(),
1009        };
1010
1011        if !message.is_reconstructable() {
1012            return Err(PipelineMiddlewareError::Projection(
1013                BackendProjectionError {
1014                    state: self.state(),
1015                    message,
1016                },
1017            ));
1018        }
1019        self.commit_response(prepared, message)
1020            .map_err(PipelineMiddlewareError::Projection)
1021    }
1022
1023    fn prepare_response(
1024        &self,
1025        local_id: Option<OperationId>,
1026        message: &BackendMessage,
1027    ) -> PreparedResponse {
1028        if is_asynchronous(message) {
1029            return PreparedResponse::Asynchronous;
1030        }
1031        let Some(head) = self.operations.front().copied() else {
1032            return PreparedResponse::Illegal;
1033        };
1034        if let Some(id) = local_id {
1035            if head.id != id {
1036                return PreparedResponse::Deferred;
1037            }
1038            if head.origin != Origin::Local {
1039                return PreparedResponse::Illegal;
1040            }
1041        } else if head.origin == Origin::Local {
1042            return if self.operations.iter().skip(1).any(|operation| {
1043                operation.origin == Origin::Forwarded && response_fits(*operation, message)
1044            }) {
1045                PreparedResponse::Deferred
1046            } else {
1047                PreparedResponse::Illegal
1048            };
1049        }
1050        if head.discarded || !response_fits(head, message) {
1051            return if self
1052                .operations
1053                .iter()
1054                .skip(1)
1055                .any(|operation| response_fits(*operation, message))
1056            {
1057                PreparedResponse::Deferred
1058            } else {
1059                PreparedResponse::Illegal
1060            };
1061        }
1062        PreparedResponse::Emit {
1063            head,
1064            response_state: head.response_state,
1065        }
1066    }
1067
1068    fn accept_response(
1069        &mut self,
1070        local_id: Option<OperationId>,
1071        message: BackendMessage,
1072    ) -> Result<BackendAction, BackendProjectionError> {
1073        let prepared = self.prepare_response(local_id, &message);
1074        self.commit_response(prepared, message)
1075    }
1076
1077    fn commit_response(
1078        &mut self,
1079        prepared: PreparedResponse,
1080        message: BackendMessage,
1081    ) -> Result<BackendAction, BackendProjectionError> {
1082        let PreparedResponse::Emit {
1083            head,
1084            response_state,
1085        } = prepared
1086        else {
1087            return match prepared {
1088                PreparedResponse::Asynchronous => Ok(BackendAction::Emit(message)),
1089                PreparedResponse::Deferred => Ok(BackendAction::Deferred(message)),
1090                PreparedResponse::Illegal => Err(BackendProjectionError {
1091                    state: self.state(),
1092                    message,
1093                }),
1094                PreparedResponse::Emit { .. } => unreachable!(),
1095            };
1096        };
1097        let event = backend::project_internal(response_state, &message)
1098            .expect("response was validated against its generated backend phase");
1099        let next_response_state = backend::transition(response_state, event)
1100            .expect("projected backend event has a generated transition")
1101            .target;
1102        let terminal = response_is_terminal(head.kind, &message);
1103        let error = matches!(message, BackendMessage::ErrorResponse(_));
1104        let copy_state = response_copy_state(next_response_state);
1105        if terminal {
1106            self.operations.pop_front();
1107            if error && is_extended_kind(head.kind) {
1108                self.enter_extended_error();
1109            }
1110        } else if let Some(head) = self.operations.front_mut() {
1111            head.response_state = next_response_state;
1112        }
1113        if !terminal {
1114            self.response_state = copy_state.map(RequestState::public);
1115        }
1116        if let Some(state) = copy_state
1117            && matches!(state, RequestState::CopyIn | RequestState::CopyBoth)
1118        {
1119            self.request_state = state;
1120        }
1121        if terminal {
1122            self.response_state = None;
1123            match head.kind {
1124                OperationKind::Sync | OperationKind::Query => {
1125                    self.request_state = RequestState::Ready;
1126                }
1127                OperationKind::Execute if !error => {
1128                    self.request_state = RequestState::Extended { bound: true };
1129                }
1130                _ => {}
1131            }
1132        }
1133        self.remove_inert_heads();
1134        self.changed.notify_waiters();
1135        Ok(BackendAction::Emit(message))
1136    }
1137
1138    fn enter_extended_error(&mut self) {
1139        self.request_state = RequestState::ExtendedError;
1140        self.response_state = None;
1141        for operation in &mut self.operations {
1142            if operation.kind == OperationKind::Sync {
1143                break;
1144            }
1145            operation.discarded = true;
1146        }
1147    }
1148
1149    fn remove_inert_heads(&mut self) {
1150        let previous_len = self.operations.len();
1151        while self.operations.front().is_some_and(|operation| {
1152            operation.kind == OperationKind::Flush
1153                || operation.kind == OperationKind::CopyData
1154                || operation.kind == OperationKind::CopyDone
1155                || operation.kind == OperationKind::CopyFail
1156                || operation.kind == OperationKind::Terminate
1157                || operation.discarded
1158        }) {
1159            self.operations.pop_front();
1160        }
1161        if self.operations.len() != previous_len {
1162            self.changed.notify_waiters();
1163        }
1164    }
1165}
1166
1167fn project_frontend(
1168    state: RequestState,
1169    message: &FrontendMessage,
1170) -> Option<(OperationKind, RequestState)> {
1171    use RequestState as S;
1172    let generated_state = match state {
1173        S::Ready => backend::RuntimeState::Ready,
1174        S::Extended { .. } => backend::RuntimeState::Building,
1175        S::ExtendedError => backend::RuntimeState::ExtendedError,
1176        S::CopyIn => backend::RuntimeState::ExtendedCopyIn,
1177        S::CopyOut => backend::RuntimeState::ExtendedCopyOut,
1178        S::CopyBoth => backend::RuntimeState::ExtendedCopyBoth,
1179        S::Terminated => backend::RuntimeState::Terminated,
1180    };
1181    backend::project_external(generated_state, message)?;
1182    classify_frontend(state, message)
1183}
1184
1185fn classify_frontend(
1186    state: RequestState,
1187    message: &FrontendMessage,
1188) -> Option<(OperationKind, RequestState)> {
1189    use FrontendMessage as F;
1190    use OperationKind as O;
1191    use RequestState as S;
1192
1193    match (state, message) {
1194        (S::Ready, F::Query(_)) => Some((O::Query, S::Ready)),
1195        (S::Ready, F::FunctionCall(_)) => Some((O::FunctionCall, S::Ready)),
1196        (S::Ready, F::Parse(_)) => Some((O::Parse, S::Extended { bound: false })),
1197        (S::Ready | S::Extended { .. }, F::Bind(_)) => Some((O::Bind, S::Extended { bound: true })),
1198        (S::Ready, F::Describe(_)) => Some((O::Describe, S::Extended { bound: false })),
1199        (S::Ready | S::Extended { .. }, F::Execute(_)) => {
1200            Some((O::Execute, S::Extended { bound: true }))
1201        }
1202        (S::Ready, F::Close(_)) => Some((O::Close, S::Extended { bound: false })),
1203        (S::Ready, F::Terminate) => Some((O::Terminate, S::Terminated)),
1204        (S::Extended { bound }, F::Parse(_)) => Some((O::Parse, S::Extended { bound })),
1205        (S::Extended { bound }, F::Describe(_)) => Some((O::Describe, S::Extended { bound })),
1206        (S::Extended { bound }, F::Close(_)) => Some((O::Close, S::Extended { bound })),
1207        (S::Extended { bound }, F::Flush) => Some((O::Flush, S::Extended { bound })),
1208        (S::Extended { .. } | S::ExtendedError, F::Sync) => Some((O::Sync, S::Ready)),
1209        (S::ExtendedError, _) => Some((classify_discard(message)?, S::ExtendedError)),
1210        (S::CopyIn, F::CopyData(_)) => Some((O::CopyData, S::CopyIn)),
1211        (S::CopyIn, F::CopyDone) => Some((O::CopyDone, S::Extended { bound: true })),
1212        (S::CopyIn, F::CopyFail(_)) => Some((O::CopyFail, S::ExtendedError)),
1213        (S::CopyBoth, F::CopyData(_)) => Some((O::CopyData, S::CopyBoth)),
1214        (S::CopyBoth, F::CopyDone) => Some((O::CopyDone, S::CopyBoth)),
1215        _ => None,
1216    }
1217}
1218
1219fn frontend_phase(state: RequestState, response_head: Option<OperationKind>) -> FrontendPhase {
1220    match (state, response_head) {
1221        (RequestState::Ready, _) => FrontendPhase::Ready,
1222        (RequestState::Extended { .. }, _) => FrontendPhase::Building,
1223        (RequestState::ExtendedError, _) => FrontendPhase::ExtendedError,
1224        (RequestState::CopyIn, Some(OperationKind::Query)) => FrontendPhase::SimpleCopyIn,
1225        (RequestState::CopyIn, Some(OperationKind::Execute)) => FrontendPhase::ExtendedCopyIn,
1226        (RequestState::CopyBoth, Some(OperationKind::Query)) => FrontendPhase::SimpleCopyBoth,
1227        (RequestState::CopyBoth, Some(OperationKind::Execute)) => FrontendPhase::ExtendedCopyBoth,
1228        (RequestState::CopyIn | RequestState::CopyBoth, _) => {
1229            unreachable!("COPY phase must belong to Query or Execute")
1230        }
1231        (RequestState::CopyOut | RequestState::Terminated, _) => {
1232            unreachable!("non-accepting frontend phase cannot be prepared")
1233        }
1234    }
1235}
1236
1237fn classify_discard(message: &FrontendMessage) -> Option<OperationKind> {
1238    Some(match message {
1239        FrontendMessage::Parse(_) => OperationKind::Parse,
1240        FrontendMessage::Bind(_) => OperationKind::Bind,
1241        FrontendMessage::Describe(_) => OperationKind::Describe,
1242        FrontendMessage::Execute(_) => OperationKind::Execute,
1243        FrontendMessage::Close(_) => OperationKind::Close,
1244        FrontendMessage::Flush => OperationKind::Flush,
1245        FrontendMessage::Query(_) => OperationKind::Query,
1246        FrontendMessage::FunctionCall(_) => OperationKind::FunctionCall,
1247        FrontendMessage::CopyData(_) => OperationKind::CopyData,
1248        FrontendMessage::CopyDone => OperationKind::CopyDone,
1249        FrontendMessage::CopyFail(_) => OperationKind::CopyFail,
1250        FrontendMessage::Terminate => OperationKind::Terminate,
1251        FrontendMessage::PasswordResponse(_) => return None,
1252        FrontendMessage::Sync => unreachable!("Sync is classified before discard"),
1253    })
1254}
1255
1256const fn initial_response_state(kind: OperationKind) -> backend::RuntimeState {
1257    use OperationKind as O;
1258    match kind {
1259        O::Query => backend::RuntimeState::Simple,
1260        O::FunctionCall => backend::RuntimeState::FunctionResponse,
1261        O::Parse => backend::RuntimeState::ParseResponse,
1262        O::Bind => backend::RuntimeState::BindResponse,
1263        O::Describe => backend::RuntimeState::DescribeResponse,
1264        O::Execute => backend::RuntimeState::ExecuteResponse,
1265        O::Close => backend::RuntimeState::CloseResponse,
1266        O::Sync => backend::RuntimeState::SyncResponse,
1267        O::Flush | O::CopyData | O::CopyDone | O::CopyFail | O::Terminate => {
1268            backend::RuntimeState::Terminated
1269        }
1270    }
1271}
1272
1273fn response_fits(operation: Operation, message: &BackendMessage) -> bool {
1274    backend::project_internal(operation.response_state, message).is_some()
1275}
1276
1277fn response_is_terminal(kind: OperationKind, message: &BackendMessage) -> bool {
1278    use BackendMessage as B;
1279    use OperationKind as O;
1280    match kind {
1281        O::Query | O::FunctionCall | O::Sync => matches!(message, B::ReadyForQuery(_)),
1282        O::Parse => matches!(message, B::ParseComplete | B::ErrorResponse(_)),
1283        O::Bind => matches!(message, B::BindComplete | B::ErrorResponse(_)),
1284        O::Describe => matches!(
1285            message,
1286            B::RowDescription(_) | B::NoData | B::ErrorResponse(_)
1287        ),
1288        O::Execute => matches!(
1289            message,
1290            B::CommandComplete(_) | B::PortalSuspended | B::ErrorResponse(_)
1291        ),
1292        O::Close => matches!(message, B::CloseComplete | B::ErrorResponse(_)),
1293        O::CopyDone => matches!(
1294            message,
1295            B::CopyDone | B::CommandComplete(_) | B::ErrorResponse(_)
1296        ),
1297        O::CopyFail => matches!(message, B::ErrorResponse(_)),
1298        O::Flush | O::CopyData | O::Terminate => true,
1299    }
1300}
1301
1302fn is_extended_kind(kind: OperationKind) -> bool {
1303    !matches!(
1304        kind,
1305        OperationKind::Query | OperationKind::FunctionCall | OperationKind::Terminate
1306    )
1307}
1308
1309fn response_copy_state(state: backend::RuntimeState) -> Option<RequestState> {
1310    use backend::RuntimeState as S;
1311    match state {
1312        S::SimpleCopyIn | S::ExtendedCopyIn => Some(RequestState::CopyIn),
1313        S::SimpleCopyOut | S::ExtendedCopyOut => Some(RequestState::CopyOut),
1314        S::SimpleCopyBoth
1315        | S::SimpleCopyBothClientDone
1316        | S::SimpleCopyBothServerDone
1317        | S::ExtendedCopyBoth
1318        | S::ExtendedCopyBothClientDone
1319        | S::ExtendedCopyBothServerDone => Some(RequestState::CopyBoth),
1320        _ => None,
1321    }
1322}
1323
1324fn is_asynchronous(message: &BackendMessage) -> bool {
1325    Demux::is_asynchronous(message)
1326}