1use crate::database_client::DatabaseClient;
16use crate::model::CommitResponse;
17use crate::model::request_options::Priority;
18use crate::model::transaction_options::IsolationLevel;
19use crate::model::transaction_options::read_write::ReadLockMode;
20use crate::read_only_transaction::{BeginTransactionOption, ReadContextTransactionSelector};
21use crate::read_write_transaction::{ReadWriteTransaction, ReadWriteTransactionBuilder};
22use crate::transaction_retry_policy::{
23 BasicTransactionRetryPolicy, TransactionRetryPolicy, backoff_if_aborted, is_aborted,
24};
25use google_cloud_gax::backoff_policy::BackoffPolicyArg;
26use google_cloud_gax::retry_policy::RetryPolicyArg;
27
28use std::time::Duration as StdDuration;
29use tokio::time::Instant;
30use wkt::Duration;
31
32pub struct TransactionRunnerBuilder {
54 builder: ReadWriteTransactionBuilder,
55 retry_policy: Box<dyn TransactionRetryPolicy>,
56 timeout: Option<StdDuration>,
57 begin_gax_options: Option<crate::RequestOptions>,
58 commit_gax_options: Option<crate::RequestOptions>,
59}
60
61impl TransactionRunnerBuilder {
62 pub(crate) fn new(client: DatabaseClient) -> Self {
63 Self {
64 builder: ReadWriteTransactionBuilder::new(client),
65 retry_policy: Box::new(BasicTransactionRetryPolicy::default()),
66 timeout: None,
67 begin_gax_options: None,
68 commit_gax_options: None,
69 }
70 }
71
72 pub fn with_transaction_timeout(mut self, timeout: StdDuration) -> Self {
93 self.timeout = Some(timeout);
94 self
95 }
96
97 pub fn with_begin_attempt_timeout(mut self, timeout: StdDuration) -> Self {
115 self.begin_gax_options
116 .get_or_insert_with(crate::RequestOptions::default)
117 .set_attempt_timeout(timeout);
118 self
119 }
120
121 pub fn with_begin_retry_policy(mut self, policy: impl Into<RetryPolicyArg>) -> Self {
139 self.begin_gax_options
140 .get_or_insert_with(crate::RequestOptions::default)
141 .set_retry_policy(policy);
142 self
143 }
144
145 pub fn with_begin_backoff_policy(mut self, policy: impl Into<BackoffPolicyArg>) -> Self {
163 self.begin_gax_options
164 .get_or_insert_with(crate::RequestOptions::default)
165 .set_backoff_policy(policy);
166 self
167 }
168
169 pub fn with_commit_attempt_timeout(mut self, timeout: StdDuration) -> Self {
185 self.commit_gax_options
186 .get_or_insert_with(crate::RequestOptions::default)
187 .set_attempt_timeout(timeout);
188 self
189 }
190
191 pub fn with_commit_retry_policy(mut self, policy: impl Into<RetryPolicyArg>) -> Self {
207 self.commit_gax_options
208 .get_or_insert_with(crate::RequestOptions::default)
209 .set_retry_policy(policy);
210 self
211 }
212
213 pub fn with_commit_backoff_policy(mut self, policy: impl Into<BackoffPolicyArg>) -> Self {
229 self.commit_gax_options
230 .get_or_insert_with(crate::RequestOptions::default)
231 .set_backoff_policy(policy);
232 self
233 }
234
235 pub fn set_isolation_level(mut self, isolation_level: IsolationLevel) -> Self {
254 self.builder = self.builder.set_isolation_level(isolation_level);
255 self
256 }
257
258 pub fn set_read_lock_mode(mut self, read_lock_mode: ReadLockMode) -> Self {
277 self.builder = self.builder.set_read_lock_mode(read_lock_mode);
278 self
279 }
280
281 pub fn set_transaction_tag(mut self, tag: impl Into<String>) -> Self {
300 self.builder = self.builder.set_transaction_tag(tag);
301 self
302 }
303
304 pub fn with_begin_transaction_option(mut self, option: BeginTransactionOption) -> Self {
337 self.builder = self.builder.with_begin_transaction_option(option);
338 self
339 }
340
341 pub fn set_commit_priority(mut self, priority: Priority) -> Self {
358 self.builder = self.builder.set_commit_priority(priority);
359 self
360 }
361
362 pub fn set_max_commit_delay(mut self, delay: Duration) -> Self {
385 self.builder = self.builder.set_max_commit_delay(delay);
386 self
387 }
388
389 pub fn set_exclude_txn_from_change_streams(mut self, exclude: bool) -> Self {
412 self.builder = self.builder.set_exclude_txn_from_change_streams(exclude);
413 self
414 }
415
416 pub fn set_return_commit_stats(mut self, return_stats: bool) -> Self {
444 self.builder = self.builder.set_return_commit_stats(return_stats);
445 self
446 }
447
448 pub fn with_retry_policy<P: TransactionRetryPolicy + 'static>(mut self, policy: P) -> Self {
471 self.retry_policy = Box::new(policy);
472 self
473 }
474
475 pub async fn build(self) -> crate::Result<TransactionRunner> {
494 Ok(TransactionRunner {
495 builder: self
496 .builder
497 .with_begin_transaction_request_options(self.begin_gax_options)
498 .with_commit_request_options(self.commit_gax_options),
499 retry_policy: self.retry_policy,
500 timeout: self.timeout,
501 })
502 }
503}
504
505#[derive(Debug)]
507#[non_exhaustive]
508pub struct TransactionResult<T> {
509 pub result: T,
511 pub commit_response: CommitResponse,
513}
514
515pub struct TransactionRunner {
517 builder: ReadWriteTransactionBuilder,
518 retry_policy: Box<dyn TransactionRetryPolicy>,
519 timeout: Option<StdDuration>,
520}
521
522impl TransactionRunner {
523 pub async fn run<T, F>(mut self, mut work: F) -> crate::Result<TransactionResult<T>>
549 where
550 F: std::ops::AsyncFnMut(ReadWriteTransaction) -> crate::Result<T>,
551 {
552 let start_time = Instant::now();
553 let mut attempts: u32 = 0;
554 let backoff = crate::transaction_retry_policy::default_retry_backoff();
555 let deadline = self.timeout.map(|t| start_time + t);
556
557 let mut force_explicit_begin = false;
558 loop {
559 attempts += 1;
560
561 let mut current_tx_id = None;
562 let attempt_result = async {
563 let mut builder = self.builder.clone();
564 if force_explicit_begin {
565 builder = builder
566 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin);
567 }
568 let transaction = builder.build(deadline).await.map_err(|e| (e, None))?;
569 let selector = transaction.context.transaction_selector.clone();
570
571 let result = match work(transaction.clone()).await {
572 Ok(res) => res,
573 Err(e) => {
574 let id = selector.get_id_no_wait().ok().flatten();
580 if !is_aborted(&e) {
582 let _ = transaction.rollback().await;
583 }
584 current_tx_id = id;
585 return Err((e, Some(selector)));
586 }
587 };
588
589 let commit_result = transaction.commit().await;
593 current_tx_id = selector.get_id_no_wait().ok().flatten();
594 let commit_response = match commit_result {
595 Ok(r) => r,
596 Err(e) => return Err((e, Some(selector))),
597 };
598 Ok::<TransactionResult<T>, (crate::Error, Option<ReadContextTransactionSelector>)>(
599 TransactionResult {
600 result,
601 commit_response,
602 },
603 )
604 }
605 .await;
606
607 match attempt_result {
608 Ok(res) => return Ok(res),
609 Err((e, selector_opt)) => {
610 if is_aborted(&e) {
611 let current_tx_id = current_tx_id.clone();
612 self.builder = self.builder.set_previous_transaction_id(current_tx_id);
613 force_explicit_begin = selector_opt
614 .as_ref()
615 .is_some_and(|s| s.is_first_statement_failed());
616 }
617
618 backoff_if_aborted(
619 e,
620 attempts,
621 start_time.elapsed(),
622 self.retry_policy.as_ref(),
623 &backoff,
624 self.builder.client.is_emulator(),
625 )
626 .await?;
627 }
628 }
629 }
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636 use crate::mutation::Mutation;
637 use crate::read_only_transaction::tests::{create_session_mock, setup_db_client};
638 use crate::transaction_retry_policy::tests::create_aborted_status;
639 use gaxi::grpc::tonic;
640 use google_cloud_gax::exponential_backoff::ExponentialBackoff;
641 use google_cloud_gax::retry_policy::NeverRetry;
642 use google_cloud_test_macros::tokio_test_no_panics;
643 use prost_types::value::Kind;
644 use spanner_grpc_mock::google::spanner::v1;
645 use spanner_grpc_mock::google::spanner::v1::CommitResponse;
646 use spanner_grpc_mock::google::spanner::v1::commit_request::Transaction as CommitTransaction;
647 use spanner_grpc_mock::google::spanner::v1::commit_response::CommitStats;
648 use spanner_grpc_mock::google::spanner::v1::mutation::Operation;
649 use spanner_grpc_mock::google::spanner::v1::transaction_options::Mode;
650 use spanner_grpc_mock::google::spanner::v1::transaction_selector::Selector as ProtoSelector;
651 use std::sync::Mutex;
652 use std::sync::mpsc::channel as std_channel;
653 use std::time::Duration as StdDuration;
654 use std::time::Duration as StdTimeDuration;
655 use tokio::sync::oneshot::channel as oneshot_channel;
656
657 fn expect_begin_transaction(
658 mock: &mut spanner_grpc_mock::MockSpanner,
659 times: usize,
660 transaction_id: Vec<u8>,
661 ) {
662 mock.expect_begin_transaction()
663 .times(times)
664 .returning(move |req| {
665 let req = req.into_inner();
666 assert_eq!(
667 req.session,
668 "projects/p/instances/i/databases/d/sessions/123"
669 );
670 Ok(tonic::Response::new(v1::Transaction {
671 id: transaction_id.clone(),
672 ..Default::default()
673 }))
674 });
675 }
676
677 async fn execute_test_runner(
678 mock: spanner_grpc_mock::MockSpanner,
679 begin_transaction_option: BeginTransactionOption,
680 ) -> Result<i64, crate::Error> {
681 let (db_client, server) = setup_db_client(mock).await;
682 let runner = TransactionRunnerBuilder::new(db_client)
683 .with_begin_transaction_option(begin_transaction_option)
684 .build()
685 .await
686 .unwrap();
687 tokio::select! {
688 res = runner.run(async |tx| {
689 let count = tx.execute_update("UPDATE Users SET active = true").await?;
690 Ok(count)
691 }) => res.map(|r| r.result),
692 err = server => panic!("Mock server panicked or terminated unexpectedly: {:?}", err),
693 }
694 }
695
696 fn commit_response() -> Result<tonic::Response<v1::CommitResponse>, tonic::Status> {
697 Ok(tonic::Response::new(v1::CommitResponse {
698 commit_timestamp: Some(prost_types::Timestamp {
699 seconds: 123456789,
700 nanos: 0,
701 }),
702 ..Default::default()
703 }))
704 }
705
706 fn row_count_exact_response(
707 count: i64,
708 ) -> Result<tonic::Response<v1::ResultSet>, tonic::Status> {
709 Ok(tonic::Response::new(v1::ResultSet {
710 stats: Some(v1::ResultSetStats {
711 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(count)),
712 ..Default::default()
713 }),
714 ..Default::default()
715 }))
716 }
717
718 #[test]
719 fn auto_traits() {
720 static_assertions::assert_impl_all!(TransactionRunnerBuilder: Send, Sync);
721 static_assertions::assert_impl_all!(TransactionRunner: Send, Sync);
722 }
723
724 #[tokio_test_no_panics]
725 async fn execute_run_success_explicit() {
726 run_success(BeginTransactionOption::ExplicitBegin).await;
727 }
728
729 #[tokio_test_no_panics]
730 async fn execute_run_success_inline() {
731 run_success(BeginTransactionOption::InlineBegin).await;
732 }
733
734 async fn run_success(begin_transaction_option: BeginTransactionOption) {
735 let mut mock = create_session_mock();
736
737 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
738 expect_begin_transaction(&mut mock, 1, vec![1, 2, 3]);
739 }
740
741 mock.expect_execute_sql().once().returning(move |req| {
742 let req = req.into_inner();
743 assert_eq!(req.sql, "UPDATE Users SET active = true");
744 assert_eq!(req.seqno, 1);
745
746 if begin_transaction_option == BeginTransactionOption::InlineBegin {
747 let transaction = req
748 .transaction
749 .as_ref()
750 .expect("transaction options required for inline begin");
751 let selector = transaction.selector.as_ref().expect("selector required");
752 assert!(matches!(
753 selector,
754 v1::transaction_selector::Selector::Begin(_)
755 ));
756 }
757
758 let mut metadata = v1::ResultSetMetadata {
759 ..Default::default()
760 };
761 if begin_transaction_option == BeginTransactionOption::InlineBegin {
762 metadata.transaction = Some(v1::Transaction {
763 id: vec![1, 2, 3],
764 ..Default::default()
765 });
766 }
767
768 Ok(tonic::Response::new(v1::ResultSet {
769 metadata: Some(metadata),
770 stats: Some(v1::ResultSetStats {
771 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
772 ..Default::default()
773 }),
774 ..Default::default()
775 }))
776 });
777
778 mock.expect_commit().once().returning(|req| {
779 let req = req.into_inner();
780 assert_eq!(
781 req.transaction,
782 Some(v1::commit_request::Transaction::TransactionId(vec![
783 1, 2, 3
784 ]))
785 );
786 commit_response()
787 });
788
789 let res = execute_test_runner(mock, begin_transaction_option)
790 .await
791 .unwrap();
792 assert_eq!(res, 1);
793 }
794
795 #[tokio_test_no_panics]
796 async fn execute_run_success_with_commit_stats_explicit() {
797 run_success_with_commit_stats(BeginTransactionOption::ExplicitBegin).await;
798 }
799
800 #[tokio_test_no_panics]
801 async fn execute_run_success_with_commit_stats_inline() {
802 run_success_with_commit_stats(BeginTransactionOption::InlineBegin).await;
803 }
804
805 async fn run_success_with_commit_stats(begin_transaction_option: BeginTransactionOption) {
806 let mut mock = create_session_mock();
807
808 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
809 expect_begin_transaction(&mut mock, 1, vec![1, 2, 3]);
810 }
811
812 mock.expect_execute_sql().once().returning(move |req| {
813 let req = req.into_inner();
814 assert_eq!(req.sql, "UPDATE Users SET active = true");
815
816 if begin_transaction_option == BeginTransactionOption::InlineBegin {
817 let transaction = req
818 .transaction
819 .as_ref()
820 .expect("transaction options required for inline begin");
821 let selector = transaction.selector.as_ref().expect("selector required");
822 assert!(matches!(
823 selector,
824 v1::transaction_selector::Selector::Begin(_)
825 ));
826 }
827
828 let mut metadata = v1::ResultSetMetadata {
829 ..Default::default()
830 };
831 if begin_transaction_option == BeginTransactionOption::InlineBegin {
832 metadata.transaction = Some(v1::Transaction {
833 id: vec![1, 2, 3],
834 ..Default::default()
835 });
836 }
837
838 Ok(tonic::Response::new(v1::ResultSet {
839 metadata: Some(metadata),
840 stats: Some(v1::ResultSetStats {
841 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
842 ..Default::default()
843 }),
844 ..Default::default()
845 }))
846 });
847
848 mock.expect_commit().once().returning(|req| {
849 let req = req.into_inner();
850 assert!(req.return_commit_stats);
851 Ok(tonic::Response::new(CommitResponse {
852 commit_timestamp: Some(prost_types::Timestamp {
853 seconds: 123456789,
854 nanos: 0,
855 }),
856 commit_stats: Some(CommitStats { mutation_count: 5 }),
857 ..Default::default()
858 }))
859 });
860
861 let (db_client, _server) = setup_db_client(mock).await;
862 let runner = TransactionRunnerBuilder::new(db_client)
863 .set_return_commit_stats(true)
864 .with_begin_transaction_option(begin_transaction_option)
865 .build()
866 .await
867 .unwrap();
868
869 let res = runner
870 .run(async |tx| {
871 let count = tx.execute_update("UPDATE Users SET active = true").await?;
872 Ok(count)
873 })
874 .await
875 .unwrap();
876
877 assert_eq!(res.result, 1);
878 assert!(res.commit_response.commit_stats.is_some());
879 assert_eq!(
880 res.commit_response
881 .commit_stats
882 .expect("Commit stats should be present")
883 .mutation_count,
884 5
885 );
886 }
887
888 #[tokio_test_no_panics]
889 async fn execute_run_with_aborted_retry_explicit() -> anyhow::Result<()> {
890 run_with_aborted_retry(BeginTransactionOption::ExplicitBegin).await
891 }
892
893 #[tokio_test_no_panics]
894 async fn execute_run_with_aborted_retry_inline() -> anyhow::Result<()> {
895 run_with_aborted_retry(BeginTransactionOption::InlineBegin).await
896 }
897
898 async fn run_with_aborted_retry(
899 begin_transaction_option: BeginTransactionOption,
900 ) -> anyhow::Result<()> {
901 let mut mock = create_session_mock();
902 let mut seq = mockall::Sequence::new();
903
904 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
905 mock.expect_begin_transaction()
906 .once()
907 .in_sequence(&mut seq)
908 .returning(move |req| {
909 let req = req.into_inner();
910 assert_eq!(
911 req.session,
912 "projects/p/instances/i/databases/d/sessions/123"
913 );
914 Ok(tonic::Response::new(v1::Transaction {
915 id: vec![9, 9, 9],
916 ..Default::default()
917 }))
918 });
919 }
920
921 if begin_transaction_option == BeginTransactionOption::InlineBegin {
922 mock.expect_execute_sql()
924 .once()
925 .in_sequence(&mut seq)
926 .returning(move |req| {
927 let req = req.into_inner();
928 let transaction = req
929 .transaction
930 .as_ref()
931 .expect("transaction options required for inline begin");
932 let selector = transaction.selector.as_ref().expect("selector required");
933 assert!(matches!(
934 selector,
935 v1::transaction_selector::Selector::Begin(_)
936 ));
937
938 Err(create_aborted_status(std::time::Duration::from_nanos(1)))
939 });
940 } else {
941 mock.expect_execute_sql()
942 .once()
943 .in_sequence(&mut seq)
944 .returning(move |_req| {
945 Err(create_aborted_status(std::time::Duration::from_nanos(1)))
946 });
947 }
948
949 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
950 mock.expect_begin_transaction()
951 .once()
952 .in_sequence(&mut seq)
953 .returning(move |req| {
954 let req = req.into_inner();
955 assert_eq!(req.session, "projects/p/instances/i/databases/d/sessions/123");
956
957 let options = req.options.as_ref().expect("options required on retry");
958 let read_write = options.mode.as_ref().expect("mode required on retry");
959 match read_write {
960 Mode::ReadWrite(rw) => {
961 assert_eq!(rw.multiplexed_session_previous_transaction_id, vec![9, 9, 9], "previous_transaction_id should be set to the ID of the aborted transaction");
962 }
963 _ => panic!("Expected ReadWrite mode"),
964 }
965
966 Ok(tonic::Response::new(v1::Transaction {
967 id: vec![8, 8, 8],
968 ..Default::default()
969 }))
970 });
971 }
972
973 mock.expect_execute_sql()
975 .once()
976 .in_sequence(&mut seq)
977 .returning(move |req| {
978 if begin_transaction_option == BeginTransactionOption::InlineBegin {
979 let req = req.into_inner();
980 let transaction = req
981 .transaction
982 .as_ref()
983 .expect("transaction options required for inline begin");
984 let selector = transaction.selector.as_ref().expect("selector required");
985 assert!(matches!(
986 selector,
987 v1::transaction_selector::Selector::Begin(_)
988 ));
989
990 let options = match selector {
991 v1::transaction_selector::Selector::Begin(o) => o,
992 _ => panic!("Expected Begin"),
993 };
994 let read_write = options.mode.as_ref().expect("mode required");
995 match read_write {
996 Mode::ReadWrite(rw) => {
997 assert!(rw.multiplexed_session_previous_transaction_id.is_empty());
998 }
999 _ => panic!("Expected ReadWrite"),
1000 }
1001 }
1002
1003 let mut metadata = v1::ResultSetMetadata {
1004 ..Default::default()
1005 };
1006 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1007 metadata.transaction = Some(v1::Transaction {
1008 id: vec![8, 8, 8],
1009 ..Default::default()
1010 });
1011 }
1012
1013 Ok(tonic::Response::new(v1::ResultSet {
1014 metadata: Some(metadata),
1015 stats: Some(v1::ResultSetStats {
1016 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(5)),
1017 ..Default::default()
1018 }),
1019 ..Default::default()
1020 }))
1021 });
1022
1023 mock.expect_commit()
1024 .once()
1025 .returning(|_req| commit_response());
1026
1027 let res = execute_test_runner(mock, begin_transaction_option)
1028 .await
1029 .expect("runner should succeed");
1030 assert_eq!(res, 5);
1031 Ok(())
1032 }
1033
1034 #[tokio_test_no_panics]
1035 async fn execute_run_query_stream_with_aborted_retry_explicit() -> anyhow::Result<()> {
1036 run_query_stream_with_aborted_retry(BeginTransactionOption::ExplicitBegin).await
1037 }
1038
1039 #[tokio_test_no_panics]
1040 async fn execute_run_query_stream_with_aborted_retry_inline() -> anyhow::Result<()> {
1041 run_query_stream_with_aborted_retry(BeginTransactionOption::InlineBegin).await
1042 }
1043
1044 async fn run_query_stream_with_aborted_retry(
1045 begin_transaction_option: BeginTransactionOption,
1046 ) -> anyhow::Result<()> {
1047 let mut mock = create_session_mock();
1048 let mut seq = mockall::Sequence::new();
1049
1050 let tx_id_1 = vec![9, 9, 9];
1051 let tx_id_2 = vec![8, 8, 8];
1052
1053 let tx_id_1_c1 = tx_id_1.clone();
1054 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1055 mock.expect_begin_transaction()
1056 .once()
1057 .in_sequence(&mut seq)
1058 .returning(move |_| {
1059 Ok(tonic::Response::new(v1::Transaction {
1060 id: tx_id_1_c1.clone(),
1061 ..Default::default()
1062 }))
1063 });
1064 }
1065
1066 let tx_id_1_c2 = tx_id_1.clone();
1067 mock.expect_execute_streaming_sql()
1068 .once()
1069 .in_sequence(&mut seq)
1070 .returning(move |req| {
1071 let req = req.into_inner();
1072 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1073 let transaction = req
1074 .transaction
1075 .as_ref()
1076 .expect("transaction options required for inline begin");
1077 let selector = transaction.selector.as_ref().expect("selector required");
1078 assert!(matches!(
1079 selector,
1080 v1::transaction_selector::Selector::Begin(_)
1081 ));
1082 }
1083
1084 let mut rs = v1::PartialResultSet {
1085 metadata: Some(v1::ResultSetMetadata {
1086 row_type: Some(v1::StructType {
1087 fields: vec![Default::default()],
1088 }),
1089 ..Default::default()
1090 }),
1091 values: vec![prost_types::Value {
1092 kind: Some(prost_types::value::Kind::StringValue("1".to_string())),
1093 }],
1094 resume_token: b"token1".to_vec(),
1095 ..Default::default()
1096 };
1097
1098 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1099 rs.metadata.as_mut().unwrap().transaction = Some(v1::Transaction {
1100 id: tx_id_1_c2.clone(),
1101 ..Default::default()
1102 });
1103 }
1104
1105 let (tx, rx) = tokio::sync::mpsc::channel(2);
1106 tx.try_send(Ok(rs)).unwrap();
1107 tx.try_send(Err(tonic::Status::new(tonic::Code::Aborted, "aborted")))
1108 .unwrap();
1109 Ok(tonic::Response::from(rx))
1110 });
1111
1112 let tx_id_2_c1 = tx_id_2.clone();
1113 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1114 mock.expect_begin_transaction()
1115 .once()
1116 .in_sequence(&mut seq)
1117 .returning(move |req| {
1118 let req = req.into_inner();
1119 let options = req.options.as_ref().expect("options required on retry");
1120 let read_write = options.mode.as_ref().expect("mode required on retry");
1121 match read_write {
1122 Mode::ReadWrite(rw) => {
1123 assert_eq!(
1124 rw.multiplexed_session_previous_transaction_id,
1125 vec![9, 9, 9]
1126 );
1127 }
1128 _ => panic!("Expected ReadWrite mode"),
1129 }
1130
1131 Ok(tonic::Response::new(v1::Transaction {
1132 id: tx_id_2_c1.clone(),
1133 ..Default::default()
1134 }))
1135 });
1136 }
1137
1138 let tx_id_2_c2 = tx_id_2.clone();
1139 mock.expect_execute_streaming_sql()
1140 .once()
1141 .in_sequence(&mut seq)
1142 .returning(move |req| {
1143 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1144 let req = req.into_inner();
1145 let transaction = req
1146 .transaction
1147 .as_ref()
1148 .expect("transaction options required for inline begin");
1149 let selector = transaction.selector.as_ref().expect("selector required");
1150 assert!(matches!(
1151 selector,
1152 v1::transaction_selector::Selector::Begin(_)
1153 ));
1154
1155 let options = match selector {
1156 v1::transaction_selector::Selector::Begin(o) => o,
1157 _ => panic!("Expected Begin"),
1158 };
1159 let read_write = options.mode.as_ref().expect("mode required");
1160 match read_write {
1161 Mode::ReadWrite(rw) => {
1162 assert_eq!(
1163 rw.multiplexed_session_previous_transaction_id,
1164 vec![9, 9, 9]
1165 );
1166 }
1167 _ => panic!("Expected ReadWrite"),
1168 }
1169 }
1170
1171 let mut rs = v1::PartialResultSet {
1172 metadata: Some(v1::ResultSetMetadata {
1173 row_type: Some(v1::StructType {
1174 fields: vec![Default::default()],
1175 }),
1176 ..Default::default()
1177 }),
1178 values: vec![prost_types::Value {
1179 kind: Some(prost_types::value::Kind::StringValue("1".to_string())),
1180 }],
1181 last: true,
1182 ..Default::default()
1183 };
1184
1185 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1186 rs.metadata.as_mut().unwrap().transaction = Some(v1::Transaction {
1187 id: tx_id_2_c2.clone(),
1188 ..Default::default()
1189 });
1190 }
1191
1192 let (tx, rx) = tokio::sync::mpsc::channel(2);
1193 tx.try_send(Ok(rs)).unwrap();
1194 Ok(tonic::Response::from(rx))
1195 });
1196
1197 mock.expect_commit()
1198 .once()
1199 .returning(|_req| commit_response());
1200
1201 let (db_client, _server) = setup_db_client(mock).await;
1202 let runner = TransactionRunnerBuilder::new(db_client)
1203 .with_begin_transaction_option(begin_transaction_option)
1204 .build()
1205 .await?;
1206
1207 let mut attempt_counter = 0;
1208 let res = runner
1209 .run(async |tx| {
1210 attempt_counter += 1;
1211 let mut rs = tx.execute_query("SELECT 1").await?;
1212 let mut last_val = None;
1213 while let Some(row_res) = rs.next().await {
1214 let row = row_res?;
1215 last_val = Some(row.raw_values()[0].as_string().to_string());
1216 }
1217 Ok(last_val.unwrap())
1218 })
1219 .await?;
1220
1221 assert_eq!(res.result, "1");
1222 assert_eq!(attempt_counter, 2);
1223 Ok(())
1224 }
1225
1226 #[tokio_test_no_panics]
1227 async fn execute_run_with_non_aborted_error_explicit() {
1228 run_with_non_aborted_error(BeginTransactionOption::ExplicitBegin).await;
1229 }
1230
1231 #[tokio_test_no_panics]
1232 async fn execute_run_with_non_aborted_error_inline() {
1233 run_with_non_aborted_error(BeginTransactionOption::InlineBegin).await;
1234 }
1235
1236 async fn run_with_non_aborted_error(begin_transaction_option: BeginTransactionOption) {
1237 let mut mock = create_session_mock();
1238
1239 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1240 expect_begin_transaction(&mut mock, 1, vec![9, 9, 9]);
1241 }
1242
1243 mock.expect_execute_sql().once().returning(move |_req| {
1245 Err(tonic::Status::new(
1246 tonic::Code::PermissionDenied,
1247 "permission denied",
1248 ))
1249 });
1250
1251 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1252 expect_begin_transaction(&mut mock, 1, vec![9, 9, 9]);
1253 mock.expect_execute_sql().once().returning(move |_req| {
1254 Err(tonic::Status::new(
1255 tonic::Code::PermissionDenied,
1256 "permission denied",
1257 ))
1258 });
1259 }
1260
1261 mock.expect_rollback()
1263 .once()
1264 .returning(|_req| Ok(tonic::Response::new(())));
1265
1266 let res = execute_test_runner(mock, begin_transaction_option).await;
1267
1268 assert!(res.is_err());
1269 let err = res.unwrap_err();
1270 if let Some(status) = err.status() {
1271 assert_eq!(
1272 status.code,
1273 google_cloud_gax::error::rpc::Code::PermissionDenied
1274 );
1275 } else {
1276 panic!("Expected GRPC error");
1277 }
1278 }
1279
1280 #[tokio_test_no_panics]
1281 async fn execute_run_with_non_aborted_error_and_rollback_fails_explicit() {
1282 run_with_non_aborted_error_and_rollback_fails(BeginTransactionOption::ExplicitBegin).await;
1283 }
1284
1285 #[tokio_test_no_panics]
1286 async fn execute_run_with_non_aborted_error_and_rollback_fails_inline() {
1287 run_with_non_aborted_error_and_rollback_fails(BeginTransactionOption::InlineBegin).await;
1288 }
1289
1290 async fn run_with_non_aborted_error_and_rollback_fails(
1291 begin_transaction_option: BeginTransactionOption,
1292 ) {
1293 let mut mock = create_session_mock();
1294
1295 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1296 expect_begin_transaction(&mut mock, 1, vec![9, 9, 9]);
1297 }
1298
1299 mock.expect_execute_sql().once().returning(move |_req| {
1301 Err(tonic::Status::new(
1302 tonic::Code::PermissionDenied,
1303 "permission denied",
1304 ))
1305 });
1306
1307 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1308 expect_begin_transaction(&mut mock, 1, vec![9, 9, 9]);
1309 mock.expect_execute_sql().once().returning(move |_req| {
1310 Err(tonic::Status::new(
1311 tonic::Code::PermissionDenied,
1312 "permission denied",
1313 ))
1314 });
1315 }
1316
1317 mock.expect_rollback()
1319 .once()
1320 .returning(|_req| Err(tonic::Status::new(tonic::Code::Internal, "rollback failed")));
1321
1322 let res = execute_test_runner(mock, begin_transaction_option).await;
1323
1324 assert!(res.is_err());
1326 let err = res.unwrap_err();
1327 if let Some(status) = err.status() {
1328 assert_eq!(
1329 status.code,
1330 google_cloud_gax::error::rpc::Code::PermissionDenied
1331 );
1332 } else {
1333 panic!("Expected GRPC error");
1334 }
1335 }
1336
1337 #[tokio_test_no_panics]
1338 async fn execute_run_commit_aborted_retry_explicit() {
1339 run_commit_aborted_retry(BeginTransactionOption::ExplicitBegin).await;
1340 }
1341
1342 #[tokio_test_no_panics]
1343 async fn execute_run_commit_aborted_retry_inline() {
1344 run_commit_aborted_retry(BeginTransactionOption::InlineBegin).await;
1345 }
1346
1347 async fn run_commit_aborted_retry(begin_transaction_option: BeginTransactionOption) {
1348 let mut mock = create_session_mock();
1349
1350 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1351 expect_begin_transaction(&mut mock, 2, vec![9, 9, 9]);
1352 }
1353
1354 let mut attempt = 0;
1355 mock.expect_execute_sql().times(2).returning(move |req| {
1356 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1357 let req = req.into_inner();
1358 let transaction = req
1359 .transaction
1360 .as_ref()
1361 .expect("transaction options required for inline begin");
1362 let selector = transaction.selector.as_ref().expect("selector required");
1363 assert!(matches!(
1364 selector,
1365 v1::transaction_selector::Selector::Begin(_)
1366 ));
1367
1368 attempt += 1;
1369 if attempt == 2 {
1370 let options = match selector {
1371 v1::transaction_selector::Selector::Begin(o) => o,
1372 _ => panic!("Expected Begin"),
1373 };
1374 let read_write = options.mode.as_ref().expect("mode required");
1375 match read_write {
1376 Mode::ReadWrite(rw) => {
1377 assert_eq!(
1378 rw.multiplexed_session_previous_transaction_id,
1379 vec![9, 9, 9]
1380 );
1381 }
1382 _ => panic!("Expected ReadWrite"),
1383 }
1384 }
1385
1386 let mut metadata = v1::ResultSetMetadata {
1387 ..Default::default()
1388 };
1389 metadata.transaction = Some(v1::Transaction {
1390 id: vec![9, 9, 9],
1391 ..Default::default()
1392 });
1393
1394 return Ok(tonic::Response::new(v1::ResultSet {
1395 metadata: Some(metadata),
1396 stats: Some(v1::ResultSetStats {
1397 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(5)),
1398 ..Default::default()
1399 }),
1400 ..Default::default()
1401 }));
1402 }
1403 row_count_exact_response(5)
1404 });
1405
1406 let mut commit_attempt = 0;
1407 mock.expect_commit().times(2).returning(move |_req| {
1408 commit_attempt += 1;
1409 if commit_attempt == 1 {
1410 Err(create_aborted_status(std::time::Duration::from_nanos(1)))
1411 } else {
1412 commit_response()
1413 }
1414 });
1415
1416 let res = execute_test_runner(mock, begin_transaction_option)
1417 .await
1418 .unwrap();
1419 assert_eq!(res, 5);
1420 }
1421
1422 #[tokio_test_no_panics]
1423 async fn execute_run_begin_transaction_fails_explicit() {
1424 run_begin_transaction_fails(BeginTransactionOption::ExplicitBegin).await;
1425 }
1426
1427 #[tokio_test_no_panics]
1428 async fn execute_run_begin_transaction_fails_inline() {
1429 run_begin_transaction_fails(BeginTransactionOption::InlineBegin).await;
1430 }
1431
1432 async fn run_begin_transaction_fails(begin_transaction_option: BeginTransactionOption) {
1433 let mut mock = create_session_mock();
1434 let mut seq = mockall::Sequence::new();
1435
1436 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1437 mock.expect_begin_transaction()
1438 .once()
1439 .returning(|_req| Err(tonic::Status::new(tonic::Code::Internal, "internal error")));
1440 } else {
1441 mock.expect_execute_sql()
1442 .once()
1443 .in_sequence(&mut seq)
1444 .returning(move |req| {
1445 let req = req.into_inner();
1446 let transaction = req
1447 .transaction
1448 .as_ref()
1449 .expect("transaction options required for inline begin");
1450 let selector = transaction.selector.as_ref().expect("selector required");
1451 assert!(matches!(
1452 selector,
1453 v1::transaction_selector::Selector::Begin(_)
1454 ));
1455
1456 Err(tonic::Status::new(tonic::Code::Internal, "internal error"))
1457 });
1458
1459 mock.expect_begin_transaction()
1460 .once()
1461 .in_sequence(&mut seq)
1462 .returning(|_req| Err(tonic::Status::new(tonic::Code::Internal, "internal error")));
1463 }
1464
1465 let res = execute_test_runner(mock, begin_transaction_option).await;
1466
1467 assert!(res.is_err());
1468 let err = res.unwrap_err();
1469 if let Some(status) = err.status() {
1470 assert_eq!(status.code, google_cloud_gax::error::rpc::Code::Internal);
1471 } else {
1472 panic!("Expected GRPC error");
1473 }
1474 }
1475
1476 #[tokio_test_no_panics]
1477 async fn builder_options() {
1478 use crate::transaction_retry_policy::BasicTransactionRetryPolicy;
1479
1480 let mock = create_session_mock();
1481 let (db_client, _server) = setup_db_client(mock).await;
1482
1483 let retry_policy = BasicTransactionRetryPolicy::new()
1484 .with_max_attempts(1)
1485 .with_total_timeout(std::time::Duration::from_secs(10));
1486
1487 let _runner = TransactionRunnerBuilder::new(db_client)
1489 .set_isolation_level(IsolationLevel::Serializable)
1490 .set_read_lock_mode(ReadLockMode::Pessimistic)
1491 .with_retry_policy(retry_policy)
1492 .build()
1493 .await
1494 .unwrap();
1495 }
1496
1497 #[tokio_test_no_panics]
1498 async fn execute_run_batch_dml_aborted_retry_explicit() {
1499 run_batch_dml_aborted_retry(BeginTransactionOption::ExplicitBegin).await;
1500 }
1501
1502 #[tokio_test_no_panics]
1503 async fn execute_run_batch_dml_aborted_retry_inline() {
1504 run_batch_dml_aborted_retry(BeginTransactionOption::InlineBegin).await;
1505 }
1506
1507 async fn run_batch_dml_aborted_retry(begin_transaction_option: BeginTransactionOption) {
1508 use crate::batch_dml::BatchDml;
1509 use crate::statement::Statement;
1510 use gaxi::grpc::tonic::Code;
1511 use spanner_grpc_mock::google::rpc::Status;
1512 use spanner_grpc_mock::google::spanner::v1::result_set_stats::RowCount;
1513
1514 let mut mock = create_session_mock();
1515
1516 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1517 expect_begin_transaction(&mut mock, 2, vec![9, 9, 9]);
1518 }
1519
1520 let mut seq = mockall::Sequence::new();
1521 mock.expect_execute_batch_dml()
1522 .once()
1523 .in_sequence(&mut seq)
1524 .returning(move |req| {
1525 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1526 let req = req.into_inner();
1527 let selector = req
1528 .transaction
1529 .expect("missing transaction selector")
1530 .selector
1531 .expect("missing selector");
1532 assert!(matches!(
1533 selector,
1534 v1::transaction_selector::Selector::Begin(_)
1535 ));
1536 }
1537
1538 let status = Status {
1540 code: Code::Aborted as i32,
1541 message: "transaction aborted".to_string(),
1542 ..Default::default()
1543 };
1544
1545 let mut metadata = v1::ResultSetMetadata {
1546 ..Default::default()
1547 };
1548 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1549 metadata.transaction = Some(v1::Transaction {
1550 id: vec![9, 9, 9],
1551 ..Default::default()
1552 });
1553 }
1554
1555 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
1556 result_sets: vec![v1::ResultSet {
1557 metadata: Some(metadata),
1558 stats: Some(v1::ResultSetStats {
1559 row_count: Some(RowCount::RowCountExact(1)),
1560 ..Default::default()
1561 }),
1562 ..Default::default()
1563 }],
1564 status: Some(status),
1565 ..Default::default()
1566 }))
1567 });
1568 mock.expect_execute_batch_dml()
1569 .once()
1570 .in_sequence(&mut seq)
1571 .returning(move |req| {
1572 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1573 let req = req.into_inner();
1574 let selector = req
1575 .transaction
1576 .expect("missing transaction selector")
1577 .selector
1578 .expect("missing selector");
1579 assert!(matches!(
1580 selector,
1581 v1::transaction_selector::Selector::Begin(_)
1582 ));
1583 }
1584
1585 let mut metadata = v1::ResultSetMetadata {
1586 ..Default::default()
1587 };
1588 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1589 metadata.transaction = Some(v1::Transaction {
1590 id: vec![9, 9, 9],
1591 ..Default::default()
1592 });
1593 }
1594
1595 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
1597 result_sets: vec![v1::ResultSet {
1598 metadata: Some(metadata),
1599 stats: Some(v1::ResultSetStats {
1600 row_count: Some(RowCount::RowCountExact(5)),
1601 ..Default::default()
1602 }),
1603 ..Default::default()
1604 }],
1605 ..Default::default()
1606 }))
1607 });
1608
1609 mock.expect_commit()
1610 .once()
1611 .returning(move |_| commit_response());
1612
1613 let (db_client, _) = setup_db_client(mock).await;
1614 let runner = TransactionRunnerBuilder::new(db_client)
1615 .with_begin_transaction_option(begin_transaction_option)
1616 .build()
1617 .await
1618 .expect("failed to build TransactionRunner");
1619
1620 let mut attempt_counter = 0;
1621
1622 let res = runner
1624 .run(async |tx| {
1625 attempt_counter += 1;
1626 let stmt = Statement::builder("UPDATE t SET c = 1").build();
1627 let batch = BatchDml::builder().add_statement(stmt).build();
1628 let counts = tx.execute_batch_update(batch).await?;
1629 Ok(counts)
1630 })
1631 .await
1632 .expect("transaction failed");
1633
1634 assert_eq!(res.result, vec![5]);
1635 assert_eq!(attempt_counter, 2);
1636 }
1637
1638 #[tokio_test_no_panics]
1639 async fn execute_run_with_transaction_tag_explicit() -> anyhow::Result<()> {
1640 run_with_transaction_tag(BeginTransactionOption::ExplicitBegin).await
1641 }
1642
1643 #[tokio_test_no_panics]
1644 async fn execute_run_with_transaction_tag_inline() -> anyhow::Result<()> {
1645 run_with_transaction_tag(BeginTransactionOption::InlineBegin).await
1646 }
1647
1648 async fn run_with_transaction_tag(
1649 begin_transaction_option: BeginTransactionOption,
1650 ) -> anyhow::Result<()> {
1651 let mut mock = create_session_mock();
1652
1653 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1654 mock.expect_begin_transaction().once().returning(|req| {
1655 let req = req.into_inner();
1656 assert_eq!(
1658 req.request_options
1659 .expect("Missing request_options")
1660 .transaction_tag,
1661 "my-test-tag"
1662 );
1663
1664 Ok(tonic::Response::new(v1::Transaction {
1665 id: vec![9, 9, 9],
1666 ..Default::default()
1667 }))
1668 });
1669 }
1670
1671 mock.expect_execute_sql().once().returning(move |req| {
1672 let req = req.into_inner();
1673 assert_eq!(
1674 req.request_options
1675 .expect("Missing request_options")
1676 .transaction_tag,
1677 "my-test-tag"
1678 );
1679
1680 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1681 let transaction = req
1682 .transaction
1683 .as_ref()
1684 .expect("transaction options required for inline begin");
1685 let selector = transaction.selector.as_ref().expect("selector required");
1686 assert!(matches!(
1687 selector,
1688 v1::transaction_selector::Selector::Begin(_)
1689 ));
1690 }
1691
1692 let mut metadata = v1::ResultSetMetadata {
1693 ..Default::default()
1694 };
1695 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1696 metadata.transaction = Some(v1::Transaction {
1697 id: vec![9, 9, 9],
1698 ..Default::default()
1699 });
1700 }
1701
1702 Ok(tonic::Response::new(v1::ResultSet {
1703 metadata: Some(metadata),
1704 stats: Some(v1::ResultSetStats {
1705 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(5)),
1706 ..Default::default()
1707 }),
1708 ..Default::default()
1709 }))
1710 });
1711
1712 mock.expect_commit().once().returning(|req| {
1713 let req = req.into_inner();
1714 assert_eq!(
1715 req.request_options
1716 .expect("Missing request_options")
1717 .transaction_tag,
1718 "my-test-tag"
1719 );
1720 commit_response()
1721 });
1722
1723 let (db_client, _server) = setup_db_client(mock).await;
1724
1725 let runner = TransactionRunnerBuilder::new(db_client)
1726 .with_begin_transaction_option(begin_transaction_option)
1727 .set_transaction_tag("my-test-tag")
1728 .build()
1729 .await?;
1730
1731 let res = runner
1732 .run(async |tx| {
1733 let count = tx.execute_update("UPDATE Users SET active = true").await?;
1734 Ok(count)
1735 })
1736 .await?;
1737
1738 assert_eq!(res.result, 5);
1739
1740 Ok(())
1741 }
1742
1743 #[tokio_test_no_panics]
1744 async fn execute_run_with_exclude_txn_from_change_streams_explicit() -> anyhow::Result<()> {
1745 run_with_exclude_txn_from_change_streams(BeginTransactionOption::ExplicitBegin).await
1746 }
1747
1748 #[tokio_test_no_panics]
1749 async fn execute_run_with_exclude_txn_from_change_streams_inline() -> anyhow::Result<()> {
1750 run_with_exclude_txn_from_change_streams(BeginTransactionOption::InlineBegin).await
1751 }
1752
1753 async fn run_with_exclude_txn_from_change_streams(
1754 begin_transaction_option: BeginTransactionOption,
1755 ) -> anyhow::Result<()> {
1756 let mut mock = create_session_mock();
1757
1758 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1759 mock.expect_begin_transaction().once().returning(|req| {
1760 let req = req.into_inner();
1761 let options = req.options.expect("Missing transaction options");
1762 assert!(options.exclude_txn_from_change_streams);
1763
1764 Ok(tonic::Response::new(v1::Transaction {
1765 id: vec![9, 9, 9],
1766 ..Default::default()
1767 }))
1768 });
1769 }
1770
1771 mock.expect_execute_sql().once().returning(move |req| {
1772 let req = req.into_inner();
1773 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1774 let transaction = req
1775 .transaction
1776 .as_ref()
1777 .expect("transaction options required for inline begin");
1778 let selector = transaction.selector.as_ref().expect("selector required");
1779 assert!(matches!(
1780 selector,
1781 v1::transaction_selector::Selector::Begin(_)
1782 ));
1783 }
1784
1785 let mut metadata = v1::ResultSetMetadata {
1786 ..Default::default()
1787 };
1788 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1789 metadata.transaction = Some(v1::Transaction {
1790 id: vec![9, 9, 9],
1791 ..Default::default()
1792 });
1793 }
1794
1795 Ok(tonic::Response::new(v1::ResultSet {
1796 metadata: Some(metadata),
1797 stats: Some(v1::ResultSetStats {
1798 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(5)),
1799 ..Default::default()
1800 }),
1801 ..Default::default()
1802 }))
1803 });
1804
1805 mock.expect_commit()
1806 .once()
1807 .returning(|_req| commit_response());
1808
1809 let (db_client, _server) = setup_db_client(mock).await;
1810
1811 let runner = TransactionRunnerBuilder::new(db_client)
1812 .set_exclude_txn_from_change_streams(true)
1813 .with_begin_transaction_option(begin_transaction_option)
1814 .build()
1815 .await?;
1816
1817 let res = runner
1818 .run(async |tx| {
1819 let count = tx.execute_update("UPDATE Users SET active = true").await?;
1820 Ok(count)
1821 })
1822 .await?;
1823
1824 assert_eq!(res.result, 5);
1825
1826 Ok(())
1827 }
1828
1829 #[tokio_test_no_panics]
1830 async fn execute_run_with_max_commit_delay_explicit() -> anyhow::Result<()> {
1831 run_with_max_commit_delay(BeginTransactionOption::ExplicitBegin).await
1832 }
1833
1834 #[tokio_test_no_panics]
1835 async fn execute_run_with_max_commit_delay_inline() -> anyhow::Result<()> {
1836 run_with_max_commit_delay(BeginTransactionOption::InlineBegin).await
1837 }
1838
1839 async fn run_with_max_commit_delay(
1840 begin_transaction_option: BeginTransactionOption,
1841 ) -> anyhow::Result<()> {
1842 let mut mock = create_session_mock();
1843
1844 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1845 expect_begin_transaction(&mut mock, 1, vec![1, 2, 3]);
1846 }
1847
1848 mock.expect_execute_sql().once().returning(move |req| {
1849 let req = req.into_inner();
1850 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1851 let transaction = req
1852 .transaction
1853 .as_ref()
1854 .expect("transaction options required for inline begin");
1855 let selector = transaction.selector.as_ref().expect("selector required");
1856 assert!(matches!(
1857 selector,
1858 v1::transaction_selector::Selector::Begin(_)
1859 ));
1860 }
1861
1862 let mut metadata = v1::ResultSetMetadata {
1863 ..Default::default()
1864 };
1865 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1866 metadata.transaction = Some(v1::Transaction {
1867 id: vec![1, 2, 3],
1868 ..Default::default()
1869 });
1870 }
1871
1872 Ok(tonic::Response::new(v1::ResultSet {
1873 metadata: Some(metadata),
1874 stats: Some(v1::ResultSetStats {
1875 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1876 ..Default::default()
1877 }),
1878 ..Default::default()
1879 }))
1880 });
1881
1882 mock.expect_commit().once().returning(|req| {
1883 let req = req.into_inner();
1884 assert_eq!(
1885 req.max_commit_delay,
1886 Some(::prost_types::Duration {
1887 seconds: 0,
1888 nanos: 200_000_000, })
1890 );
1891 commit_response()
1892 });
1893
1894 let (db_client, _server) = setup_db_client(mock).await;
1895 let runner = TransactionRunnerBuilder::new(db_client)
1896 .set_max_commit_delay(Duration::try_from("0.2s").unwrap())
1897 .with_begin_transaction_option(begin_transaction_option)
1898 .build()
1899 .await?;
1900
1901 let res = runner
1902 .run(async |tx| {
1903 let count = tx.execute_update("UPDATE Users SET active = true").await?;
1904 Ok(count)
1905 })
1906 .await?;
1907 assert_eq!(res.result, 1);
1908 Ok(())
1909 }
1910
1911 #[tokio_test_no_panics]
1912 async fn execute_run_empty_closure_inline() {
1913 let mut mock = create_session_mock();
1914 expect_begin_transaction(&mut mock, 1, vec![1, 2, 3]);
1915 mock.expect_commit().once().returning(|req| {
1916 let req = req.into_inner();
1917 assert_eq!(
1918 req.transaction,
1919 Some(v1::commit_request::Transaction::TransactionId(vec![
1920 1, 2, 3
1921 ]))
1922 );
1923 commit_response()
1924 });
1925
1926 let (db_client, _server) = setup_db_client(mock).await;
1927
1928 let runner = TransactionRunnerBuilder::new(db_client)
1929 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1930 .build()
1931 .await
1932 .unwrap();
1933
1934 let res = runner.run(async |_tx| Ok(42)).await.unwrap();
1935 assert_eq!(res.result, 42);
1936 }
1937
1938 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1939 async fn execute_run_async_statement_still_starting() {
1940 let (tx_rpc, rx_rpc) = std_channel();
1941 let (tx_started, rx_started) = oneshot_channel();
1942 let tx_started_mutex = Mutex::new(Some(tx_started));
1943
1944 let mut mock = create_session_mock();
1945
1946 mock.expect_execute_sql().once().returning(move |_req| {
1947 if let Some(tx) = tx_started_mutex.lock().unwrap().take() {
1948 let _ = tx.send(());
1949 }
1950 rx_rpc.recv().unwrap();
1951 row_count_exact_response(1)
1952 });
1953
1954 let (db_client, _server) = setup_db_client(mock).await;
1955
1956 let runner = TransactionRunnerBuilder::new(db_client)
1957 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1958 .build()
1959 .await
1960 .unwrap();
1961
1962 let mut rx_started_opt = Some(rx_started);
1963 let res = runner
1964 .run(async |tx| {
1965 tokio::spawn(async move {
1966 let _ = tx.execute_update("UPDATE Users SET active = true").await;
1967 });
1968 if let Some(rx) = rx_started_opt.take() {
1969 rx.await.unwrap();
1970 }
1971 Ok(42)
1972 })
1973 .await;
1974
1975 tx_rpc.send(()).unwrap();
1976
1977 assert!(res.is_err());
1978 assert!(
1979 format!("{:?}", res.unwrap_err())
1980 .contains("asynchronous statement is still starting the transaction")
1981 );
1982 }
1983
1984 #[tokio_test_no_panics]
1985 async fn execute_run_with_mutations_happy_flow() {
1986 let mut mock = create_session_mock();
1987
1988 mock.expect_execute_sql().once().returning(move |req| {
1989 let req = req.into_inner();
1990 assert_eq!(req.sql, "UPDATE Users SET active = true");
1991 let transaction = req
1992 .transaction
1993 .as_ref()
1994 .expect("transaction options required for inline begin");
1995 let selector = transaction.selector.as_ref().expect("selector required");
1996 assert!(matches!(selector, ProtoSelector::Begin(_)));
1997
1998 Ok(tonic::Response::new(v1::ResultSet {
1999 metadata: Some(v1::ResultSetMetadata {
2000 transaction: Some(v1::Transaction {
2001 id: vec![1, 1, 1],
2002 ..Default::default()
2003 }),
2004 ..Default::default()
2005 }),
2006 stats: Some(v1::ResultSetStats {
2007 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2008 ..Default::default()
2009 }),
2010 ..Default::default()
2011 }))
2012 });
2013
2014 mock.expect_commit().once().returning(|req| {
2015 let req = req.into_inner();
2016 assert_eq!(
2017 req.transaction,
2018 Some(CommitTransaction::TransactionId(vec![1, 1, 1]))
2019 );
2020 assert_eq!(req.mutations.len(), 1);
2021 commit_response()
2022 });
2023
2024 let (db_client, _server) = setup_db_client(mock).await;
2025 let runner = TransactionRunnerBuilder::new(db_client)
2026 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2027 .build()
2028 .await
2029 .expect("Failed to build transaction runner");
2030
2031 let res = runner
2032 .run(async |tx| {
2033 let count = tx.execute_update("UPDATE Users SET active = true").await?;
2034 let mutation = Mutation::new_insert_builder("Audits")
2035 .set("AuditId")
2036 .to(&1)
2037 .build();
2038 tx.buffer([mutation])?;
2039 Ok(count)
2040 })
2041 .await
2042 .expect("Transaction runner failed");
2043
2044 assert_eq!(res.result, 1);
2045 }
2046
2047 #[tokio_test_no_panics]
2048 async fn execute_run_with_mutations_aborted_retry() {
2049 let mut mock = create_session_mock();
2050 let mut sequence = mockall::Sequence::new();
2051
2052 mock.expect_execute_sql()
2054 .once()
2055 .in_sequence(&mut sequence)
2056 .returning(move |_req| {
2057 Ok(tonic::Response::new(v1::ResultSet {
2058 metadata: Some(v1::ResultSetMetadata {
2059 transaction: Some(v1::Transaction {
2060 id: vec![10, 20, 30],
2061 ..Default::default()
2062 }),
2063 ..Default::default()
2064 }),
2065 stats: Some(v1::ResultSetStats {
2066 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2067 ..Default::default()
2068 }),
2069 ..Default::default()
2070 }))
2071 });
2072
2073 mock.expect_commit()
2075 .once()
2076 .in_sequence(&mut sequence)
2077 .returning(|req| {
2078 let req = req.into_inner();
2079 assert_eq!(req.mutations.len(), 1);
2080 let write = req.mutations[0]
2082 .operation
2083 .as_ref()
2084 .expect("Operation required");
2085 match write {
2086 Operation::Insert(w) => {
2087 assert_eq!(
2088 w.values[0].values[0].kind,
2089 Some(Kind::StringValue("100".to_string()))
2090 );
2091 }
2092 _ => panic!("Expected insert mutation"),
2093 }
2094 Err(create_aborted_status(StdTimeDuration::from_nanos(1)))
2095 });
2096
2097 mock.expect_execute_sql()
2099 .once()
2100 .in_sequence(&mut sequence)
2101 .returning(move |req| {
2102 let req = req.into_inner();
2103 let transaction = req
2104 .transaction
2105 .as_ref()
2106 .expect("transaction options required for inline begin");
2107 let selector = transaction.selector.as_ref().expect("selector required");
2108 let options = match selector {
2109 ProtoSelector::Begin(o) => o,
2110 _ => panic!("Expected Begin"),
2111 };
2112 let read_write = options.mode.as_ref().expect("mode required");
2113 match read_write {
2114 Mode::ReadWrite(rw) => {
2115 assert_eq!(
2116 rw.multiplexed_session_previous_transaction_id,
2117 vec![10, 20, 30]
2118 );
2119 }
2120 _ => panic!("Expected ReadWrite"),
2121 }
2122
2123 Ok(tonic::Response::new(v1::ResultSet {
2124 metadata: Some(v1::ResultSetMetadata {
2125 transaction: Some(v1::Transaction {
2126 id: vec![99, 99, 99],
2127 ..Default::default()
2128 }),
2129 ..Default::default()
2130 }),
2131 stats: Some(v1::ResultSetStats {
2132 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2133 ..Default::default()
2134 }),
2135 ..Default::default()
2136 }))
2137 });
2138
2139 mock.expect_commit()
2141 .once()
2142 .in_sequence(&mut sequence)
2143 .returning(|req| {
2144 let req = req.into_inner();
2145 assert_eq!(
2146 req.transaction,
2147 Some(CommitTransaction::TransactionId(vec![99, 99, 99]))
2148 );
2149 assert_eq!(req.mutations.len(), 1);
2150 let write = req.mutations[0]
2152 .operation
2153 .as_ref()
2154 .expect("Operation required");
2155 match write {
2156 Operation::Insert(w) => {
2157 assert_eq!(
2158 w.values[0].values[0].kind,
2159 Some(Kind::StringValue("200".to_string()))
2160 );
2161 }
2162 _ => panic!("Expected insert mutation"),
2163 }
2164 commit_response()
2165 });
2166
2167 let (db_client, _server) = setup_db_client(mock).await;
2168 let runner = TransactionRunnerBuilder::new(db_client)
2169 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2170 .build()
2171 .await
2172 .expect("Failed to build transaction runner");
2173
2174 let mut attempt = 0;
2175 let res = runner
2176 .run(async |tx| {
2177 attempt += 1;
2178 let count = tx.execute_update("UPDATE Users SET active = true").await?;
2179 let mutation_value = if attempt == 1 { 100 } else { 200 };
2180 let mutation = Mutation::new_insert_builder("Users")
2181 .set("UserId")
2182 .to(&mutation_value)
2183 .build();
2184 tx.buffer([mutation])?;
2185 Ok(count)
2186 })
2187 .await
2188 .expect("Transaction runner failed");
2189
2190 assert_eq!(res.result, 1);
2191 }
2192
2193 #[tokio_test_no_panics]
2194 async fn execute_run_mutation_only_explicit_begin_fallback() {
2195 let mut mock = create_session_mock();
2196
2197 mock.expect_begin_transaction().once().returning(|req| {
2199 let req = req.into_inner();
2200 assert_eq!(
2201 req.session,
2202 "projects/p/instances/i/databases/d/sessions/123"
2203 );
2204 Ok(tonic::Response::new(v1::Transaction {
2205 id: vec![77, 88, 99],
2206 ..Default::default()
2207 }))
2208 });
2209
2210 mock.expect_commit().once().returning(|req| {
2211 let req = req.into_inner();
2212 assert_eq!(
2213 req.transaction,
2214 Some(CommitTransaction::TransactionId(vec![77, 88, 99]))
2215 );
2216 assert_eq!(req.mutations.len(), 2);
2217 commit_response()
2218 });
2219
2220 let (db_client, _server) = setup_db_client(mock).await;
2221 let runner = TransactionRunnerBuilder::new(db_client)
2222 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2223 .build()
2224 .await
2225 .expect("Failed to build transaction runner");
2226
2227 let res = runner
2228 .run(async |tx| {
2229 let m1 = Mutation::new_insert_builder("Orders")
2230 .set("OrderId")
2231 .to(&1)
2232 .build();
2233 let m2 = Mutation::new_insert_builder("Orders")
2234 .set("OrderId")
2235 .to(&2)
2236 .build();
2237 tx.buffer([m1, m2])?;
2238 Ok(())
2239 })
2240 .await
2241 .expect("Transaction runner failed");
2242
2243 assert_eq!(
2244 res.commit_response
2245 .commit_timestamp
2246 .expect("Timestamp required")
2247 .seconds(),
2248 123456789
2249 );
2250 }
2251
2252 #[tokio_test_no_panics]
2253 async fn read_write_transaction_builder_sets_gax_options() -> anyhow::Result<()> {
2254 let mock = create_session_mock();
2255 let (db_client, _server) = setup_db_client(mock).await;
2256
2257 let runner = TransactionRunnerBuilder::new(db_client)
2258 .with_begin_attempt_timeout(StdDuration::from_secs(5))
2259 .with_begin_retry_policy(NeverRetry)
2260 .with_begin_backoff_policy(ExponentialBackoff::default())
2261 .with_commit_attempt_timeout(StdDuration::from_secs(10))
2262 .with_commit_retry_policy(NeverRetry)
2263 .with_commit_backoff_policy(ExponentialBackoff::default());
2264
2265 let begin_gax = runner
2266 .begin_gax_options
2267 .as_ref()
2268 .expect("begin_gax_options missing");
2269 assert_eq!(
2270 *begin_gax.attempt_timeout(),
2271 Some(StdDuration::from_secs(5))
2272 );
2273 assert!(begin_gax.retry_policy().is_some());
2274 assert!(begin_gax.backoff_policy().is_some());
2275
2276 let commit_gax = runner
2277 .commit_gax_options
2278 .as_ref()
2279 .expect("commit_gax_options missing");
2280 assert_eq!(
2281 *commit_gax.attempt_timeout(),
2282 Some(StdDuration::from_secs(10))
2283 );
2284 assert!(commit_gax.retry_policy().is_some());
2285 assert!(commit_gax.backoff_policy().is_some());
2286
2287 Ok(())
2288 }
2289}