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 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 enum FrontendHandling {
100 Forward,
102 Local,
104}
105
106#[derive(Debug, Eq, PartialEq)]
108pub 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 enum FrontendAdmission {
126 Immediate(FrontendAction),
128 Waiting(FrontendAction),
130}
131
132impl FrontendAdmission {
133 #[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#[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 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 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 trait FrontendPipelineMiddleware<State> {
269 type Error;
271 frontend_pipeline_phases!(declare_pipeline_hooks);
272}
273
274#[allow(async_fn_in_trait)]
276pub 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 struct PipelineWireAdapter<Handler> {
333 handler: Handler,
334}
335
336impl<Handler> PipelineWireAdapter<Handler> {
337 pub const fn new(handler: Handler) -> Self {
339 Self { handler }
340 }
341
342 pub fn into_inner(self) -> Handler {
344 self.handler
345 }
346}
347
348#[derive(Debug)]
350pub enum FrontendPipelineWireAdapterError<Error> {
351 Middleware(Error),
353 IllegalFrontend(FrontendMessage),
355}
356
357#[derive(Debug)]
359pub 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 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 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 fn state(&self) -> PipelineState {
552 self.response_state
553 .unwrap_or_else(|| self.request_state.public())
554 }
555
556 #[must_use]
558 pub fn len(&self) -> usize {
559 self.operations.len()
560 }
561
562 #[must_use]
564 pub fn is_empty(&self) -> bool {
565 self.operations.is_empty()
566 }
567
568 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 pub async fn accept_frontend_typed<State, Handler>(
662 &mut self,
663 middleware: &mut Middleware<State, Handler>,
664 message: FrontendMessage,
665 handling: FrontendHandling,
666 ) -> Result<FrontendAdmission, PipelineMiddlewareError<Handler::Error, FrontendProjectionError>>
667 where
668 Handler: FrontendPipelineMiddleware<State>,
669 {
670 let prepared = self
671 .prepare_frontend(&message)
672 .map_err(PipelineMiddlewareError::Projection)?;
673
674 let message = self
675 .intercept_frontend(prepared.phase, middleware, message)
676 .await
677 .map_err(PipelineMiddlewareError::Middleware)?;
678 if !message.is_reconstructable() {
679 return Err(PipelineMiddlewareError::Projection(
680 FrontendProjectionError::Illegal {
681 state: self.state(),
682 message: Box::new(message),
683 },
684 ));
685 }
686 Ok(self.commit_frontend(prepared, message, handling))
687 }
688
689 pub fn accept_backend(
695 &mut self,
696 message: BackendMessage,
697 ) -> Result<BackendAction, BackendProjectionError> {
698 self.accept_response(None, message)
699 }
700
701 pub async fn accept_backend_typed<State, Handler>(
712 &mut self,
713 middleware: &mut Middleware<State, Handler>,
714 message: BackendMessage,
715 ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
716 where
717 Handler: BackendPipelineMiddleware<State>,
718 {
719 self.accept_response_typed(None, middleware, message).await
720 }
721
722 pub fn try_emit_local(
731 &mut self,
732 id: OperationId,
733 message: BackendMessage,
734 ) -> Result<BackendAction, BackendProjectionError> {
735 self.accept_response(Some(id), message)
736 }
737
738 pub async fn try_emit_local_typed<State, Handler>(
747 &mut self,
748 middleware: &mut Middleware<State, Handler>,
749 id: OperationId,
750 message: BackendMessage,
751 ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
752 where
753 Handler: BackendPipelineMiddleware<State>,
754 {
755 self.accept_response_typed(Some(id), middleware, message)
756 .await
757 }
758
759 pub async fn wait_until_emittable(&self, id: OperationId) {
764 loop {
765 let notified = self.changed.notified();
766 if self
767 .operations
768 .front()
769 .is_some_and(|operation| operation.id == id)
770 {
771 return;
772 }
773 notified.await;
774 }
775 }
776
777 async fn intercept_frontend<State, Handler>(
778 &self,
779 phase: FrontendPhase,
780 middleware: &mut Middleware<State, Handler>,
781 message: FrontendMessage,
782 ) -> Result<FrontendMessage, Handler::Error>
783 where
784 Handler: FrontendPipelineMiddleware<State>,
785 {
786 let (state, handler) = middleware.parts_mut();
787 macro_rules! dispatch {
788 ($message:expr, $type:path, $handler:ident, $state:ident, $method:ident) => {{
789 let Ok(typed) = <$type>::try_from($message) else {
790 unreachable!("frontend message was prevalidated for pipeline phase")
791 };
792 $handler.$method($state, typed).await?.into()
793 }};
794 }
795
796 macro_rules! dispatch_catalogue {
797 ($($catalogue_phase:ident => $method:ident => $message_type:path),+ $(,)?) => {
798 match phase {
799 $(
800 FrontendPhase::$catalogue_phase =>
801 dispatch!(message, $message_type, handler, state, $method),
802 )+
803 }
804 };
805 }
806
807 Ok(frontend_pipeline_phases!(dispatch_catalogue))
808 }
809
810 #[allow(clippy::too_many_lines)]
811 async fn accept_response_typed<State, Handler>(
812 &mut self,
813 local_id: Option<OperationId>,
814 middleware: &mut Middleware<State, Handler>,
815 message: BackendMessage,
816 ) -> Result<BackendAction, PipelineMiddlewareError<Handler::Error, BackendProjectionError>>
817 where
818 Handler: BackendPipelineMiddleware<State>,
819 {
820 let prepared = self.prepare_response(local_id, &message);
821 if matches!(prepared, PreparedResponse::Deferred) {
822 return Ok(BackendAction::Deferred(message));
823 }
824 if matches!(prepared, PreparedResponse::Illegal) {
825 return Err(PipelineMiddlewareError::Projection(
826 BackendProjectionError {
827 state: self.state(),
828 message,
829 },
830 ));
831 }
832
833 let (state, handler) = middleware.parts_mut();
834 let message = match prepared {
835 PreparedResponse::Asynchronous => {
836 let Ok(typed) = AsynchronousBackendMessage::try_from(message) else {
837 unreachable!("asynchronous response was prevalidated")
838 };
839 handler
840 .backend_asynchronous(state, typed)
841 .await
842 .map_err(PipelineMiddlewareError::Middleware)?
843 .into_wire()
844 }
845 PreparedResponse::Emit { response_state, .. } => {
846 macro_rules! dispatch {
847 ($message:ty, $method:ident) => {{
848 let typed = match <$message>::try_from(message) {
849 Ok(typed) => typed,
850 Err(message) => {
851 return Err(PipelineMiddlewareError::Projection(
852 BackendProjectionError {
853 state: self.state(),
854 message,
855 },
856 ));
857 }
858 };
859 handler
860 .$method(state, typed)
861 .await
862 .map_err(PipelineMiddlewareError::Middleware)?
863 .into_wire()
864 }};
865 }
866
867 macro_rules! dispatch_catalogue {
868 ($ignored:ident => $ignored_method:ident => AsynchronousBackendMessage,
869 $($catalogue_phase:ident => $method:ident => $message_type:path),+ $(,)?) => {
870 match response_state {
871 $(
872 backend::RuntimeState::$catalogue_phase =>
873 dispatch!($message_type, $method),
874 )+
875 _ => unreachable!("response phase has no backend-selected transition"),
876 }
877 };
878 }
879
880 backend_pipeline_phases!(dispatch_catalogue)
881 }
882 PreparedResponse::Deferred | PreparedResponse::Illegal => unreachable!(),
883 };
884
885 if !message.is_reconstructable() {
886 return Err(PipelineMiddlewareError::Projection(
887 BackendProjectionError {
888 state: self.state(),
889 message,
890 },
891 ));
892 }
893 self.commit_response(prepared, message)
894 .map_err(PipelineMiddlewareError::Projection)
895 }
896
897 fn prepare_response(
898 &self,
899 local_id: Option<OperationId>,
900 message: &BackendMessage,
901 ) -> PreparedResponse {
902 if is_asynchronous(message) {
903 return PreparedResponse::Asynchronous;
904 }
905 let Some(head) = self.operations.front().copied() else {
906 return PreparedResponse::Illegal;
907 };
908 if let Some(id) = local_id {
909 if head.id != id {
910 return PreparedResponse::Deferred;
911 }
912 if head.origin != Origin::Local {
913 return PreparedResponse::Illegal;
914 }
915 } else if head.origin == Origin::Local {
916 return if self.operations.iter().skip(1).any(|operation| {
917 operation.origin == Origin::Forwarded && response_fits(*operation, message)
918 }) {
919 PreparedResponse::Deferred
920 } else {
921 PreparedResponse::Illegal
922 };
923 }
924 if head.discarded || !response_fits(head, message) {
925 return if self
926 .operations
927 .iter()
928 .skip(1)
929 .any(|operation| response_fits(*operation, message))
930 {
931 PreparedResponse::Deferred
932 } else {
933 PreparedResponse::Illegal
934 };
935 }
936 PreparedResponse::Emit {
937 head,
938 response_state: head.response_state,
939 }
940 }
941
942 fn accept_response(
943 &mut self,
944 local_id: Option<OperationId>,
945 message: BackendMessage,
946 ) -> Result<BackendAction, BackendProjectionError> {
947 let prepared = self.prepare_response(local_id, &message);
948 self.commit_response(prepared, message)
949 }
950
951 fn commit_response(
952 &mut self,
953 prepared: PreparedResponse,
954 message: BackendMessage,
955 ) -> Result<BackendAction, BackendProjectionError> {
956 let PreparedResponse::Emit {
957 head,
958 response_state,
959 } = prepared
960 else {
961 return match prepared {
962 PreparedResponse::Asynchronous => Ok(BackendAction::Emit(message)),
963 PreparedResponse::Deferred => Ok(BackendAction::Deferred(message)),
964 PreparedResponse::Illegal => Err(BackendProjectionError {
965 state: self.state(),
966 message,
967 }),
968 PreparedResponse::Emit { .. } => unreachable!(),
969 };
970 };
971 let event = backend::project_internal(response_state, &message)
972 .expect("response was validated against its generated backend phase");
973 let next_response_state = backend::transition(response_state, event)
974 .expect("projected backend event has a generated transition")
975 .target;
976 let terminal = response_is_terminal(head.kind, &message);
977 let error = matches!(message, BackendMessage::ErrorResponse(_));
978 let copy_state = response_copy_state(next_response_state);
979 if terminal {
980 self.operations.pop_front();
981 if error && is_extended_kind(head.kind) {
982 self.enter_extended_error();
983 }
984 } else if let Some(head) = self.operations.front_mut() {
985 head.response_state = next_response_state;
986 }
987 if !terminal {
988 self.response_state = copy_state.map(RequestState::public);
989 }
990 if let Some(state) = copy_state
991 && matches!(state, RequestState::CopyIn | RequestState::CopyBoth)
992 {
993 self.request_state = state;
994 }
995 if terminal {
996 self.response_state = None;
997 match head.kind {
998 OperationKind::Sync | OperationKind::Query => {
999 self.request_state = RequestState::Ready;
1000 }
1001 OperationKind::Execute if !error => {
1002 self.request_state = RequestState::Extended { bound: true };
1003 }
1004 _ => {}
1005 }
1006 }
1007 self.remove_inert_heads();
1008 self.changed.notify_waiters();
1009 Ok(BackendAction::Emit(message))
1010 }
1011
1012 fn enter_extended_error(&mut self) {
1013 self.request_state = RequestState::ExtendedError;
1014 self.response_state = None;
1015 for operation in &mut self.operations {
1016 if operation.kind == OperationKind::Sync {
1017 break;
1018 }
1019 operation.discarded = true;
1020 }
1021 }
1022
1023 fn remove_inert_heads(&mut self) {
1024 let previous_len = self.operations.len();
1025 while self.operations.front().is_some_and(|operation| {
1026 operation.kind == OperationKind::Flush
1027 || operation.kind == OperationKind::CopyData
1028 || operation.kind == OperationKind::CopyDone
1029 || operation.kind == OperationKind::CopyFail
1030 || operation.kind == OperationKind::Terminate
1031 || operation.discarded
1032 }) {
1033 self.operations.pop_front();
1034 }
1035 if self.operations.len() != previous_len {
1036 self.changed.notify_waiters();
1037 }
1038 }
1039}
1040
1041fn project_frontend(
1042 state: RequestState,
1043 message: &FrontendMessage,
1044) -> Option<(OperationKind, RequestState)> {
1045 use RequestState as S;
1046 let generated_state = match state {
1047 S::Ready => backend::RuntimeState::Ready,
1048 S::Extended { .. } => backend::RuntimeState::Building,
1049 S::ExtendedError => backend::RuntimeState::ExtendedError,
1050 S::CopyIn => backend::RuntimeState::ExtendedCopyIn,
1051 S::CopyOut => backend::RuntimeState::ExtendedCopyOut,
1052 S::CopyBoth => backend::RuntimeState::ExtendedCopyBoth,
1053 S::Terminated => backend::RuntimeState::Terminated,
1054 };
1055 backend::project_external(generated_state, message)?;
1056 classify_frontend(state, message)
1057}
1058
1059fn classify_frontend(
1060 state: RequestState,
1061 message: &FrontendMessage,
1062) -> Option<(OperationKind, RequestState)> {
1063 use FrontendMessage as F;
1064 use OperationKind as O;
1065 use RequestState as S;
1066
1067 match (state, message) {
1068 (S::Ready, F::Query(_)) => Some((O::Query, S::Ready)),
1069 (S::Ready, F::FunctionCall(_)) => Some((O::FunctionCall, S::Ready)),
1070 (S::Ready, F::Parse(_)) => Some((O::Parse, S::Extended { bound: false })),
1071 (S::Ready | S::Extended { .. }, F::Bind(_)) => Some((O::Bind, S::Extended { bound: true })),
1072 (S::Ready, F::Describe(_)) => Some((O::Describe, S::Extended { bound: false })),
1073 (S::Ready | S::Extended { .. }, F::Execute(_)) => {
1074 Some((O::Execute, S::Extended { bound: true }))
1075 }
1076 (S::Ready, F::Close(_)) => Some((O::Close, S::Extended { bound: false })),
1077 (S::Ready, F::Terminate) => Some((O::Terminate, S::Terminated)),
1078 (S::Extended { bound }, F::Parse(_)) => Some((O::Parse, S::Extended { bound })),
1079 (S::Extended { bound }, F::Describe(_)) => Some((O::Describe, S::Extended { bound })),
1080 (S::Extended { bound }, F::Close(_)) => Some((O::Close, S::Extended { bound })),
1081 (S::Extended { bound }, F::Flush) => Some((O::Flush, S::Extended { bound })),
1082 (S::Extended { .. } | S::ExtendedError, F::Sync) => Some((O::Sync, S::Ready)),
1083 (S::ExtendedError, _) => Some((classify_discard(message)?, S::ExtendedError)),
1084 (S::CopyIn, F::CopyData(_)) => Some((O::CopyData, S::CopyIn)),
1085 (S::CopyIn, F::CopyDone) => Some((O::CopyDone, S::Extended { bound: true })),
1086 (S::CopyIn, F::CopyFail(_)) => Some((O::CopyFail, S::ExtendedError)),
1087 (S::CopyBoth, F::CopyData(_)) => Some((O::CopyData, S::CopyBoth)),
1088 (S::CopyBoth, F::CopyDone) => Some((O::CopyDone, S::CopyBoth)),
1089 _ => None,
1090 }
1091}
1092
1093fn frontend_phase(state: RequestState, response_head: Option<OperationKind>) -> FrontendPhase {
1094 match (state, response_head) {
1095 (RequestState::Ready, _) => FrontendPhase::Ready,
1096 (RequestState::Extended { .. }, _) => FrontendPhase::Building,
1097 (RequestState::ExtendedError, _) => FrontendPhase::ExtendedError,
1098 (RequestState::CopyIn, Some(OperationKind::Query)) => FrontendPhase::SimpleCopyIn,
1099 (RequestState::CopyIn, Some(OperationKind::Execute)) => FrontendPhase::ExtendedCopyIn,
1100 (RequestState::CopyBoth, Some(OperationKind::Query)) => FrontendPhase::SimpleCopyBoth,
1101 (RequestState::CopyBoth, Some(OperationKind::Execute)) => FrontendPhase::ExtendedCopyBoth,
1102 (RequestState::CopyIn | RequestState::CopyBoth, _) => {
1103 unreachable!("COPY phase must belong to Query or Execute")
1104 }
1105 (RequestState::CopyOut | RequestState::Terminated, _) => {
1106 unreachable!("non-accepting frontend phase cannot be prepared")
1107 }
1108 }
1109}
1110
1111fn classify_discard(message: &FrontendMessage) -> Option<OperationKind> {
1112 Some(match message {
1113 FrontendMessage::Parse(_) => OperationKind::Parse,
1114 FrontendMessage::Bind(_) => OperationKind::Bind,
1115 FrontendMessage::Describe(_) => OperationKind::Describe,
1116 FrontendMessage::Execute(_) => OperationKind::Execute,
1117 FrontendMessage::Close(_) => OperationKind::Close,
1118 FrontendMessage::Flush => OperationKind::Flush,
1119 FrontendMessage::Query(_) => OperationKind::Query,
1120 FrontendMessage::FunctionCall(_) => OperationKind::FunctionCall,
1121 FrontendMessage::CopyData(_) => OperationKind::CopyData,
1122 FrontendMessage::CopyDone => OperationKind::CopyDone,
1123 FrontendMessage::CopyFail(_) => OperationKind::CopyFail,
1124 FrontendMessage::Terminate => OperationKind::Terminate,
1125 FrontendMessage::PasswordResponse(_) => return None,
1126 FrontendMessage::Sync => unreachable!("Sync is classified before discard"),
1127 })
1128}
1129
1130const fn initial_response_state(kind: OperationKind) -> backend::RuntimeState {
1131 use OperationKind as O;
1132 match kind {
1133 O::Query => backend::RuntimeState::Simple,
1134 O::FunctionCall => backend::RuntimeState::FunctionResponse,
1135 O::Parse => backend::RuntimeState::ParseResponse,
1136 O::Bind => backend::RuntimeState::BindResponse,
1137 O::Describe => backend::RuntimeState::DescribeResponse,
1138 O::Execute => backend::RuntimeState::ExecuteResponse,
1139 O::Close => backend::RuntimeState::CloseResponse,
1140 O::Sync => backend::RuntimeState::SyncResponse,
1141 O::Flush | O::CopyData | O::CopyDone | O::CopyFail | O::Terminate => {
1142 backend::RuntimeState::Terminated
1143 }
1144 }
1145}
1146
1147fn response_fits(operation: Operation, message: &BackendMessage) -> bool {
1148 backend::project_internal(operation.response_state, message).is_some()
1149}
1150
1151fn response_is_terminal(kind: OperationKind, message: &BackendMessage) -> bool {
1152 use BackendMessage as B;
1153 use OperationKind as O;
1154 match kind {
1155 O::Query | O::FunctionCall | O::Sync => matches!(message, B::ReadyForQuery(_)),
1156 O::Parse => matches!(message, B::ParseComplete | B::ErrorResponse(_)),
1157 O::Bind => matches!(message, B::BindComplete | B::ErrorResponse(_)),
1158 O::Describe => matches!(
1159 message,
1160 B::RowDescription(_) | B::NoData | B::ErrorResponse(_)
1161 ),
1162 O::Execute => matches!(
1163 message,
1164 B::CommandComplete(_) | B::PortalSuspended | B::ErrorResponse(_)
1165 ),
1166 O::Close => matches!(message, B::CloseComplete | B::ErrorResponse(_)),
1167 O::CopyDone => matches!(
1168 message,
1169 B::CopyDone | B::CommandComplete(_) | B::ErrorResponse(_)
1170 ),
1171 O::CopyFail => matches!(message, B::ErrorResponse(_)),
1172 O::Flush | O::CopyData | O::Terminate => true,
1173 }
1174}
1175
1176fn is_extended_kind(kind: OperationKind) -> bool {
1177 !matches!(
1178 kind,
1179 OperationKind::Query | OperationKind::FunctionCall | OperationKind::Terminate
1180 )
1181}
1182
1183fn response_copy_state(state: backend::RuntimeState) -> Option<RequestState> {
1184 use backend::RuntimeState as S;
1185 match state {
1186 S::SimpleCopyIn | S::ExtendedCopyIn => Some(RequestState::CopyIn),
1187 S::SimpleCopyOut | S::ExtendedCopyOut => Some(RequestState::CopyOut),
1188 S::SimpleCopyBoth
1189 | S::SimpleCopyBothClientDone
1190 | S::SimpleCopyBothServerDone
1191 | S::ExtendedCopyBoth
1192 | S::ExtendedCopyBothClientDone
1193 | S::ExtendedCopyBothServerDone => Some(RequestState::CopyBoth),
1194 _ => None,
1195 }
1196}
1197
1198fn is_asynchronous(message: &BackendMessage) -> bool {
1199 Demux::is_asynchronous(message)
1200}