1use std::io;
4
5use bytes::{BufMut, Bytes, BytesMut};
6
7use crate::{
8 Conn, Dirty, Pristine,
9 auth::Ready,
10 codec::{
11 BackendMessage, Bind, Close, CopyResponse, Describe, DiagnosticResponse, Execute, Frame,
12 FunctionCall, Parse, TransactionStatus,
13 },
14 demux::SessionItem,
15 grammar::frontend,
16 pre_startup::Terminated,
17 replication::{BackendReplication, FrontendReplication},
18};
19
20#[derive(Debug)]
21pub enum SimpleQuery {}
23
24#[derive(Debug)]
25pub enum FunctionCalling {}
27
28#[derive(Debug)]
29pub enum Building {}
31
32#[derive(Debug)]
33pub enum BoundBuilding {}
35
36#[derive(Debug)]
37pub enum AwaitingReady {}
39
40#[derive(Debug)]
41pub enum CopyIn {}
43
44#[derive(Debug)]
45pub enum CopyOut {}
47
48#[derive(Debug)]
49pub enum CopyBoth {}
51
52#[derive(Debug)]
53pub enum CopyBothClientDone {}
55
56#[derive(Debug)]
57pub enum CopyBothServerDone {}
59
60#[derive(Debug)]
61pub enum Draining {}
63
64#[derive(Debug)]
65pub enum Resetting {}
67
68#[derive(Debug)]
69pub enum ResetComplete {}
71
72pub type ErrorResponse = DiagnosticResponse;
74
75pub type Fallible<T, S, C = Pristine> = Result<T, (Conn<S, Draining, C>, ErrorResponse)>;
77
78#[derive(Debug)]
80pub enum SimpleTransition<S, C> {
81 Continue(Conn<S, SimpleQuery, C>, SessionItem),
83 CopyIn(Conn<S, CopyIn, C>, CopyResponse),
85 CopyOut(Conn<S, CopyOut, C>, CopyResponse),
87 CopyBoth(Conn<S, CopyBoth, C>, CopyResponse),
89 Ready(ReadyState<S, C>),
91 Error(Conn<S, Draining, C>, ErrorResponse),
93}
94
95#[derive(Debug)]
97pub enum AwaitingReadyTransition<S, C> {
98 Continue(Conn<S, AwaitingReady, C>, SessionItem),
100 Ready(ReadyState<S, C>),
102 Error(Conn<S, Draining, C>, ErrorResponse),
104}
105
106#[derive(Debug)]
108pub enum FunctionCallTransition<S, C> {
109 Response(Conn<S, AwaitingReady, C>, Bytes),
111 Error(Conn<S, Draining, C>, ErrorResponse),
113}
114
115#[derive(Debug)]
117pub enum DrainingTransition<S, C> {
118 Continue(Conn<S, Draining, C>, SessionItem),
120 Ready(ReadyState<S, C>),
122}
123
124#[derive(Debug)]
126pub enum CopyOutTransition<S, C> {
127 Data(Conn<S, CopyOut, C>, Bytes),
129 Done(Conn<S, AwaitingReady, C>),
131 Error(Conn<S, Draining, C>, ErrorResponse),
133}
134
135#[derive(Debug)]
137pub enum CopyInTransition<S, C> {
138 Error(Conn<S, Draining, C>, ErrorResponse),
140}
141
142#[derive(Debug)]
144pub enum CopyBothReceive<S, C> {
145 Data(Conn<S, CopyBoth, C>, Bytes),
147 Done(Conn<S, CopyBothServerDone, C>),
149 Error(Conn<S, Draining, C>, ErrorResponse),
151}
152
153#[derive(Debug)]
155pub enum CopyBothClientDoneReceive<S, C> {
156 Data(Conn<S, CopyBothClientDone, C>, Bytes),
158 Done(Conn<S, AwaitingReady, C>),
160 Error(Conn<S, Draining, C>, ErrorResponse),
162}
163
164#[derive(Debug)]
166pub enum ReplicationReceive<S, C> {
167 Message(Conn<S, CopyBoth, C>, BackendReplication),
169 Done(Conn<S, CopyBothServerDone, C>),
171 Error(Conn<S, Draining, C>, ErrorResponse),
173}
174
175#[derive(Debug)]
177pub enum ReplicationClientDoneReceive<S, C> {
178 Message(Conn<S, CopyBothClientDone, C>, BackendReplication),
180 Done(Conn<S, AwaitingReady, C>),
182 Error(Conn<S, Draining, C>, ErrorResponse),
184}
185
186pub type ReplicationProjection<S, C> =
188 Result<ReplicationReceive<S, C>, (Conn<S, CopyBoth, C>, io::Error)>;
189pub type ReplicationClientDoneProjection<S, C> =
191 Result<ReplicationClientDoneReceive<S, C>, (Conn<S, CopyBothClientDone, C>, io::Error)>;
192
193#[derive(Debug)]
195pub enum ReadyState<S, C> {
196 Clean(Conn<S, Ready, C>),
198 Dirty {
200 conn: Conn<S, Ready, Dirty>,
202 status: TransactionStatus,
204 parameters_changed: bool,
206 },
207}
208
209#[derive(Debug)]
211pub enum ResettingTransition<S> {
212 Continue(Conn<S, Resetting, Dirty>, SessionItem),
214 Complete(Conn<S, ResetComplete, Dirty>),
216 Error(Conn<S, Draining, Dirty>, ErrorResponse),
218}
219
220#[derive(Debug)]
222pub enum ResetCompleteTransition<S> {
223 Continue(Conn<S, ResetComplete, Dirty>, SessionItem),
225 Ready(Conn<S, Ready, Pristine>),
227 Dirty {
229 conn: Conn<S, Ready, Dirty>,
231 status: TransactionStatus,
233 parameters_changed: bool,
235 },
236 Error(Conn<S, Draining, Dirty>, ErrorResponse),
238}
239
240impl<S, C> Conn<S, Ready, C> {
241 pub fn push_terminate(self) -> (Conn<S, Terminated, C>, Frame) {
243 (self.transition(), empty_frame(b'X'))
244 }
245
246 pub fn push_query(self, query: &[u8]) -> io::Result<(Conn<S, SimpleQuery, Dirty>, Frame)> {
257 Ok((self.transition(), cstr_frame(b'Q', query)?))
258 }
259
260 pub fn push_stateless_query(
266 self,
267 query: &[u8],
268 ) -> io::Result<(Conn<S, SimpleQuery, C>, Frame)> {
269 Ok((self.transition(), cstr_frame(b'Q', query)?))
270 }
271
272 pub fn push_function_call(
278 self,
279 message: &FunctionCall,
280 ) -> io::Result<(Conn<S, FunctionCalling, Dirty>, Frame)> {
281 Ok((self.transition(), message.to_frame()?))
282 }
283
284 pub fn push_stateless_function_call(
290 self,
291 message: &FunctionCall,
292 ) -> io::Result<(Conn<S, FunctionCalling, C>, Frame)> {
293 Ok((self.transition(), message.to_frame()?))
294 }
295
296 pub fn begin_extended(self) -> Conn<S, Building, C> {
306 self.transition()
307 }
308}
309
310impl<S, C> Conn<S, FunctionCalling, C> {
311 pub fn offer(
318 self,
319 message: BackendMessage,
320 ) -> Result<FunctionCallTransition<S, C>, (Self, BackendMessage)> {
321 match (
322 frontend::project_external(frontend::RuntimeState::FunctionCalling, &message),
323 message,
324 ) {
325 (
326 Some(frontend::Event::FunctionResponse),
327 BackendMessage::FunctionCallResponse(value),
328 ) => Ok(FunctionCallTransition::Response(self.transition(), value)),
329 (Some(frontend::Event::Error), BackendMessage::ErrorResponse(error)) => {
330 Ok(FunctionCallTransition::Error(self.transition(), error))
331 }
332 (_, other) => Err((self, other)),
333 }
334 }
335}
336
337impl<S> Conn<S, Ready, Pristine> {
338 pub fn release(self) -> S {
347 self.into_transport()
348 }
349}
350
351impl<S> Conn<S, Ready, Dirty> {
352 pub fn begin_reset(self) -> io::Result<(Conn<S, Resetting, Dirty>, Frame)> {
361 Ok((
362 self.transition(),
363 cstr_frame(b'Q', b"ROLLBACK; DISCARD ALL")?,
364 ))
365 }
366}
367
368impl<S, C> Conn<S, Building, C> {
369 pub fn push_parse(self, message: &Parse) -> io::Result<(Conn<S, Building, Dirty>, Frame)> {
375 Ok((self.transition(), message.to_frame()?))
376 }
377
378 pub fn push_describe(self, message: &Describe) -> io::Result<(Self, Frame)> {
384 Ok((self, message.to_frame()?))
385 }
386
387 pub fn push_bind(self, message: &Bind) -> io::Result<(Conn<S, BoundBuilding, Dirty>, Frame)> {
402 Ok((self.transition(), message.to_frame()?))
403 }
404
405 pub fn push_close(self, message: &Close) -> io::Result<(Self, Frame)> {
410 Ok((self, message.to_frame()?))
411 }
412
413 pub fn push_flush(self) -> (Self, Frame) {
415 (self, empty_frame(b'H'))
416 }
417
418 pub fn push_sync(self) -> (Conn<S, AwaitingReady, C>, Frame) {
420 (self.transition(), empty_frame(b'S'))
421 }
422}
423
424impl<S, C> Conn<S, BoundBuilding, C> {
425 pub fn push_parse(self, message: &Parse) -> io::Result<(Conn<S, BoundBuilding, Dirty>, Frame)> {
429 Ok((self.transition(), message.to_frame()?))
430 }
431
432 pub fn push_bind(self, message: &Bind) -> io::Result<(Conn<S, BoundBuilding, Dirty>, Frame)> {
436 Ok((self.transition(), message.to_frame()?))
437 }
438
439 pub fn push_describe(self, message: &Describe) -> io::Result<(Self, Frame)> {
443 Ok((self, message.to_frame()?))
444 }
445
446 pub fn push_execute(self, message: &Execute) -> io::Result<(Self, Frame)> {
452 Ok((self, message.to_frame()?))
453 }
454
455 pub fn push_close(self, message: &Close) -> io::Result<(Self, Frame)> {
459 Ok((self, message.to_frame()?))
460 }
461
462 pub fn push_flush(self) -> (Self, Frame) {
464 (self, empty_frame(b'H'))
465 }
466
467 pub fn push_sync(self) -> (Conn<S, AwaitingReady, C>, Frame) {
469 (self.transition(), empty_frame(b'S'))
470 }
471}
472
473impl<S, C> Conn<S, SimpleQuery, C> {
474 pub fn offer(self, item: SessionItem) -> Result<SimpleTransition<S, C>, (Self, SessionItem)> {
480 match (
481 project_session_item(frontend::RuntimeState::Simple, &item),
482 item,
483 ) {
484 (
485 Some(frontend::Event::CopyIn),
486 SessionItem::Message(BackendMessage::CopyInResponse(response)),
487 ) => Ok(SimpleTransition::CopyIn(self.transition(), response)),
488 (
489 Some(frontend::Event::CopyOut),
490 SessionItem::Message(BackendMessage::CopyOutResponse(response)),
491 ) => Ok(SimpleTransition::CopyOut(self.transition(), response)),
492 (
493 Some(frontend::Event::CopyBoth),
494 SessionItem::Message(BackendMessage::CopyBothResponse(response)),
495 ) => Ok(SimpleTransition::CopyBoth(self.transition(), response)),
496 (
497 Some(frontend::Event::Ready),
498 SessionItem::ReadyForQuery {
499 status,
500 parameters_changed,
501 },
502 ) => Ok(SimpleTransition::Ready(ready_state(
503 self,
504 status,
505 parameters_changed,
506 ))),
507 (
508 Some(frontend::Event::Error),
509 SessionItem::Message(BackendMessage::ErrorResponse(error)),
510 ) => Ok(SimpleTransition::Error(self.transition(), error)),
511 (
512 Some(frontend::Event::Continue),
513 item @ (SessionItem::CommandComplete { .. }
514 | SessionItem::Message(
515 BackendMessage::RowDescription(_)
516 | BackendMessage::DataRow(_)
517 | BackendMessage::EmptyQueryResponse,
518 )),
519 ) => Ok(SimpleTransition::Continue(self, item)),
520 (_, item) => Err((self, item)),
521 }
522 }
523}
524
525impl<S, C> Conn<S, CopyIn, C> {
526 pub fn push_copy_data(self, data: Bytes) -> (Self, Frame) {
535 (
536 self,
537 Frame {
538 tag: b'd',
539 body: data,
540 },
541 )
542 }
543
544 pub fn push_copy_done(self) -> (Conn<S, AwaitingReady, C>, Frame) {
546 (self.transition(), empty_frame(b'c'))
547 }
548
549 pub fn push_copy_fail(self, message: &[u8]) -> io::Result<(Conn<S, AwaitingReady, C>, Frame)> {
555 Ok((self.transition(), cstr_frame(b'f', message)?))
556 }
557
558 pub fn offer(self, item: SessionItem) -> Result<CopyInTransition<S, C>, (Self, SessionItem)> {
567 match (
568 project_session_item(frontend::RuntimeState::CopyIn, &item),
569 item,
570 ) {
571 (
572 Some(frontend::Event::Error),
573 SessionItem::Message(BackendMessage::ErrorResponse(error)),
574 ) => Ok(CopyInTransition::Error(self.transition(), error)),
575 (_, item) => Err((self, item)),
576 }
577 }
578}
579
580impl<S, C> Conn<S, CopyBothClientDone, C> {
581 pub fn offer(
587 self,
588 item: SessionItem,
589 ) -> Result<CopyBothClientDoneReceive<S, C>, (Self, SessionItem)> {
590 match (
591 project_session_item(frontend::RuntimeState::CopyBothClientDone, &item),
592 item,
593 ) {
594 (
595 Some(frontend::Event::ReceiveCopyData),
596 SessionItem::Message(BackendMessage::CopyData(data)),
597 ) => Ok(CopyBothClientDoneReceive::Data(self, data)),
598 (
599 Some(frontend::Event::ReceiveCopyDone),
600 SessionItem::Message(BackendMessage::CopyDone),
601 ) => Ok(CopyBothClientDoneReceive::Done(self.transition())),
602 (
603 Some(frontend::Event::Error),
604 SessionItem::Message(BackendMessage::ErrorResponse(error)),
605 ) => Ok(CopyBothClientDoneReceive::Error(self.transition(), error)),
606 (_, item) => Err((self, item)),
607 }
608 }
609}
610
611impl<S, C> CopyBothClientDoneReceive<S, C> {
612 pub fn decode_replication(self) -> ReplicationClientDoneProjection<S, C> {
618 match self {
619 Self::Data(conn, data) => match BackendReplication::decode(data) {
620 Ok(message) => Ok(ReplicationClientDoneReceive::Message(conn, message)),
621 Err(error) => Err((conn, error)),
622 },
623 Self::Done(conn) => Ok(ReplicationClientDoneReceive::Done(conn)),
624 Self::Error(conn, error) => Ok(ReplicationClientDoneReceive::Error(conn, error)),
625 }
626 }
627}
628
629impl<S, C> Conn<S, CopyBothServerDone, C> {
630 pub fn push_copy_data(self, data: Bytes) -> (Self, Frame) {
632 (
633 self,
634 Frame {
635 tag: b'd',
636 body: data,
637 },
638 )
639 }
640
641 pub fn push_replication(self, message: &FrontendReplication) -> (Self, Frame) {
643 self.push_copy_data(message.encode())
644 }
645
646 pub fn push_copy_done(self) -> (Conn<S, AwaitingReady, C>, Frame) {
648 (self.transition(), empty_frame(b'c'))
649 }
650}
651
652impl<S, C> Conn<S, CopyOut, C> {
653 pub fn offer(self, item: SessionItem) -> Result<CopyOutTransition<S, C>, (Self, SessionItem)> {
659 match (
660 project_session_item(frontend::RuntimeState::CopyOut, &item),
661 item,
662 ) {
663 (
664 Some(frontend::Event::CopyData),
665 SessionItem::Message(BackendMessage::CopyData(data)),
666 ) => Ok(CopyOutTransition::Data(self, data)),
667 (Some(frontend::Event::CopyDone), SessionItem::Message(BackendMessage::CopyDone)) => {
668 Ok(CopyOutTransition::Done(self.transition()))
669 }
670 (
671 Some(frontend::Event::Error),
672 SessionItem::Message(BackendMessage::ErrorResponse(error)),
673 ) => Ok(CopyOutTransition::Error(self.transition(), error)),
674 (_, item) => Err((self, item)),
675 }
676 }
677}
678
679impl<S, C> Conn<S, CopyBoth, C> {
680 pub fn push_copy_data(self, data: Bytes) -> (Self, Frame) {
682 (
683 self,
684 Frame {
685 tag: b'd',
686 body: data,
687 },
688 )
689 }
690
691 pub fn push_replication(self, message: &FrontendReplication) -> (Self, Frame) {
693 self.push_copy_data(message.encode())
694 }
695
696 pub fn push_copy_done(self) -> (Conn<S, CopyBothClientDone, C>, Frame) {
698 (self.transition(), empty_frame(b'c'))
699 }
700
701 pub fn offer(self, item: SessionItem) -> Result<CopyBothReceive<S, C>, (Self, SessionItem)> {
707 match (
708 project_session_item(frontend::RuntimeState::CopyBoth, &item),
709 item,
710 ) {
711 (
712 Some(frontend::Event::ReceiveCopyData),
713 SessionItem::Message(BackendMessage::CopyData(data)),
714 ) => Ok(CopyBothReceive::Data(self, data)),
715 (
716 Some(frontend::Event::ReceiveCopyDone),
717 SessionItem::Message(BackendMessage::CopyDone),
718 ) => Ok(CopyBothReceive::Done(self.transition())),
719 (
720 Some(frontend::Event::Error),
721 SessionItem::Message(BackendMessage::ErrorResponse(error)),
722 ) => Ok(CopyBothReceive::Error(self.transition(), error)),
723 (_, item) => Err((self, item)),
724 }
725 }
726}
727
728impl<S, C> CopyBothReceive<S, C> {
729 pub fn decode_replication(self) -> ReplicationProjection<S, C> {
735 match self {
736 Self::Data(conn, data) => match BackendReplication::decode(data) {
737 Ok(message) => Ok(ReplicationReceive::Message(conn, message)),
738 Err(error) => Err((conn, error)),
739 },
740 Self::Done(conn) => Ok(ReplicationReceive::Done(conn)),
741 Self::Error(conn, error) => Ok(ReplicationReceive::Error(conn, error)),
742 }
743 }
744}
745
746impl<S, C> Conn<S, Draining, C> {
747 pub fn offer(self, item: SessionItem) -> DrainingTransition<S, C> {
749 match (
750 project_session_item(frontend::RuntimeState::Draining, &item),
751 item,
752 ) {
753 (
754 Some(frontend::Event::Ready),
755 SessionItem::ReadyForQuery {
756 status,
757 parameters_changed,
758 },
759 ) => DrainingTransition::Ready(ready_state(self, status, parameters_changed)),
760 (_, item) => DrainingTransition::Continue(self, item),
761 }
762 }
763}
764
765impl<S, C> Conn<S, AwaitingReady, C> {
766 pub fn offer(self, item: SessionItem) -> AwaitingReadyTransition<S, C> {
768 match (
769 project_session_item(frontend::RuntimeState::AwaitingReady, &item),
770 item,
771 ) {
772 (
773 Some(frontend::Event::Ready),
774 SessionItem::ReadyForQuery {
775 status,
776 parameters_changed,
777 },
778 ) => AwaitingReadyTransition::Ready(ready_state(self, status, parameters_changed)),
779 (
780 Some(frontend::Event::Error),
781 SessionItem::Message(BackendMessage::ErrorResponse(error)),
782 ) => AwaitingReadyTransition::Error(self.transition(), error),
783 (_, item) => AwaitingReadyTransition::Continue(self, item),
784 }
785 }
786}
787
788impl<S> Conn<S, Resetting, Dirty> {
789 #[must_use]
791 pub fn offer(self, item: SessionItem) -> ResettingTransition<S> {
792 match (
793 project_session_item(frontend::RuntimeState::Resetting, &item),
794 item,
795 ) {
796 (Some(frontend::Event::DiscardComplete), SessionItem::CommandComplete { tag, .. })
797 if tag == b"DISCARD ALL".as_slice() =>
798 {
799 ResettingTransition::Complete(self.transition())
800 }
801 (
802 Some(frontend::Event::Error),
803 SessionItem::Message(BackendMessage::ErrorResponse(error)),
804 ) => ResettingTransition::Error(self.transition(), error),
805 (_, item) => ResettingTransition::Continue(self, item),
806 }
807 }
808}
809
810impl<S> Conn<S, ResetComplete, Dirty> {
811 #[must_use]
813 pub fn offer(self, item: SessionItem) -> ResetCompleteTransition<S> {
814 match (
815 project_session_item(frontend::RuntimeState::ResetComplete, &item),
816 item,
817 ) {
818 (
819 Some(frontend::Event::ReadyClean),
820 SessionItem::ReadyForQuery {
821 status: TransactionStatus::Idle,
822 parameters_changed: false,
823 },
824 ) => ResetCompleteTransition::Ready(self.transition()),
825 (
826 Some(frontend::Event::ReadyClean | frontend::Event::ReadyDirty),
827 SessionItem::ReadyForQuery {
828 status,
829 parameters_changed,
830 },
831 ) => ResetCompleteTransition::Dirty {
832 conn: self.transition(),
833 status,
834 parameters_changed,
835 },
836 (
837 Some(frontend::Event::Error),
838 SessionItem::Message(BackendMessage::ErrorResponse(error)),
839 ) => ResetCompleteTransition::Error(self.transition(), error),
840 (_, item) => ResetCompleteTransition::Continue(self, item),
841 }
842 }
843}
844
845impl<S, P> Conn<S, P, Pristine> {
846 pub fn mark_dirty(self) -> Conn<S, P, Dirty> {
848 self.transition()
849 }
850}
851
852fn project_session_item(
853 state: frontend::RuntimeState,
854 item: &SessionItem,
855) -> Option<frontend::Event> {
856 match item {
857 SessionItem::Message(message) => frontend::project_external(state, message),
858 SessionItem::CommandComplete { tag, .. } => {
859 frontend::project_external(state, &BackendMessage::CommandComplete(tag.clone()))
860 }
861 SessionItem::ReadyForQuery { status, .. } => {
862 frontend::project_external(state, &BackendMessage::ReadyForQuery(*status))
863 }
864 }
865}
866
867fn cstr_frame(tag: u8, value: &[u8]) -> io::Result<Frame> {
868 if value.contains(&0) {
869 return Err(io::Error::new(
870 io::ErrorKind::InvalidInput,
871 "message string contains a NUL byte",
872 ));
873 }
874 let mut body = BytesMut::with_capacity(value.len() + 1);
875 body.extend_from_slice(value);
876 body.put_u8(0);
877 Ok(Frame {
878 tag,
879 body: body.freeze(),
880 })
881}
882
883fn empty_frame(tag: u8) -> Frame {
884 Frame {
885 tag,
886 body: Bytes::new(),
887 }
888}
889
890fn ready_state<S, P, C>(
891 conn: Conn<S, P, C>,
892 status: TransactionStatus,
893 parameters_changed: bool,
894) -> ReadyState<S, C> {
895 if status == TransactionStatus::Idle && !parameters_changed {
896 ReadyState::Clean(conn.transition())
897 } else {
898 ReadyState::Dirty {
899 conn: conn.transition(),
900 status,
901 parameters_changed,
902 }
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909
910 #[test]
911 fn extended_building_self_loops_then_syncs() {
912 let ready: Conn<(), Ready> = Conn::new(()).transition();
913 let building = ready.begin_extended();
914 let (building, _) = building
915 .push_parse(&Parse {
916 statement: Bytes::from_static(b"statement"),
917 query: Bytes::from_static(b"select $1"),
918 parameter_types: vec![23],
919 })
920 .expect("valid Parse");
921 let (bound, _) = building
922 .push_bind(&Bind {
923 portal: Bytes::from_static(b"portal"),
924 statement: Bytes::from_static(b"statement"),
925 parameter_formats: vec![0],
926 parameters: vec![Some(Bytes::from_static(b"42"))],
927 result_formats: vec![0],
928 })
929 .expect("valid Bind");
930 let (bound, _) = bound
931 .push_execute(&Execute {
932 portal: Bytes::from_static(b"portal"),
933 max_rows: 0,
934 })
935 .expect("valid Execute");
936 let (awaiting_ready, sync) = bound.push_sync();
937 assert_eq!(sync.tag, b'S');
938 awaiting_ready.into_transport();
939 }
940
941 #[test]
942 fn function_call_requires_result_then_ready() {
943 let ready: Conn<(), Ready> = Conn::new(()).transition();
944 let call = FunctionCall {
945 function_oid: 42,
946 argument_formats: vec![1],
947 arguments: vec![Some(Bytes::from_static(b"argument"))],
948 result_format: 1,
949 };
950 let (calling, frame) = ready.push_function_call(&call).unwrap();
951 assert_eq!(frame.tag, b'F');
952
953 let FunctionCallTransition::Response(awaiting_ready, result) = calling
954 .offer(BackendMessage::FunctionCallResponse(Bytes::from_static(
955 b"result",
956 )))
957 .unwrap()
958 else {
959 panic!("function result projected to the wrong branch")
960 };
961 assert_eq!(result, Bytes::from_static(b"result"));
962 let AwaitingReadyTransition::Ready(ReadyState::Clean(ready)) =
963 awaiting_ready.offer(SessionItem::ReadyForQuery {
964 status: TransactionStatus::Idle,
965 parameters_changed: false,
966 })
967 else {
968 panic!("function call did not return to ready")
969 };
970 ready.into_transport();
971 }
972
973 #[test]
974 fn ready_session_can_terminate_gracefully() {
975 let ready: Conn<(), Ready> = Conn::new(()).transition();
976 let (terminated, frame) = ready.push_terminate();
977 assert_eq!(frame.tag, b'X');
978 assert!(frame.body.is_empty());
979 terminated.into_transport();
980 }
981
982 #[test]
983 fn copy_both_waits_for_both_half_closes() {
984 use crate::grammar::frontend::{Event, RuntimeFsm, RuntimeState};
985
986 let mut client_first = RuntimeFsm::new();
987 client_first.step(Event::Query).unwrap();
988 client_first.step(Event::CopyBoth).unwrap();
989 let open: Conn<(), CopyBoth> = Conn::new(()).transition();
990 let (client_done, frame) = open.push_copy_done();
991 client_first.step(Event::SendCopyDone).unwrap();
992 assert_eq!(frame.tag, b'c');
993 let CopyBothClientDoneReceive::Data(client_done, data) = client_done
994 .offer(SessionItem::Message(BackendMessage::CopyData(
995 Bytes::from_static(b"after client close"),
996 )))
997 .unwrap()
998 else {
999 panic!("backend data projected to the wrong branch")
1000 };
1001 client_first.step(Event::ReceiveCopyData).unwrap();
1002 assert_eq!(data, Bytes::from_static(b"after client close"));
1003 let CopyBothClientDoneReceive::Done(awaiting) = client_done
1004 .offer(SessionItem::Message(BackendMessage::CopyDone))
1005 .unwrap()
1006 else {
1007 panic!("backend close projected to the wrong branch")
1008 };
1009 client_first.step(Event::ReceiveCopyDone).unwrap();
1010 assert_eq!(client_first.state(), RuntimeState::AwaitingReady);
1011 awaiting.into_transport();
1012
1013 let mut server_first = RuntimeFsm::new();
1014 server_first.step(Event::Query).unwrap();
1015 server_first.step(Event::CopyBoth).unwrap();
1016 let open: Conn<(), CopyBoth> = Conn::new(()).transition();
1017 let CopyBothReceive::Done(server_done) = open
1018 .offer(SessionItem::Message(BackendMessage::CopyDone))
1019 .unwrap()
1020 else {
1021 panic!("backend close projected to the wrong branch")
1022 };
1023 server_first.step(Event::ReceiveCopyDone).unwrap();
1024 let (server_done, data) =
1025 server_done.push_copy_data(Bytes::from_static(b"after server close"));
1026 server_first.step(Event::SendCopyData).unwrap();
1027 assert_eq!(data.tag, b'd');
1028 let (awaiting, done) = server_done.push_copy_done();
1029 server_first.step(Event::SendCopyDone).unwrap();
1030 assert_eq!(done.tag, b'c');
1031 assert_eq!(server_first.state(), RuntimeState::AwaitingReady);
1032 awaiting.into_transport();
1033 }
1034
1035 #[test]
1036 fn copy_in_can_receive_an_early_backend_error() {
1037 let copy: Conn<(), CopyIn> = Conn::new(()).transition();
1038 let error = DiagnosticResponse {
1039 fields: vec![crate::codec::DiagnosticField {
1040 code: b'M',
1041 value: Bytes::from_static(b"copy cancelled"),
1042 }],
1043 };
1044 let CopyInTransition::Error(draining, received) = copy
1045 .offer(SessionItem::Message(BackendMessage::ErrorResponse(
1046 error.clone(),
1047 )))
1048 .unwrap();
1049 assert_eq!(received, error);
1050
1051 let DrainingTransition::Ready(ReadyState::Clean(ready)) =
1052 draining.offer(SessionItem::ReadyForQuery {
1053 status: TransactionStatus::Idle,
1054 parameters_changed: false,
1055 })
1056 else {
1057 panic!("COPY failure did not drain to readiness")
1058 };
1059 ready.release();
1060 }
1061
1062 #[test]
1063 fn copy_both_projects_typed_replication_without_losing_connection() {
1064 let open: Conn<(), CopyBoth> = Conn::new(()).transition();
1065 let status = FrontendReplication::StandbyStatus {
1066 written: 10,
1067 flushed: 9,
1068 applied: 8,
1069 client_time: 7,
1070 reply_requested: true,
1071 };
1072 let (open, frame) = open.push_replication(&status);
1073 assert_eq!(frame.body, status.encode());
1074
1075 let keepalive = BackendReplication::PrimaryKeepalive {
1076 wal_end: 11,
1077 server_time: 12,
1078 reply_requested: true,
1079 };
1080 let receive = open
1081 .offer(SessionItem::Message(BackendMessage::CopyData(
1082 keepalive.encode(),
1083 )))
1084 .unwrap();
1085 let ReplicationReceive::Message(open, decoded) = receive.decode_replication().unwrap()
1086 else {
1087 panic!("keepalive projected to the wrong branch")
1088 };
1089 assert_eq!(decoded, keepalive);
1090 open.into_transport();
1091
1092 let open: Conn<(), CopyBoth> = Conn::new(()).transition();
1093 let receive = open
1094 .offer(SessionItem::Message(BackendMessage::CopyData(
1095 Bytes::from_static(b"kshort"),
1096 )))
1097 .unwrap();
1098 let (open, _) = receive.decode_replication().unwrap_err();
1099 open.into_transport();
1100 }
1101
1102 #[test]
1103 fn transaction_status_taints_ready_connection() {
1104 let query: Conn<(), SimpleQuery> = Conn::new(()).transition();
1105 let transition = query
1106 .offer(SessionItem::ReadyForQuery {
1107 status: TransactionStatus::InTransaction,
1108 parameters_changed: false,
1109 })
1110 .expect("ReadyForQuery is valid evidence");
1111 let SimpleTransition::Ready(ReadyState::Dirty {
1112 conn,
1113 status: TransactionStatus::InTransaction,
1114 parameters_changed: false,
1115 }) = transition
1116 else {
1117 panic!("transaction should taint readiness")
1118 };
1119 conn.into_transport();
1120 }
1121
1122 #[test]
1123 fn changed_parameters_taint_idle_connection() {
1124 let query: Conn<(), SimpleQuery> = Conn::new(()).transition();
1125 let transition = query
1126 .offer(SessionItem::ReadyForQuery {
1127 status: TransactionStatus::Idle,
1128 parameters_changed: true,
1129 })
1130 .expect("ReadyForQuery is valid evidence");
1131 let SimpleTransition::Ready(ReadyState::Dirty {
1132 conn,
1133 status: TransactionStatus::Idle,
1134 parameters_changed: true,
1135 }) = transition
1136 else {
1137 panic!("parameter change should taint readiness")
1138 };
1139 conn.into_transport();
1140 }
1141
1142 #[test]
1143 fn simple_queries_are_dirty_unless_inspection_proves_them_stateless() {
1144 fn require_dirty<S>(conn: Conn<S, Ready, Dirty>) {
1145 conn.into_transport();
1146 }
1147
1148 let ready: Conn<(), Ready> = Conn::new(()).transition();
1149 let (query, _) = ready.push_query(b"LISTEN events").unwrap();
1150 let SimpleTransition::Ready(ReadyState::Clean(dirty)) = query
1151 .offer(SessionItem::ReadyForQuery {
1152 status: TransactionStatus::Idle,
1153 parameters_changed: false,
1154 })
1155 .unwrap()
1156 else {
1157 panic!("idle readiness should preserve the query's dirty evidence")
1158 };
1159 require_dirty(dirty);
1160
1161 let ready: Conn<(), Ready> = Conn::new(()).transition();
1162 let (query, _) = ready.push_stateless_query(b"SELECT 1").unwrap();
1163 let SimpleTransition::Ready(ReadyState::Clean(pristine)) = query
1164 .offer(SessionItem::ReadyForQuery {
1165 status: TransactionStatus::Idle,
1166 parameters_changed: false,
1167 })
1168 .unwrap()
1169 else {
1170 panic!("stateless query should retain pristine evidence")
1171 };
1172 pristine.release();
1173 }
1174
1175 #[test]
1176 fn discard_all_evidence_recovers_pool_cleanliness() {
1177 let ready: Conn<(), Ready> = Conn::new(()).transition();
1178 let (resetting, frame) = ready.mark_dirty().begin_reset().unwrap();
1179 assert_eq!(frame.body, Bytes::from_static(b"ROLLBACK; DISCARD ALL\0"));
1180 let ResettingTransition::Continue(resetting, _) =
1181 resetting.offer(SessionItem::CommandComplete {
1182 tag: Bytes::from_static(b"ROLLBACK"),
1183 command: crate::demux::CommandIndex(0),
1184 notices: vec![],
1185 })
1186 else {
1187 panic!("ROLLBACK incorrectly completed reset")
1188 };
1189 let ResettingTransition::Complete(reset_complete) =
1190 resetting.offer(SessionItem::CommandComplete {
1191 tag: Bytes::from_static(b"DISCARD ALL"),
1192 command: crate::demux::CommandIndex(1),
1193 notices: vec![],
1194 })
1195 else {
1196 panic!("DISCARD ALL did not advance reset")
1197 };
1198 let ResetCompleteTransition::Ready(ready) =
1199 reset_complete.offer(SessionItem::ReadyForQuery {
1200 status: TransactionStatus::Idle,
1201 parameters_changed: false,
1202 })
1203 else {
1204 panic!("clean ready evidence did not restore pristine state")
1205 };
1206 ready.release();
1207 }
1208}