1use std::{collections::VecDeque, convert::Infallible, sync::Arc};
14
15use tokio::sync::Notify;
16
17use crate::{
18 codec::{BackendMessage, FrontendMessage},
19 demux::Demux,
20 grammar::backend,
21 middleware::{
22 AsynchronousBackendMessage, ChainError, MessageMiddleware, Middleware,
23 ReconstructableMessage as _, Then,
24 },
25};
26
27#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub(crate) struct OperationId(u64);
30
31#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
33pub struct NoPipeline;
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct BoundedPipeline {
38 max_operations: usize,
39}
40
41impl BoundedPipeline {
42 pub fn new(max_operations: usize) -> Result<Self, PipelineConfigError> {
48 if max_operations == 0 {
49 return Err(PipelineConfigError);
50 }
51 Ok(Self { max_operations })
52 }
53
54 #[must_use]
56 pub const fn max_operations(self) -> usize {
57 self.max_operations
58 }
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct PipelineConfigError;
64
65impl std::fmt::Display for PipelineConfigError {
66 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 formatter.write_str("pipeline operation limit must be non-zero")
68 }
69}
70
71impl std::error::Error for PipelineConfigError {}
72
73mod private {
74 pub trait Sealed {}
75}
76
77pub trait PipelinePolicy: private::Sealed + Copy {
79 fn operation_limit(self) -> usize;
81}
82
83impl private::Sealed for NoPipeline {}
84impl PipelinePolicy for NoPipeline {
85 fn operation_limit(self) -> usize {
86 1
87 }
88}
89
90impl private::Sealed for BoundedPipeline {}
91impl PipelinePolicy for BoundedPipeline {
92 fn operation_limit(self) -> usize {
93 self.max_operations
94 }
95}
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub(crate) enum FrontendHandling {
100 Forward,
102 Local,
104}
105
106#[derive(Debug, Eq, PartialEq)]
108pub(crate) enum FrontendAction {
109 Forward {
111 id: OperationId,
113 message: FrontendMessage,
115 },
116 Discard {
118 id: OperationId,
120 },
121}
122
123#[derive(Debug, Eq, PartialEq)]
125pub(crate) enum FrontendAdmission {
126 Immediate(FrontendAction),
128 Waiting(FrontendAction),
130}
131
132impl FrontendAdmission {
133 #[must_use]
135 pub(crate) fn into_action(self) -> FrontendAction {
136 match self {
137 Self::Immediate(action) | Self::Waiting(action) => action,
138 }
139 }
140}
141
142#[derive(Debug, Eq, PartialEq)]
144pub enum FrontendProjectionError {
145 Capacity(Box<FrontendMessage>),
147 Illegal {
149 state: PipelineState,
151 message: Box<FrontendMessage>,
153 },
154}
155
156#[derive(Debug, Eq, PartialEq)]
158pub(crate) enum BackendAction {
159 Emit(BackendMessage),
161 Deferred(BackendMessage),
163}
164
165#[derive(Debug, Eq, PartialEq)]
167pub struct BackendProjectionError {
168 pub state: PipelineState,
170 pub message: BackendMessage,
172}
173
174#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum PipelineState {
177 Ready,
179 Extended,
181 ExtendedError,
183 CopyIn,
185 CopyOut,
187 CopyBoth,
189 Terminated,
191}
192
193#[derive(Debug)]
195pub(crate) enum PipelineMiddlewareError<MiddlewareError, ProjectionError> {
196 Middleware(MiddlewareError),
198 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#[allow(async_fn_in_trait)]
268pub(crate) trait FrontendPipelineMiddleware<State> {
269 type Error;
271 frontend_pipeline_phases!(declare_pipeline_hooks);
272}
273
274#[allow(async_fn_in_trait)]
276pub(crate) trait BackendPipelineMiddleware<State> {
277 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
331pub(crate) struct PipelineWireAdapter<Handler> {
333 handler: Handler,
334}
335
336impl<Handler> PipelineWireAdapter<Handler> {
337 pub(crate) const fn new(handler: Handler) -> Self {
339 Self { handler }
340 }
341
342 pub(crate) fn into_inner(self) -> Handler {
344 self.handler
345 }
346}
347
348#[derive(Debug)]
350pub(crate) enum FrontendPipelineWireAdapterError<Error> {
351 Middleware(Error),
353 IllegalFrontend(FrontendMessage),
355}
356
357#[derive(Debug)]
359pub(crate) enum BackendPipelineWireAdapterError<Error> {
360 Middleware(Error),
362 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#[derive(Debug)]
520pub(crate) 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 #[must_use]
538 pub(crate) 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 #[must_use]
551 pub(crate) fn state(&self) -> PipelineState {
552 self.response_state
553 .unwrap_or_else(|| self.request_state.public())
554 }
555
556 #[must_use]
558 pub(crate) fn len(&self) -> usize {
559 self.operations.len()
560 }
561
562 #[must_use]
564 pub(crate) fn is_empty(&self) -> bool {
565 self.operations.is_empty()
566 }
567
568 pub(crate) 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 && !matches!(
592 self.request_state,
593 RequestState::CopyIn | RequestState::CopyBoth
594 )
595 {
596 return Err(FrontendProjectionError::Capacity(Box::new(message.clone())));
597 }
598 if project_frontend(self.request_state, message).is_none() {
599 return Err(FrontendProjectionError::Illegal {
600 state: self.state(),
601 message: Box::new(message.clone()),
602 });
603 }
604 Ok(PreparedFrontend {
605 phase: frontend_phase(
606 self.request_state,
607 self.operations.front().map(|operation| operation.kind),
608 ),
609 request_state: self.request_state,
610 })
611 }
612
613 fn commit_frontend(
614 &mut self,
615 prepared: PreparedFrontend,
616 message: FrontendMessage,
617 handling: FrontendHandling,
618 ) -> FrontendAdmission {
619 let (kind, next_state) = classify_frontend(prepared.request_state, &message)
620 .expect("phase-typed frontend replacement has a ledger classification");
621 let waiting = !self.operations.is_empty();
622 let id = OperationId(self.next_id);
623 self.next_id = self.next_id.saturating_add(1);
624 self.request_state = next_state;
625 let origin = match handling {
626 FrontendHandling::Forward => Origin::Forwarded,
627 FrontendHandling::Local => Origin::Local,
628 };
629 if let Some(head) = self.operations.front_mut()
630 && let Some(event) = backend::project_external(head.response_state, &message)
631 && let Some(transition) = backend::transition(head.response_state, event)
632 {
633 head.response_state = transition.target;
634 }
635 self.operations.push_back(Operation {
636 id,
637 kind,
638 origin,
639 discarded: matches!(self.request_state, RequestState::ExtendedError)
640 && kind != OperationKind::Sync,
641 response_state: initial_response_state(kind),
642 });
643 let action = match handling {
644 FrontendHandling::Forward => FrontendAction::Forward { id, message },
645 FrontendHandling::Local => FrontendAction::Discard { id },
646 };
647 let admission = if waiting {
648 FrontendAdmission::Waiting(action)
649 } else {
650 FrontendAdmission::Immediate(action)
651 };
652 self.remove_inert_heads();
653 admission
654 }
655
656 pub(crate) async fn accept_frontend_typed<State, Handler>(
667 &mut self,
668 middleware: &mut Middleware<State, Handler>,
669 message: FrontendMessage,
670 handling: FrontendHandling,
671 ) -> Result<FrontendAdmission, PipelineMiddlewareError<Handler::Error, FrontendProjectionError>>
672 where
673 Handler: FrontendPipelineMiddleware<State>,
674 {
675 let prepared = self
676 .prepare_frontend(&message)
677 .map_err(PipelineMiddlewareError::Projection)?;
678
679 let message = self
680 .intercept_frontend(prepared.phase, middleware, message)
681 .await
682 .map_err(PipelineMiddlewareError::Middleware)?;
683 if !message.is_reconstructable() {
684 return Err(PipelineMiddlewareError::Projection(
685 FrontendProjectionError::Illegal {
686 state: self.state(),
687 message: Box::new(message),
688 },
689 ));
690 }
691 Ok(self.commit_frontend(prepared, message, handling))
692 }
693
694 pub(crate) fn accept_backend(
700 &mut self,
701 message: BackendMessage,
702 ) -> Result<BackendAction, BackendProjectionError> {
703 self.accept_response(None, message)
704 }
705
706 pub(crate) async fn accept_backend_typed<State, Handler>(
717 &mut self,
718 middleware: &mut Middleware<State, Handler>,
719 message: BackendMessage,
720 ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
721 where
722 Handler: BackendPipelineMiddleware<State>,
723 {
724 self.accept_response_typed(None, middleware, message).await
725 }
726
727 pub(crate) fn try_emit_local(
736 &mut self,
737 id: OperationId,
738 message: BackendMessage,
739 ) -> Result<BackendAction, BackendProjectionError> {
740 self.accept_response(Some(id), message)
741 }
742
743 pub(crate) async fn try_emit_local_typed<State, Handler>(
752 &mut self,
753 middleware: &mut Middleware<State, Handler>,
754 id: OperationId,
755 message: BackendMessage,
756 ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
757 where
758 Handler: BackendPipelineMiddleware<State>,
759 {
760 self.accept_response_typed(Some(id), middleware, message)
761 .await
762 }
763
764 pub(crate) async fn wait_until_emittable(&self, id: OperationId) {
769 loop {
770 let notified = self.changed.notified();
771 if self
772 .operations
773 .front()
774 .is_some_and(|operation| operation.id == id)
775 {
776 return;
777 }
778 notified.await;
779 }
780 }
781
782 async fn intercept_frontend<State, Handler>(
783 &self,
784 phase: FrontendPhase,
785 middleware: &mut Middleware<State, Handler>,
786 message: FrontendMessage,
787 ) -> Result<FrontendMessage, Handler::Error>
788 where
789 Handler: FrontendPipelineMiddleware<State>,
790 {
791 let (state, handler) = middleware.parts_mut();
792 macro_rules! dispatch {
793 ($message:expr, $type:path, $handler:ident, $state:ident, $method:ident) => {{
794 let Ok(typed) = <$type>::try_from($message) else {
795 unreachable!("frontend message was prevalidated for pipeline phase")
796 };
797 $handler.$method($state, typed).await?.into()
798 }};
799 }
800
801 macro_rules! dispatch_catalogue {
802 ($($catalogue_phase:ident => $method:ident => $message_type:path),+ $(,)?) => {
803 match phase {
804 $(
805 FrontendPhase::$catalogue_phase =>
806 dispatch!(message, $message_type, handler, state, $method),
807 )+
808 }
809 };
810 }
811
812 Ok(frontend_pipeline_phases!(dispatch_catalogue))
813 }
814
815 #[allow(clippy::too_many_lines)]
816 async fn accept_response_typed<State, Handler>(
817 &mut self,
818 local_id: Option<OperationId>,
819 middleware: &mut Middleware<State, Handler>,
820 message: BackendMessage,
821 ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
822 where
823 Handler: BackendPipelineMiddleware<State>,
824 {
825 let prepared = self.prepare_response(local_id, &message);
826 if matches!(prepared, PreparedResponse::Deferred) {
827 return Ok(BackendAction::Deferred(message));
828 }
829 if matches!(prepared, PreparedResponse::Illegal) {
830 return Err(PipelineMiddlewareError::Projection(
831 BackendProjectionError {
832 state: self.state(),
833 message,
834 },
835 ));
836 }
837
838 let (state, handler) = middleware.parts_mut();
839 let message = match prepared {
840 PreparedResponse::Asynchronous => {
841 let Ok(typed) = AsynchronousBackendMessage::try_from(message) else {
842 unreachable!("asynchronous response was prevalidated")
843 };
844 handler
845 .backend_asynchronous(state, typed)
846 .await
847 .map_err(PipelineMiddlewareError::Middleware)?
848 .into_wire()
849 }
850 PreparedResponse::Emit { response_state, .. } => {
851 macro_rules! dispatch {
852 ($message:ty, $method:ident) => {{
853 let typed = match <$message>::try_from(message) {
854 Ok(typed) => typed,
855 Err(message) => {
856 return Err(PipelineMiddlewareError::Projection(
857 BackendProjectionError {
858 state: self.state(),
859 message,
860 },
861 ));
862 }
863 };
864 handler
865 .$method(state, typed)
866 .await
867 .map_err(PipelineMiddlewareError::Middleware)?
868 .into_wire()
869 }};
870 }
871
872 macro_rules! dispatch_catalogue {
873 ($ignored:ident => $ignored_method:ident => AsynchronousBackendMessage,
874 $($catalogue_phase:ident => $method:ident => $message_type:path),+ $(,)?) => {
875 match response_state {
876 $(
877 backend::RuntimeState::$catalogue_phase =>
878 dispatch!($message_type, $method),
879 )+
880 _ => unreachable!("response phase has no backend-selected transition"),
881 }
882 };
883 }
884
885 backend_pipeline_phases!(dispatch_catalogue)
886 }
887 PreparedResponse::Deferred | PreparedResponse::Illegal => unreachable!(),
888 };
889
890 if !message.is_reconstructable() {
891 return Err(PipelineMiddlewareError::Projection(
892 BackendProjectionError {
893 state: self.state(),
894 message,
895 },
896 ));
897 }
898 self.commit_response(prepared, message)
899 .map_err(PipelineMiddlewareError::Projection)
900 }
901
902 fn prepare_response(
903 &self,
904 local_id: Option<OperationId>,
905 message: &BackendMessage,
906 ) -> PreparedResponse {
907 if is_asynchronous(message) {
908 return PreparedResponse::Asynchronous;
909 }
910 let Some(head) = self.operations.front().copied() else {
911 return PreparedResponse::Illegal;
912 };
913 if let Some(id) = local_id {
914 if head.id != id {
915 return PreparedResponse::Deferred;
916 }
917 if head.origin != Origin::Local {
918 return PreparedResponse::Illegal;
919 }
920 } else if head.origin == Origin::Local {
921 return if self.operations.iter().skip(1).any(|operation| {
922 operation.origin == Origin::Forwarded && response_fits(*operation, message)
923 }) {
924 PreparedResponse::Deferred
925 } else {
926 PreparedResponse::Illegal
927 };
928 }
929 if head.discarded || !response_fits(head, message) {
930 return if self
931 .operations
932 .iter()
933 .skip(1)
934 .any(|operation| response_fits(*operation, message))
935 {
936 PreparedResponse::Deferred
937 } else {
938 PreparedResponse::Illegal
939 };
940 }
941 PreparedResponse::Emit {
942 head,
943 response_state: head.response_state,
944 }
945 }
946
947 fn accept_response(
948 &mut self,
949 local_id: Option<OperationId>,
950 message: BackendMessage,
951 ) -> Result<BackendAction, BackendProjectionError> {
952 let prepared = self.prepare_response(local_id, &message);
953 self.commit_response(prepared, message)
954 }
955
956 fn commit_response(
957 &mut self,
958 prepared: PreparedResponse,
959 message: BackendMessage,
960 ) -> Result<BackendAction, BackendProjectionError> {
961 let PreparedResponse::Emit {
962 head,
963 response_state,
964 } = prepared
965 else {
966 return match prepared {
967 PreparedResponse::Asynchronous => Ok(BackendAction::Emit(message)),
968 PreparedResponse::Deferred => Ok(BackendAction::Deferred(message)),
969 PreparedResponse::Illegal => Err(BackendProjectionError {
970 state: self.state(),
971 message,
972 }),
973 PreparedResponse::Emit { .. } => unreachable!(),
974 };
975 };
976 let event = backend::project_internal(response_state, &message)
977 .expect("response was validated against its generated backend phase");
978 let next_response_state = backend::transition(response_state, event)
979 .expect("projected backend event has a generated transition")
980 .target;
981 let terminal = response_is_terminal(head.kind, &message);
982 let error = matches!(message, BackendMessage::ErrorResponse(_));
983 let copy_state = response_copy_state(next_response_state);
984 if terminal {
985 self.operations.pop_front();
986 if error && is_extended_kind(head.kind) {
987 self.enter_extended_error();
988 }
989 } else if let Some(head) = self.operations.front_mut() {
990 head.response_state = next_response_state;
991 }
992 if !terminal {
993 self.response_state = copy_state.map(RequestState::public);
994 }
995 if let Some(state) = copy_state
996 && matches!(state, RequestState::CopyIn | RequestState::CopyBoth)
997 {
998 self.request_state = state;
999 }
1000 if terminal {
1001 self.response_state = None;
1002 match head.kind {
1003 OperationKind::Sync | OperationKind::Query => {
1004 self.request_state = RequestState::Ready;
1005 }
1006 OperationKind::Execute if !error => {
1007 self.request_state = RequestState::Extended { bound: true };
1008 }
1009 _ => {}
1010 }
1011 }
1012 self.remove_inert_heads();
1013 self.changed.notify_waiters();
1014 Ok(BackendAction::Emit(message))
1015 }
1016
1017 fn enter_extended_error(&mut self) {
1018 self.request_state = RequestState::ExtendedError;
1019 self.response_state = None;
1020 for operation in &mut self.operations {
1021 if operation.kind == OperationKind::Sync {
1022 break;
1023 }
1024 operation.discarded = true;
1025 }
1026 }
1027
1028 fn remove_inert_heads(&mut self) {
1029 let previous_len = self.operations.len();
1030 while self.operations.front().is_some_and(|operation| {
1031 operation.kind == OperationKind::Flush
1032 || operation.kind == OperationKind::CopyData
1033 || operation.kind == OperationKind::CopyDone
1034 || operation.kind == OperationKind::CopyFail
1035 || operation.kind == OperationKind::Terminate
1036 || operation.discarded
1037 }) {
1038 self.operations.pop_front();
1039 }
1040 if self.operations.len() != previous_len {
1041 self.changed.notify_waiters();
1042 }
1043 }
1044}
1045
1046fn project_frontend(
1047 state: RequestState,
1048 message: &FrontendMessage,
1049) -> Option<(OperationKind, RequestState)> {
1050 use RequestState as S;
1051 let generated_state = match state {
1052 S::Ready => backend::RuntimeState::Ready,
1053 S::Extended { .. } => backend::RuntimeState::Building,
1054 S::ExtendedError => backend::RuntimeState::ExtendedError,
1055 S::CopyIn => backend::RuntimeState::ExtendedCopyIn,
1056 S::CopyOut => backend::RuntimeState::ExtendedCopyOut,
1057 S::CopyBoth => backend::RuntimeState::ExtendedCopyBoth,
1058 S::Terminated => backend::RuntimeState::Terminated,
1059 };
1060 backend::project_external(generated_state, message)?;
1061 classify_frontend(state, message)
1062}
1063
1064fn classify_frontend(
1065 state: RequestState,
1066 message: &FrontendMessage,
1067) -> Option<(OperationKind, RequestState)> {
1068 use FrontendMessage as F;
1069 use OperationKind as O;
1070 use RequestState as S;
1071
1072 match (state, message) {
1073 (S::Ready, F::Query(_)) => Some((O::Query, S::Ready)),
1074 (S::Ready, F::FunctionCall(_)) => Some((O::FunctionCall, S::Ready)),
1075 (S::Ready, F::Parse(_)) => Some((O::Parse, S::Extended { bound: false })),
1076 (S::Ready | S::Extended { .. }, F::Bind(_)) => Some((O::Bind, S::Extended { bound: true })),
1077 (S::Ready, F::Describe(_)) => Some((O::Describe, S::Extended { bound: false })),
1078 (S::Ready | S::Extended { .. }, F::Execute(_)) => {
1079 Some((O::Execute, S::Extended { bound: true }))
1080 }
1081 (S::Ready, F::Close(_)) => Some((O::Close, S::Extended { bound: false })),
1082 (S::Ready, F::Terminate) => Some((O::Terminate, S::Terminated)),
1083 (S::Extended { bound }, F::Parse(_)) => Some((O::Parse, S::Extended { bound })),
1084 (S::Extended { bound }, F::Describe(_)) => Some((O::Describe, S::Extended { bound })),
1085 (S::Extended { bound }, F::Close(_)) => Some((O::Close, S::Extended { bound })),
1086 (S::Extended { bound }, F::Flush) => Some((O::Flush, S::Extended { bound })),
1087 (S::Extended { .. } | S::ExtendedError, F::Sync) => Some((O::Sync, S::Ready)),
1088 (S::ExtendedError, _) => Some((classify_discard(message)?, S::ExtendedError)),
1089 (S::CopyIn, F::CopyData(_)) => Some((O::CopyData, S::CopyIn)),
1090 (S::CopyIn, F::CopyDone) => Some((O::CopyDone, S::Extended { bound: true })),
1091 (S::CopyIn, F::CopyFail(_)) => Some((O::CopyFail, S::ExtendedError)),
1092 (S::CopyBoth, F::CopyData(_)) => Some((O::CopyData, S::CopyBoth)),
1093 (S::CopyBoth, F::CopyDone) => Some((O::CopyDone, S::CopyBoth)),
1094 _ => None,
1095 }
1096}
1097
1098fn frontend_phase(state: RequestState, response_head: Option<OperationKind>) -> FrontendPhase {
1099 match (state, response_head) {
1100 (RequestState::Ready, _) => FrontendPhase::Ready,
1101 (RequestState::Extended { .. }, _) => FrontendPhase::Building,
1102 (RequestState::ExtendedError, _) => FrontendPhase::ExtendedError,
1103 (RequestState::CopyIn, Some(OperationKind::Query)) => FrontendPhase::SimpleCopyIn,
1104 (RequestState::CopyIn, Some(OperationKind::Execute)) => FrontendPhase::ExtendedCopyIn,
1105 (RequestState::CopyBoth, Some(OperationKind::Query)) => FrontendPhase::SimpleCopyBoth,
1106 (RequestState::CopyBoth, Some(OperationKind::Execute)) => FrontendPhase::ExtendedCopyBoth,
1107 (RequestState::CopyIn | RequestState::CopyBoth, _) => {
1108 unreachable!("COPY phase must belong to Query or Execute")
1109 }
1110 (RequestState::CopyOut | RequestState::Terminated, _) => {
1111 unreachable!("non-accepting frontend phase cannot be prepared")
1112 }
1113 }
1114}
1115
1116fn classify_discard(message: &FrontendMessage) -> Option<OperationKind> {
1117 Some(match message {
1118 FrontendMessage::Parse(_) => OperationKind::Parse,
1119 FrontendMessage::Bind(_) => OperationKind::Bind,
1120 FrontendMessage::Describe(_) => OperationKind::Describe,
1121 FrontendMessage::Execute(_) => OperationKind::Execute,
1122 FrontendMessage::Close(_) => OperationKind::Close,
1123 FrontendMessage::Flush => OperationKind::Flush,
1124 FrontendMessage::Query(_) => OperationKind::Query,
1125 FrontendMessage::FunctionCall(_) => OperationKind::FunctionCall,
1126 FrontendMessage::CopyData(_) => OperationKind::CopyData,
1127 FrontendMessage::CopyDone => OperationKind::CopyDone,
1128 FrontendMessage::CopyFail(_) => OperationKind::CopyFail,
1129 FrontendMessage::Terminate => OperationKind::Terminate,
1130 FrontendMessage::PasswordResponse(_) => return None,
1131 FrontendMessage::Sync => unreachable!("Sync is classified before discard"),
1132 })
1133}
1134
1135const fn initial_response_state(kind: OperationKind) -> backend::RuntimeState {
1136 use OperationKind as O;
1137 match kind {
1138 O::Query => backend::RuntimeState::Simple,
1139 O::FunctionCall => backend::RuntimeState::FunctionResponse,
1140 O::Parse => backend::RuntimeState::ParseResponse,
1141 O::Bind => backend::RuntimeState::BindResponse,
1142 O::Describe => backend::RuntimeState::DescribeResponse,
1143 O::Execute => backend::RuntimeState::ExecuteResponse,
1144 O::Close => backend::RuntimeState::CloseResponse,
1145 O::Sync => backend::RuntimeState::SyncResponse,
1146 O::Flush | O::CopyData | O::CopyDone | O::CopyFail | O::Terminate => {
1147 backend::RuntimeState::Terminated
1148 }
1149 }
1150}
1151
1152fn response_fits(operation: Operation, message: &BackendMessage) -> bool {
1153 backend::project_internal(operation.response_state, message).is_some()
1154}
1155
1156fn response_is_terminal(kind: OperationKind, message: &BackendMessage) -> bool {
1157 use BackendMessage as B;
1158 use OperationKind as O;
1159 match kind {
1160 O::Query | O::FunctionCall | O::Sync => matches!(message, B::ReadyForQuery(_)),
1161 O::Parse => matches!(message, B::ParseComplete | B::ErrorResponse(_)),
1162 O::Bind => matches!(message, B::BindComplete | B::ErrorResponse(_)),
1163 O::Describe => matches!(
1164 message,
1165 B::RowDescription(_) | B::NoData | B::ErrorResponse(_)
1166 ),
1167 O::Execute => matches!(
1168 message,
1169 B::CommandComplete(_) | B::PortalSuspended | B::ErrorResponse(_)
1170 ),
1171 O::Close => matches!(message, B::CloseComplete | B::ErrorResponse(_)),
1172 O::CopyDone => matches!(
1173 message,
1174 B::CopyDone | B::CommandComplete(_) | B::ErrorResponse(_)
1175 ),
1176 O::CopyFail => matches!(message, B::ErrorResponse(_)),
1177 O::Flush | O::CopyData | O::Terminate => true,
1178 }
1179}
1180
1181fn is_extended_kind(kind: OperationKind) -> bool {
1182 !matches!(
1183 kind,
1184 OperationKind::Query | OperationKind::FunctionCall | OperationKind::Terminate
1185 )
1186}
1187
1188fn response_copy_state(state: backend::RuntimeState) -> Option<RequestState> {
1189 use backend::RuntimeState as S;
1190 match state {
1191 S::SimpleCopyIn | S::ExtendedCopyIn => Some(RequestState::CopyIn),
1192 S::SimpleCopyOut | S::ExtendedCopyOut => Some(RequestState::CopyOut),
1193 S::SimpleCopyBoth
1194 | S::SimpleCopyBothClientDone
1195 | S::SimpleCopyBothServerDone
1196 | S::ExtendedCopyBoth
1197 | S::ExtendedCopyBothClientDone
1198 | S::ExtendedCopyBothServerDone => Some(RequestState::CopyBoth),
1199 _ => None,
1200 }
1201}
1202
1203fn is_asynchronous(message: &BackendMessage) -> bool {
1204 Demux::is_asynchronous(message)
1205}