1use crate::Error;
16use crate::RequestOptions;
17use crate::batch::BatchDml;
18use crate::client::amend_request_options_for_lar;
19use crate::database_client::DatabaseClient;
20use crate::error::internal_error;
21use crate::model::CommitRequest;
22use crate::model::CommitResponse;
23use crate::model::ExecuteBatchDmlRequest;
24use crate::model::ExecuteBatchDmlResponse;
25use crate::model::Mutation as ProtoMutation;
26use crate::model::ResultSet as ProtoResultSet;
27use crate::model::RollbackRequest;
28use crate::model::TransactionOptions;
29use crate::model::TransactionSelector;
30use crate::model::execute_batch_dml_request::Statement as ExecuteBatchDmlStatement;
31use crate::model::request_options::Priority;
32use crate::model::result_set_stats::RowCount;
33use crate::model::transaction_options::IsolationLevel;
34use crate::model::transaction_options::Mode;
35use crate::model::transaction_options::ReadWrite;
36use crate::model::transaction_options::read_write::ReadLockMode;
37use crate::model::transaction_selector::Selector;
38use crate::mutation::Mutation;
39use crate::precommit::PrecommitTokenTracker;
40use crate::read_only_transaction::{
41 BeginTransactionOption, ReadContext, ReadContextTransactionSelector, TransactionState,
42};
43use crate::result_set::ResultSet;
44use crate::retry_policy::SpannerRetryPolicy;
45use crate::statement::Statement;
46#[cfg(test)]
47use crate::transaction_retry_policy::is_aborted;
48use crate::write_only_transaction::create_commit_request;
49use google_cloud_gax::error::Error as GaxError;
50use google_cloud_gax::error::rpc::{Code, Status};
51use google_cloud_gax::options::RequestOptions as GaxRequestOptions;
52use google_cloud_gax::retry_policy::RetryPolicy;
53use google_cloud_gax::retry_result::RetryResult;
54use google_cloud_gax::retry_state::RetryState;
55use google_cloud_gax::throttle_result::ThrottleResult;
56use std::cmp::min;
57use std::mem::take;
58use std::sync::Arc;
59use std::sync::Mutex;
60use std::sync::atomic::{AtomicI64, Ordering};
61use std::time::Duration as StdDuration;
62use tokio::time::Instant;
63use wkt::Duration;
64
65#[derive(Clone, Debug)]
67pub(crate) struct ReadWriteTransactionBuilder {
68 pub(crate) client: DatabaseClient,
69 options: TransactionOptions,
70 transaction_tag: Option<String>,
71 max_commit_delay: Option<Duration>,
72 pub(crate) session_name: String,
73 return_commit_stats: bool,
74 commit_priority: Priority,
75 begin_transaction_option: BeginTransactionOption,
76 begin_gax_options: Option<crate::RequestOptions>,
77 commit_gax_options: Option<crate::RequestOptions>,
78}
79
80impl ReadWriteTransactionBuilder {
81 pub(crate) fn new(client: DatabaseClient) -> Self {
82 let session_name = client.session_name();
83 Self {
84 client,
85 options: TransactionOptions::default().set_read_write(ReadWrite::default()),
86 transaction_tag: None,
87 max_commit_delay: None,
88 session_name,
89 return_commit_stats: false,
90 commit_priority: Priority::Unspecified,
91 begin_transaction_option: BeginTransactionOption::InlineBegin,
92 begin_gax_options: None,
93 commit_gax_options: None,
94 }
95 }
96
97 pub(crate) fn set_isolation_level(mut self, isolation_level: IsolationLevel) -> Self {
98 self.options = self.options.set_isolation_level(isolation_level);
99 self
100 }
101
102 pub(crate) fn set_read_lock_mode(mut self, read_lock_mode: ReadLockMode) -> Self {
103 if let Some(Mode::ReadWrite(rw)) = self.options.mode.take() {
104 self.options = self
105 .options
106 .set_read_write(rw.set_read_lock_mode(read_lock_mode));
107 }
108 self
109 }
110
111 pub(crate) fn set_previous_transaction_id(mut self, id: Option<bytes::Bytes>) -> Self {
112 if let Some(id) = id
113 && let Some(Mode::ReadWrite(rw)) = self.options.mode.take()
114 {
115 self.options = self
116 .options
117 .set_read_write(rw.set_multiplexed_session_previous_transaction_id(id));
118 }
119 self
120 }
121
122 pub(crate) fn set_transaction_tag(mut self, tag: impl Into<String>) -> Self {
123 self.transaction_tag = Some(tag.into());
124 self
125 }
126
127 pub(crate) fn set_commit_priority(mut self, priority: Priority) -> Self {
128 self.commit_priority = priority;
129 self
130 }
131
132 pub(crate) fn set_max_commit_delay(mut self, delay: Duration) -> Self {
133 self.max_commit_delay = Some(delay);
134 self
135 }
136
137 pub(crate) fn set_exclude_txn_from_change_streams(mut self, exclude: bool) -> Self {
138 self.options = self.options.set_exclude_txn_from_change_streams(exclude);
139 self
140 }
141
142 pub(crate) fn set_return_commit_stats(mut self, return_stats: bool) -> Self {
143 self.return_commit_stats = return_stats;
144 self
145 }
146
147 pub fn with_begin_transaction_option(mut self, option: BeginTransactionOption) -> Self {
148 self.begin_transaction_option = option;
149 self
150 }
151
152 pub(crate) fn with_begin_transaction_request_options(
153 mut self,
154 options: Option<crate::RequestOptions>,
155 ) -> Self {
156 self.begin_gax_options = options;
157 self
158 }
159
160 pub(crate) fn with_commit_request_options(
161 mut self,
162 options: Option<crate::RequestOptions>,
163 ) -> Self {
164 self.commit_gax_options = options;
165 self
166 }
167
168 async fn begin(
169 &self,
170 session_name: String,
171 channel_hint: usize,
172 request_options: crate::RequestOptions,
173 ) -> crate::Result<ReadContextTransactionSelector> {
174 let response = crate::read_only_transaction::execute_begin_transaction(
175 &self.client,
176 session_name,
177 self.options.clone(),
178 self.transaction_tag.clone(),
179 channel_hint,
180 request_options,
181 None,
182 )
183 .await?;
184
185 Ok(ReadContextTransactionSelector::Fixed(
186 TransactionSelector::default().set_id(response.id),
187 None,
188 ))
189 }
190
191 pub(crate) async fn build(
192 &self,
193 deadline: Option<Instant>,
194 ) -> crate::Result<ReadWriteTransaction> {
195 let session_name = self.session_name.clone();
196 let channel_hint = self.client.spanner.next_channel_hint();
197 let transaction_selector = match self.begin_transaction_option {
198 BeginTransactionOption::ExplicitBegin => {
199 let mut options = self.begin_gax_options.clone().unwrap_or_default();
200 amend_gax_options(
201 self.client.leader_aware_routing_enabled,
202 deadline,
203 &mut options,
204 );
205
206 self.begin(session_name.clone(), channel_hint, options)
207 .await?
208 }
209 BeginTransactionOption::InlineBegin => ReadContextTransactionSelector::Lazy(Arc::new(
210 Mutex::new(TransactionState::NotStarted(self.options.clone())),
211 )),
212 };
213
214 Ok(ReadWriteTransaction {
215 context: ReadContext {
216 session_name,
217 client: self.client.clone(),
218 transaction_selector,
219 precommit_token_tracker: PrecommitTokenTracker::new(),
220 transaction_tag: self.transaction_tag.clone(),
221 channel_hint,
222 begin_transaction_request_options: None,
223 },
224 seqno: Arc::new(AtomicI64::new(1)),
225 max_commit_delay: self.max_commit_delay,
226 return_commit_stats: self.return_commit_stats,
227 deadline,
228 commit_priority: self.commit_priority.clone(),
229 mutations: Arc::new(Mutex::new(Vec::new())),
230 begin_gax_options: self.begin_gax_options.clone(),
231 commit_gax_options: self.commit_gax_options.clone(),
232 })
233 }
234}
235
236trait CheckServiceError {
237 fn check_service_error(&self) -> Option<Error>;
238}
239
240impl CheckServiceError for ProtoResultSet {
241 fn check_service_error(&self) -> Option<Error> {
242 None
243 }
244}
245
246impl CheckServiceError for ExecuteBatchDmlResponse {
251 fn check_service_error(&self) -> Option<Error> {
252 if self.result_sets.is_empty()
253 && let Some(status) = &self.status
254 && status.code != Code::Ok as i32
255 {
256 let rpc_status = Status::default()
257 .set_code(status.code)
258 .set_message(status.message.clone());
259 return Some(Error::service(rpc_status));
260 }
261 None
262 }
263}
264
265struct LazyTransactionStartGuard {
280 selector: ReadContextTransactionSelector,
281 active: bool,
282}
283
284impl LazyTransactionStartGuard {
285 fn new(selector: ReadContextTransactionSelector, active: bool) -> Self {
286 Self { selector, active }
287 }
288
289 fn disarm(&mut self) {
290 self.active = false;
291 }
292}
293
294impl Drop for LazyTransactionStartGuard {
295 fn drop(&mut self) {
296 if self.active {
297 self.selector.maybe_reset_starting();
298 }
299 }
300}
301
302macro_rules! execute_with_retry {
305 ($self:expr, $request:ident, $gax_options:expr, $rpc_method:ident, $extract_id:expr) => {{
306 let is_starting = matches!(
307 $request
308 .transaction
309 .as_ref()
310 .and_then(|t| t.selector.as_ref()),
311 Some(Selector::Begin(_))
312 );
313
314 let mut guard =
315 LazyTransactionStartGuard::new($self.context.transaction_selector.clone(), is_starting);
316
317 let response_result = $self
318 .context
319 .client
320 .spanner
321 .$rpc_method(
322 $request.clone(),
323 $gax_options.clone(),
324 $self.context.channel_hint,
325 )
326 .await;
327
328 let service_error = response_result
329 .as_ref()
330 .ok()
331 .and_then(|res| res.check_service_error());
332 let err_ref = response_result.as_ref().err().or(service_error.as_ref());
333
334 let response = match err_ref {
335 None => {
336 let response = response_result?;
337 if is_starting {
338 let id = $extract_id(&response).ok_or_else(|| {
339 internal_error("Transaction ID was not returned by Spanner")
340 })?;
341 $self.context.transaction_selector.update(id, None)?;
342 guard.disarm();
343 }
344 response
345 }
346 Some(error) => {
347 if is_starting {
348 $self.context.transaction_selector.set_failed(error);
349 }
350 response_result?
351 }
352 };
353
354 response
355 }};
356}
357
358#[derive(Clone, Debug)]
360pub struct ReadWriteTransaction {
361 pub(crate) context: ReadContext,
362 pub(crate) deadline: Option<Instant>,
363 seqno: Arc<AtomicI64>,
364 max_commit_delay: Option<Duration>,
365 return_commit_stats: bool,
366 commit_priority: Priority,
367 mutations: Arc<Mutex<Vec<ProtoMutation>>>,
368 begin_gax_options: Option<crate::RequestOptions>,
369 commit_gax_options: Option<crate::RequestOptions>,
370}
371
372impl ReadWriteTransaction {
373 pub fn buffer<I>(&self, mutations: I) -> crate::Result<()>
393 where
394 I: IntoIterator<Item = Mutation>,
395 {
396 let mut guard = self
397 .mutations
398 .lock()
399 .map_err(|_| crate::error::internal_error("mutations mutex poisoned"))?;
400 for mutation in mutations {
401 guard.push(mutation.build_proto());
402 }
403 Ok(())
404 }
405
406 pub async fn execute_query<T: Into<Statement>>(
408 &self,
409 statement: T,
410 ) -> crate::Result<ResultSet> {
411 let stmt = statement.into();
412 let mut gax_options = stmt.gax_options().clone();
413 self.amend_gax_options(&mut gax_options);
414 let stmt = stmt.with_gax_options(gax_options);
415 let seqno = self.seqno.fetch_add(1, Ordering::SeqCst);
416 self.context.execute_query(stmt, Some(seqno)).await
417 }
418
419 pub async fn execute_read<T: Into<crate::read::ReadRequest>>(
421 &self,
422 read: T,
423 ) -> crate::Result<ResultSet> {
424 let mut req = read.into();
425 self.amend_gax_options(&mut req.gax_options);
426 self.context.execute_read(req).await
427 }
428
429 pub async fn execute_update<T: Into<Statement>>(&self, statement: T) -> crate::Result<i64> {
431 let statement = statement.into();
432 let mut gax_options = statement.gax_options().clone();
433 self.amend_gax_options(&mut gax_options);
434 let seqno = self.seqno.fetch_add(1, Ordering::SeqCst);
435 let mut request = statement
436 .into_request()
437 .set_session(self.context.session_name.clone())
438 .set_transaction(self.context.transaction_selector.selector().await?)
439 .set_seqno(seqno);
440 request.request_options = self.context.amend_request_options(request.request_options);
441
442 let response = execute_with_retry!(
443 self,
444 request,
445 gax_options,
446 execute_sql,
447 |response: &crate::model::ResultSet| {
448 response
449 .metadata
450 .as_ref()
451 .and_then(|md| md.transaction.as_ref())
452 .map(|t| t.id.clone())
453 }
454 );
455
456 self.context
457 .precommit_token_tracker
458 .update(response.precommit_token);
459
460 let stats = response
461 .stats
462 .ok_or_else(|| internal_error("No stats returned"))?;
463 match stats.row_count {
464 Some(RowCount::RowCountExact(c)) => Ok(c),
465 _ => Err(internal_error(
466 "ExecuteSql returned an invalid or missing row count type for a read/write transaction",
467 )),
468 }
469 }
470
471 pub async fn execute_batch_update<T: Into<BatchDml>>(
539 &self,
540 batch: T,
541 ) -> crate::Result<Vec<i64>> {
542 let mut batch = batch.into();
543 self.amend_gax_options(&mut batch.gax_options);
544 let seqno = self.seqno.fetch_add(1, Ordering::SeqCst);
545
546 let statements: Vec<ExecuteBatchDmlStatement> = batch
547 .statements
548 .into_iter()
549 .map(|stmt: crate::statement::Statement| stmt.into_batch_statement())
550 .collect();
551
552 let request = ExecuteBatchDmlRequest::default()
553 .set_session(self.context.session_name.clone())
554 .set_transaction(self.context.transaction_selector.selector().await?)
555 .set_seqno(seqno)
556 .set_statements(statements)
557 .set_or_clear_request_options(self.context.amend_request_options(batch.request_options))
558 .set_last_statements(batch.last_statements);
559
560 let response = execute_with_retry!(
561 self,
562 request,
563 batch.gax_options,
564 execute_batch_dml,
565 |response: &crate::model::ExecuteBatchDmlResponse| {
566 response
567 .result_sets
568 .first()
569 .and_then(|rs| rs.metadata.as_ref())
570 .and_then(|md| md.transaction.as_ref())
571 .map(|t| t.id.clone())
572 }
573 );
574
575 self.context
576 .precommit_token_tracker
577 .update(response.precommit_token.clone());
578 crate::batch_dml::process_response(response)
579 }
580
581 pub(crate) async fn begin_explicitly_if_not_started(
582 &self,
583 is_stream_fallback: bool,
584 mutation_key: Option<crate::model::Mutation>,
585 ) -> crate::Result<bool> {
586 let mut begin_options = self.begin_gax_options.clone().unwrap_or_default();
587 self.amend_gax_options(&mut begin_options);
588 self.context
589 .begin_explicitly_if_not_started(begin_options, is_stream_fallback, mutation_key)
590 .await
591 }
592
593 pub(crate) fn is_starting(&self) -> crate::Result<bool> {
594 self.context.transaction_selector.is_starting()
595 }
596
597 fn commit_request_options(&self) -> Option<crate::model::RequestOptions> {
598 let mut options = self.context.amend_request_options(None);
599 if self.commit_priority != Priority::Unspecified {
600 options
601 .get_or_insert_with(crate::model::RequestOptions::default)
602 .priority = self.commit_priority.clone();
603 }
604 options
605 }
606
607 fn build_commit_request(
608 &self,
609 transaction_id: bytes::Bytes,
610 mutations: Vec<ProtoMutation>,
611 precommit_token: Option<crate::model::MultiplexedSessionPrecommitToken>,
612 ) -> CommitRequest {
613 create_commit_request(
614 self.context.session_name.clone(),
615 transaction_id,
616 mutations,
617 precommit_token,
618 self.commit_request_options(),
619 self.max_commit_delay,
620 self.return_commit_stats,
621 )
622 }
623
624 pub(crate) async fn commit(self) -> crate::Result<CommitResponse> {
626 self.context.transaction_selector.check_failed()?;
627 let mutations = take(&mut *self.mutations.lock().unwrap());
628 let mut id = self.context.transaction_selector.get_id_no_wait()?;
629 if id.is_none() {
630 if self.is_starting()? {
631 return Err(crate::error::internal_error(
632 "Commit called while an asynchronous statement is still starting the transaction",
633 ));
634 }
635 let mutation_key = Mutation::select_mutation_key(&mutations);
636 if self
637 .begin_explicitly_if_not_started(false, mutation_key)
638 .await?
639 {
640 self.context.transaction_selector.check_failed()?;
641 id = self.context.transaction_selector.get_id_no_wait()?;
642 }
643 }
644 let transaction_id = id.ok_or_else(|| internal_error("Transaction ID is missing"))?;
645 let precommit_token = self.context.precommit_token_tracker.get();
646
647 let request = self.build_commit_request(transaction_id.clone(), mutations, precommit_token);
648
649 let mut gax_options = self.commit_gax_options.clone().unwrap_or_default();
650 self.amend_gax_options(&mut gax_options);
651
652 let response = self
653 .context
654 .client
655 .spanner
656 .commit(request, gax_options, self.context.channel_hint)
657 .await?;
658
659 let response =
660 if let Some(new_precommit_token) = response.precommit_token().map(|b| (*b).clone()) {
661 let retry_commit_req = self.build_commit_request(
662 transaction_id,
663 Vec::new(), Some(*new_precommit_token),
665 );
666
667 let mut gax_options = self.commit_gax_options.clone().unwrap_or_default();
668 self.amend_gax_options(&mut gax_options);
669
670 self.context
671 .client
672 .spanner
673 .commit(retry_commit_req, gax_options, self.context.channel_hint)
674 .await?
675 } else {
676 response
677 };
678
679 Ok(response)
680 }
681
682 pub(crate) async fn rollback(self) -> crate::Result<()> {
684 let Some(transaction_id) = self.context.transaction_selector.get_id_no_wait()? else {
685 return Ok(());
686 };
687
688 let request = RollbackRequest::default()
689 .set_session(self.context.session_name.clone())
690 .set_transaction_id(transaction_id);
691
692 let mut gax_options = RequestOptions::default();
693 self.amend_gax_options(&mut gax_options);
694
695 self.context
696 .client
697 .spanner
698 .rollback(request, gax_options, self.context.channel_hint)
699 .await?;
700
701 Ok(())
702 }
703
704 fn amend_gax_options(&self, options: &mut GaxRequestOptions) {
705 amend_gax_options(
706 self.context.client.leader_aware_routing_enabled,
707 self.deadline,
708 options,
709 );
710 }
711}
712
713pub(crate) fn amend_gax_options(
714 leader_aware_routing_enabled: bool,
715 deadline: Option<Instant>,
716 options: &mut GaxRequestOptions,
717) {
718 if let Some(deadline) = deadline {
719 let remaining = deadline.saturating_duration_since(Instant::now());
720 let attempt_timeout = match options.attempt_timeout() {
721 Some(custom_timeout) => std::cmp::min(*custom_timeout, remaining),
722 None => remaining,
723 };
724 options.set_attempt_timeout(attempt_timeout);
725
726 let inner_policy = options
727 .retry_policy()
728 .clone()
729 .unwrap_or_else(|| Arc::new(SpannerRetryPolicy::new()));
730 let bounded_policy = TransactionBoundedRetryPolicy {
731 inner: inner_policy,
732 deadline,
733 };
734 options.set_retry_policy(bounded_policy);
735 }
736 *options = amend_request_options_for_lar(leader_aware_routing_enabled, take(options));
737}
738
739#[derive(Debug)]
745struct TransactionBoundedRetryPolicy {
746 inner: Arc<dyn RetryPolicy>,
747 deadline: Instant,
748}
749
750impl RetryPolicy for TransactionBoundedRetryPolicy {
751 fn on_error(&self, state: &RetryState, error: GaxError) -> RetryResult {
752 self.inner.on_error(state, error)
753 }
754
755 fn on_throttle(&self, state: &RetryState, error: GaxError) -> ThrottleResult {
756 self.inner.on_throttle(state, error)
757 }
758
759 fn remaining_time(&self, state: &RetryState) -> Option<StdDuration> {
760 let remaining = self.deadline.saturating_duration_since(Instant::now());
761 let attempt_timeout = self
762 .inner
763 .remaining_time(state)
764 .map(|inner| min(remaining, inner))
765 .unwrap_or(remaining);
766 Some(attempt_timeout)
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773 use crate::error::BatchUpdateError;
774 use crate::read_only_transaction::tests::{create_session_mock, setup_db_client};
775 use crate::result_set::tests::{adapt, string_val};
776 use crate::transaction_retry_policy::BasicTransactionRetryPolicy;
777 use gaxi::grpc::tonic;
778 use gaxi::grpc::tonic::MetadataMap;
779 use google_cloud_gax::options::internal::RequestOptionsExt as _;
780 use google_cloud_gax::retry_policy::NeverRetry;
781 use google_cloud_gax::retry_result::RetryResult;
782 use google_cloud_gax::retry_state::RetryState;
783 use google_cloud_test_macros::tokio_test_no_panics;
784 use http::HeaderMap;
785 use prost_types::Timestamp;
786 use spanner_grpc_mock::google::spanner::v1;
787 use std::fmt::Debug;
788 use std::sync::Mutex;
789 use std::sync::atomic::{AtomicU32, Ordering};
790 use std::time::Duration as StdDuration;
791
792 #[test]
793 fn auto_traits() {
794 static_assertions::assert_impl_all!(ReadWriteTransactionBuilder: Send, Sync, Clone, Debug);
795 static_assertions::assert_impl_all!(ReadWriteTransaction: Send, Sync, Debug);
796 }
797
798 #[tokio_test_no_panics]
799 async fn read_write_transaction_commit_retry_explicit() -> anyhow::Result<()> {
800 run_read_write_transaction_commit_retry(BeginTransactionOption::ExplicitBegin).await
801 }
802
803 #[tokio_test_no_panics]
804 async fn read_write_transaction_commit_retry_inline() -> anyhow::Result<()> {
805 run_read_write_transaction_commit_retry(BeginTransactionOption::InlineBegin).await
806 }
807
808 async fn run_read_write_transaction_commit_retry(
809 begin_transaction_option: BeginTransactionOption,
810 ) -> anyhow::Result<()> {
811 let mut mock = create_session_mock();
812 let remotes = Arc::new(Mutex::new(Vec::new()));
813
814 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
815 let remotes_clone = remotes.clone();
816 mock.expect_begin_transaction()
817 .once()
818 .returning(move |req| {
819 remotes_clone
820 .lock()
821 .unwrap()
822 .push(req.remote_addr().expect("remote_addr should be available"));
823 let req = req.into_inner();
824 assert_eq!(
825 req.session,
826 "projects/p/instances/i/databases/d/sessions/123"
827 );
828 Ok(tonic::Response::new(v1::Transaction {
829 id: vec![0, 0, 7],
830 ..Default::default()
831 }))
832 });
833 }
834
835 let remotes_clone = remotes.clone();
837 mock.expect_execute_sql().once().returning(move |req| {
838 remotes_clone
839 .lock()
840 .unwrap()
841 .push(req.remote_addr().expect("remote_addr should be available"));
842 let req = req.into_inner();
843 assert_eq!(req.sql, "UPDATE Users SET Name = 'Bob' WHERE Id = 1");
844
845 if begin_transaction_option == BeginTransactionOption::InlineBegin {
846 let transaction = req
847 .transaction
848 .as_ref()
849 .expect("transaction options required for inline begin");
850 let selector = transaction.selector.as_ref().expect("selector required");
851 assert!(matches!(
852 selector,
853 v1::transaction_selector::Selector::Begin(_)
854 ));
855 }
856
857 let mut metadata = v1::ResultSetMetadata {
858 row_type: Some(v1::StructType { fields: vec![] }),
859 ..Default::default()
860 };
861 if begin_transaction_option == BeginTransactionOption::InlineBegin {
862 metadata.transaction = Some(v1::Transaction {
863 id: vec![0, 0, 7],
864 ..Default::default()
865 });
866 }
867
868 Ok(tonic::Response::new(v1::ResultSet {
869 metadata: Some(metadata),
870 stats: Some(v1::ResultSetStats {
871 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
872 ..Default::default()
873 }),
874 precommit_token: Some(v1::MultiplexedSessionPrecommitToken {
875 precommit_token: vec![101],
876 seq_num: 1,
877 }),
878 ..Default::default()
879 }))
880 });
881
882 let remotes_clone = remotes.clone();
886 mock.expect_commit().once().returning(move |req| {
887 remotes_clone
888 .lock()
889 .unwrap()
890 .push(req.remote_addr().expect("remote_addr should be available"));
891 let req = req.into_inner();
892 assert_eq!(
893 req.precommit_token,
894 Some(v1::MultiplexedSessionPrecommitToken {
895 precommit_token: vec![101],
896 seq_num: 1,
897 })
898 );
899 Ok(tonic::Response::new(v1::CommitResponse {
900 commit_timestamp: Some(prost_types::Timestamp {
901 seconds: 1000,
902 nanos: 0,
903 }),
904 multiplexed_session_retry: Some(
905 v1::commit_response::MultiplexedSessionRetry::PrecommitToken(
906 v1::MultiplexedSessionPrecommitToken {
907 precommit_token: vec![202],
908 seq_num: 2,
909 },
910 ),
911 ),
912 ..Default::default()
913 }))
914 });
915
916 let remotes_clone = remotes.clone();
918 mock.expect_commit().once().returning(move |req| {
919 remotes_clone
920 .lock()
921 .unwrap()
922 .push(req.remote_addr().expect("remote_addr should be available"));
923 let req = req.into_inner();
924 assert_eq!(
925 req.precommit_token,
926 Some(v1::MultiplexedSessionPrecommitToken {
927 precommit_token: vec![202],
928 seq_num: 2,
929 })
930 );
931 assert!(
932 req.mutations.is_empty(),
933 "Expected mutations to be empty in retried CommitRequest"
934 );
935 Ok(tonic::Response::new(v1::CommitResponse {
936 commit_timestamp: Some(prost_types::Timestamp {
937 seconds: 1001,
938 nanos: 0,
939 }),
940 ..Default::default()
941 }))
942 });
943
944 let (db_client, _server) = setup_db_client(mock).await;
945
946 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
947 .with_begin_transaction_option(begin_transaction_option)
948 .build(None)
949 .await
950 .expect("Failed to build transaction");
951
952 let count = tx
953 .execute_update("UPDATE Users SET Name = 'Bob' WHERE Id = 1")
954 .await?;
955 assert_eq!(count, 1);
956
957 let timestamp = tx.commit().await?;
958 assert_eq!(
959 timestamp
960 .commit_timestamp
961 .as_ref()
962 .expect("timestamp should be present")
963 .seconds(),
964 1001
965 );
966
967 let remotes = remotes.lock().unwrap();
969 let expected_rpcs = if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
970 4
971 } else {
972 3
973 };
974 assert_eq!(
975 remotes.len(),
976 expected_rpcs,
977 "Expected exactly {} RPCs",
978 expected_rpcs
979 );
980 let first = remotes[0];
981 for addr in remotes.iter() {
982 assert_eq!(*addr, first, "All RPCs should use the same gRPC channel");
983 }
984
985 Ok(())
986 }
987
988 #[tokio_test_no_panics]
989 async fn read_write_transaction_commit_retry_preserves_options() -> anyhow::Result<()> {
990 let mut mock = create_session_mock();
991
992 mock.expect_execute_sql().once().returning(move |req| {
994 let req = req.into_inner();
995 assert_eq!(req.sql, "UPDATE Users SET Name = 'Bob' WHERE Id = 1");
996
997 let mut metadata = v1::ResultSetMetadata {
998 row_type: Some(v1::StructType { fields: vec![] }),
999 ..Default::default()
1000 };
1001 metadata.transaction = Some(v1::Transaction {
1002 id: vec![0, 0, 7],
1003 ..Default::default()
1004 });
1005
1006 Ok(tonic::Response::new(v1::ResultSet {
1007 metadata: Some(metadata),
1008 stats: Some(v1::ResultSetStats {
1009 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1010 ..Default::default()
1011 }),
1012 precommit_token: Some(v1::MultiplexedSessionPrecommitToken {
1013 precommit_token: vec![101],
1014 seq_num: 1,
1015 }),
1016 ..Default::default()
1017 }))
1018 });
1019
1020 let expected_delay = prost_types::Duration {
1024 seconds: 0,
1025 nanos: 200_000_000,
1026 };
1027
1028 let expected_delay_clone = expected_delay;
1029 mock.expect_commit().once().returning(move |req| {
1030 let req = req.into_inner();
1031 assert_eq!(
1032 req.precommit_token,
1033 Some(v1::MultiplexedSessionPrecommitToken {
1034 precommit_token: vec![101],
1035 seq_num: 1,
1036 })
1037 );
1038 assert!(
1040 req.return_commit_stats,
1041 "Expected return_commit_stats to be true in first commit"
1042 );
1043 assert_eq!(
1044 req.max_commit_delay.as_ref(),
1045 Some(&expected_delay_clone),
1046 "Expected max_commit_delay to be set in first commit"
1047 );
1048
1049 Ok(tonic::Response::new(v1::CommitResponse {
1050 commit_timestamp: Some(prost_types::Timestamp {
1051 seconds: 1000,
1052 nanos: 0,
1053 }),
1054 multiplexed_session_retry: Some(
1055 v1::commit_response::MultiplexedSessionRetry::PrecommitToken(
1056 v1::MultiplexedSessionPrecommitToken {
1057 precommit_token: vec![202],
1058 seq_num: 2,
1059 },
1060 ),
1061 ),
1062 ..Default::default()
1063 }))
1064 });
1065
1066 mock.expect_commit().once().returning(move |req| {
1068 let req = req.into_inner();
1069 assert_eq!(
1070 req.precommit_token,
1071 Some(v1::MultiplexedSessionPrecommitToken {
1072 precommit_token: vec![202],
1073 seq_num: 2,
1074 })
1075 );
1076 assert!(
1077 req.return_commit_stats,
1078 "Expected return_commit_stats to be preserved in retried commit request"
1079 );
1080 assert_eq!(
1081 req.max_commit_delay.as_ref(),
1082 Some(&expected_delay),
1083 "Expected max_commit_delay to be preserved in retried commit request"
1084 );
1085 assert!(
1086 req.mutations.is_empty(),
1087 "Expected mutations to be empty in retried CommitRequest"
1088 );
1089
1090 Ok(tonic::Response::new(v1::CommitResponse {
1091 commit_timestamp: Some(prost_types::Timestamp {
1092 seconds: 1001,
1093 nanos: 0,
1094 }),
1095 ..Default::default()
1096 }))
1097 });
1098
1099 let (db_client, _server) = setup_db_client(mock).await;
1100
1101 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1102 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1103 .set_return_commit_stats(true)
1104 .set_max_commit_delay(Duration::new(0, 200_000_000).expect("valid duration"))
1105 .build(None)
1106 .await
1107 .expect("Failed to build transaction");
1108
1109 let count = tx
1110 .execute_update("UPDATE Users SET Name = 'Bob' WHERE Id = 1")
1111 .await?;
1112 assert_eq!(count, 1);
1113
1114 let timestamp = tx.commit().await?;
1115 assert_eq!(
1116 timestamp
1117 .commit_timestamp
1118 .as_ref()
1119 .expect("timestamp should be present")
1120 .seconds(),
1121 1001
1122 );
1123
1124 Ok(())
1125 }
1126
1127 #[tokio_test_no_panics]
1128 async fn read_write_transaction_commit_carries_commit_priority() -> anyhow::Result<()> {
1129 let mut mock = create_session_mock();
1130
1131 mock.expect_execute_sql().once().returning(move |_req| {
1132 let mut metadata = v1::ResultSetMetadata {
1133 row_type: Some(v1::StructType { fields: vec![] }),
1134 ..Default::default()
1135 };
1136 metadata.transaction = Some(v1::Transaction {
1137 id: vec![1, 2, 3],
1138 ..Default::default()
1139 });
1140
1141 Ok(tonic::Response::new(v1::ResultSet {
1142 metadata: Some(metadata),
1143 stats: Some(v1::ResultSetStats {
1144 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1145 ..Default::default()
1146 }),
1147 ..Default::default()
1148 }))
1149 });
1150
1151 mock.expect_commit().once().returning(|req| {
1152 let req = req.into_inner();
1153 let request_options = req
1154 .request_options
1155 .expect("Expected request_options in CommitRequest");
1156 assert_eq!(
1157 request_options.priority,
1158 v1::request_options::Priority::Low as i32,
1159 "Expected priority to be Priority::Low in CommitRequest"
1160 );
1161
1162 Ok(tonic::Response::new(v1::CommitResponse {
1163 commit_timestamp: Some(prost_types::Timestamp {
1164 seconds: 123456789,
1165 nanos: 0,
1166 }),
1167 ..Default::default()
1168 }))
1169 });
1170
1171 let (db_client, _server) = setup_db_client(mock).await;
1172
1173 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1174 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1175 .set_commit_priority(Priority::Low)
1176 .build(None)
1177 .await
1178 .expect("Failed to build transaction");
1179
1180 let count = tx
1181 .execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1182 .await?;
1183 assert_eq!(count, 1);
1184
1185 let _ = tx.commit().await?;
1186
1187 Ok(())
1188 }
1189
1190 #[tokio_test_no_panics]
1191 async fn read_write_transaction_execute_update_last_statement() {
1192 let stmt = Statement::builder("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1193 .set_last_statement(true)
1194 .build();
1195 run_read_write_transaction_execute_update(BeginTransactionOption::ExplicitBegin, stmt)
1196 .await;
1197 }
1198
1199 #[tokio_test_no_panics]
1200 async fn read_write_transaction_execute_update_explicit() {
1201 run_read_write_transaction_execute_update(
1202 BeginTransactionOption::ExplicitBegin,
1203 "UPDATE Users SET Name = 'Alice' WHERE Id = 1",
1204 )
1205 .await;
1206 }
1207
1208 #[tokio_test_no_panics]
1209 async fn read_write_transaction_execute_update_inline() {
1210 run_read_write_transaction_execute_update(
1211 BeginTransactionOption::InlineBegin,
1212 "UPDATE Users SET Name = 'Alice' WHERE Id = 1",
1213 )
1214 .await;
1215 }
1216
1217 async fn run_read_write_transaction_execute_update(
1218 begin_transaction_option: BeginTransactionOption,
1219 statement: impl Into<Statement>,
1220 ) {
1221 let statement = statement.into();
1222 let expected_last_statement = statement.last_statement;
1223 let mut mock = create_session_mock();
1224
1225 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1226 mock.expect_begin_transaction().once().returning(|req| {
1227 let req = req.into_inner();
1228 assert_eq!(
1229 req.session,
1230 "projects/p/instances/i/databases/d/sessions/123"
1231 );
1232 Ok(tonic::Response::new(v1::Transaction {
1233 id: vec![1, 2, 3],
1234 ..Default::default()
1235 }))
1236 });
1237 }
1238
1239 mock.expect_execute_sql().once().returning(move |req| {
1240 let req = req.into_inner();
1241 assert_eq!(req.sql, "UPDATE Users SET Name = 'Alice' WHERE Id = 1");
1242 assert_eq!(req.seqno, 1);
1243 assert_eq!(req.last_statement, expected_last_statement);
1244
1245 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1246 let transaction = req
1247 .transaction
1248 .as_ref()
1249 .expect("transaction options required for inline begin");
1250 let selector = transaction.selector.as_ref().expect("selector required");
1251 assert!(matches!(
1252 selector,
1253 v1::transaction_selector::Selector::Begin(_)
1254 ));
1255 }
1256
1257 let mut metadata = v1::ResultSetMetadata {
1258 row_type: Some(v1::StructType { fields: vec![] }),
1259 ..Default::default()
1260 };
1261 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1262 metadata.transaction = Some(v1::Transaction {
1263 id: vec![1, 2, 3],
1264 ..Default::default()
1265 });
1266 }
1267
1268 Ok(tonic::Response::new(v1::ResultSet {
1269 metadata: Some(metadata),
1270 stats: Some(v1::ResultSetStats {
1271 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1272 ..Default::default()
1273 }),
1274 ..Default::default()
1275 }))
1276 });
1277
1278 mock.expect_commit().once().returning(|req| {
1279 let req = req.into_inner();
1280 assert_eq!(
1281 req.session,
1282 "projects/p/instances/i/databases/d/sessions/123"
1283 );
1284 assert_eq!(
1285 req.transaction,
1286 Some(v1::commit_request::Transaction::TransactionId(vec![
1287 1, 2, 3
1288 ]))
1289 );
1290 Ok(tonic::Response::new(v1::CommitResponse {
1291 commit_timestamp: Some(prost_types::Timestamp {
1292 seconds: 123456789,
1293 nanos: 0,
1294 }),
1295 ..Default::default()
1296 }))
1297 });
1298
1299 let (db_client, _server) = setup_db_client(mock).await;
1300
1301 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1302 .with_begin_transaction_option(begin_transaction_option)
1303 .build(None)
1304 .await
1305 .expect("Failed to build transaction");
1306 let count = tx
1307 .execute_update(statement)
1308 .await
1309 .expect("Failed to execute update");
1310 assert_eq!(count, 1);
1311
1312 let ts = tx.commit().await.expect("Failed to commit");
1313 assert_eq!(
1314 ts.commit_timestamp
1315 .expect("Commit timestamp should be present")
1316 .seconds(),
1317 123456789
1318 );
1319 }
1320
1321 #[tokio_test_no_panics]
1322 async fn read_write_transaction_execute_update_invalid_stats_explicit() -> anyhow::Result<()> {
1323 run_read_write_transaction_execute_update_invalid_stats(
1324 BeginTransactionOption::ExplicitBegin,
1325 )
1326 .await
1327 }
1328
1329 #[tokio_test_no_panics]
1330 async fn read_write_transaction_execute_update_invalid_stats_inline() -> anyhow::Result<()> {
1331 run_read_write_transaction_execute_update_invalid_stats(BeginTransactionOption::InlineBegin)
1332 .await
1333 }
1334
1335 async fn run_read_write_transaction_execute_update_invalid_stats(
1336 begin_transaction_option: BeginTransactionOption,
1337 ) -> anyhow::Result<()> {
1338 let mut mock = create_session_mock();
1339
1340 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1341 mock.expect_begin_transaction().once().returning(|_| {
1342 Ok(tonic::Response::new(v1::Transaction {
1343 id: vec![1, 2, 3],
1344 ..Default::default()
1345 }))
1346 });
1347 }
1348
1349 mock.expect_execute_sql().once().returning(move |req| {
1350 let req = req.into_inner();
1351 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1352 let transaction = req
1353 .transaction
1354 .as_ref()
1355 .expect("transaction options required for inline begin");
1356 let selector = transaction.selector.as_ref().expect("selector required");
1357 assert!(matches!(
1358 selector,
1359 v1::transaction_selector::Selector::Begin(_)
1360 ));
1361 }
1362
1363 let mut metadata = v1::ResultSetMetadata {
1364 row_type: Some(v1::StructType { fields: vec![] }),
1365 ..Default::default()
1366 };
1367 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1368 metadata.transaction = Some(v1::Transaction {
1369 id: vec![1, 2, 3],
1370 ..Default::default()
1371 });
1372 }
1373
1374 Ok(tonic::Response::new(v1::ResultSet {
1375 metadata: Some(metadata),
1376 stats: Some(v1::ResultSetStats {
1377 row_count: Some(v1::result_set_stats::RowCount::RowCountLowerBound(1)),
1378 ..Default::default()
1379 }),
1380 ..Default::default()
1381 }))
1382 });
1383
1384 let (db_client, _server) = setup_db_client(mock).await;
1385
1386 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1387 .with_begin_transaction_option(begin_transaction_option)
1388 .build(None)
1389 .await
1390 .expect("Failed to build transaction");
1391
1392 let result = tx
1393 .execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1394 .await;
1395
1396 let err = result.expect_err("Expected an error for invalid row count stats");
1397 assert!(
1398 format!("{:?}", err).contains("invalid or missing row count type"),
1399 "Error did not contain expected message: {:?}",
1400 err
1401 );
1402 Ok(())
1403 }
1404
1405 #[tokio_test_no_panics]
1406 async fn read_write_transaction_rollback_explicit() -> anyhow::Result<()> {
1407 run_read_write_transaction_rollback(BeginTransactionOption::ExplicitBegin).await
1408 }
1409
1410 #[tokio_test_no_panics]
1411 async fn read_write_transaction_rollback_inline() -> anyhow::Result<()> {
1412 run_read_write_transaction_rollback(BeginTransactionOption::InlineBegin).await
1413 }
1414
1415 async fn run_read_write_transaction_rollback(
1416 begin_transaction_option: BeginTransactionOption,
1417 ) -> anyhow::Result<()> {
1418 let mut mock = create_session_mock();
1419 let transaction_id = vec![9, 9, 9];
1420
1421 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1422 let id = transaction_id.clone();
1423 mock.expect_begin_transaction().once().returning(move |_| {
1424 Ok(tonic::Response::new(v1::Transaction {
1425 id: id.clone(),
1426 ..Default::default()
1427 }))
1428 });
1429 } else {
1430 let id = transaction_id.clone();
1431 mock.expect_execute_sql().once().returning(move |req| {
1432 let req = req.into_inner();
1433 let transaction = req
1434 .transaction
1435 .as_ref()
1436 .expect("transaction options required for inline begin");
1437 let selector = transaction.selector.as_ref().expect("selector required");
1438 assert!(matches!(
1439 selector,
1440 v1::transaction_selector::Selector::Begin(_)
1441 ));
1442
1443 Ok(tonic::Response::new(v1::ResultSet {
1444 metadata: Some(v1::ResultSetMetadata {
1445 transaction: Some(v1::Transaction {
1446 id: id.clone(),
1447 ..Default::default()
1448 }),
1449 ..Default::default()
1450 }),
1451 stats: Some(v1::ResultSetStats {
1452 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1453 ..Default::default()
1454 }),
1455 ..Default::default()
1456 }))
1457 });
1458 }
1459
1460 let id = transaction_id.clone();
1461 mock.expect_rollback().once().returning(move |req| {
1462 let req = req.into_inner();
1463 assert_eq!(
1464 req.session,
1465 "projects/p/instances/i/databases/d/sessions/123"
1466 );
1467 assert_eq!(req.transaction_id, id);
1468 Ok(tonic::Response::new(()))
1469 });
1470
1471 let (db_client, _server) = setup_db_client(mock).await;
1472
1473 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1474 .with_begin_transaction_option(begin_transaction_option)
1475 .build(None)
1476 .await?;
1477
1478 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1479 tx.execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1480 .await
1481 .expect("Failed to execute update");
1482 }
1483
1484 tx.rollback().await?;
1485 Ok(())
1486 }
1487
1488 #[tokio_test_no_panics]
1489 async fn read_write_transaction_execute_batch_update_last_statements() -> anyhow::Result<()> {
1490 let batch = BatchDml::builder()
1491 .add_statement("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1492 .add_statement("UPDATE Users SET Name = 'Bob' WHERE Id = 2")
1493 .set_last_statements(true)
1494 .build();
1495 run_read_write_transaction_execute_batch_update(
1496 BeginTransactionOption::ExplicitBegin,
1497 batch,
1498 )
1499 .await
1500 }
1501
1502 #[tokio_test_no_panics]
1503 async fn read_write_transaction_execute_batch_update_explicit() -> anyhow::Result<()> {
1504 let batch = BatchDml::builder()
1505 .add_statement("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1506 .add_statement("UPDATE Users SET Name = 'Bob' WHERE Id = 2")
1507 .build();
1508 run_read_write_transaction_execute_batch_update(
1509 BeginTransactionOption::ExplicitBegin,
1510 batch,
1511 )
1512 .await
1513 }
1514
1515 #[tokio_test_no_panics]
1516 async fn read_write_transaction_execute_batch_update_inline() -> anyhow::Result<()> {
1517 let batch = BatchDml::builder()
1518 .add_statement("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1519 .add_statement("UPDATE Users SET Name = 'Bob' WHERE Id = 2")
1520 .build();
1521 run_read_write_transaction_execute_batch_update(BeginTransactionOption::InlineBegin, batch)
1522 .await
1523 }
1524
1525 #[tokio_test_no_panics]
1526 async fn read_write_transaction_execute_batch_update_vec() -> anyhow::Result<()> {
1527 let statements = vec![
1528 "UPDATE Users SET Name = 'Alice' WHERE Id = 1",
1529 "UPDATE Users SET Name = 'Bob' WHERE Id = 2",
1530 ];
1531 run_read_write_transaction_execute_batch_update(
1532 BeginTransactionOption::InlineBegin,
1533 statements,
1534 )
1535 .await
1536 }
1537
1538 #[tokio_test_no_panics]
1539 async fn read_write_transaction_execute_batch_update_vec_statement() -> anyhow::Result<()> {
1540 let statement1 = Statement::builder("UPDATE Users SET Name = 'Alice' WHERE Id = 1").build();
1541 let statement2 = Statement::builder("UPDATE Users SET Name = 'Bob' WHERE Id = 2").build();
1542 let statements = vec![statement1, statement2];
1543 run_read_write_transaction_execute_batch_update(
1544 BeginTransactionOption::InlineBegin,
1545 statements,
1546 )
1547 .await
1548 }
1549
1550 async fn run_read_write_transaction_execute_batch_update(
1551 begin_transaction_option: BeginTransactionOption,
1552 batch: impl Into<BatchDml>,
1553 ) -> anyhow::Result<()> {
1554 let batch = batch.into();
1555 let expected_last_statements = batch.last_statements;
1556 let mut mock = create_session_mock();
1557
1558 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1559 mock.expect_begin_transaction().once().returning(|_| {
1560 Ok(tonic::Response::new(v1::Transaction {
1561 id: vec![4, 5, 6],
1562 ..Default::default()
1563 }))
1564 });
1565 }
1566
1567 mock.expect_execute_batch_dml()
1568 .once()
1569 .returning(move |req| {
1570 let req = req.into_inner();
1571 assert_eq!(req.last_statements, expected_last_statements);
1572 assert_eq!(req.statements.len(), 2);
1573 assert_eq!(
1574 req.statements[0].sql,
1575 "UPDATE Users SET Name = 'Alice' WHERE Id = 1"
1576 );
1577 assert_eq!(
1578 req.statements[1].sql,
1579 "UPDATE Users SET Name = 'Bob' WHERE Id = 2"
1580 );
1581
1582 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1583 let selector = req
1584 .transaction
1585 .expect("missing transaction selector")
1586 .selector
1587 .expect("missing selector");
1588 assert!(matches!(
1589 selector,
1590 v1::transaction_selector::Selector::Begin(_)
1591 ));
1592 }
1593
1594 let mut metadata = v1::ResultSetMetadata {
1595 ..Default::default()
1596 };
1597 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1598 metadata.transaction = Some(v1::Transaction {
1599 id: vec![4, 5, 6],
1600 ..Default::default()
1601 });
1602 }
1603
1604 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
1605 result_sets: vec![
1606 v1::ResultSet {
1607 metadata: Some(metadata),
1608 stats: Some(v1::ResultSetStats {
1609 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1610 ..Default::default()
1611 }),
1612 ..Default::default()
1613 },
1614 v1::ResultSet {
1615 stats: Some(v1::ResultSetStats {
1616 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1617 ..Default::default()
1618 }),
1619 ..Default::default()
1620 },
1621 ],
1622 status: Some(spanner_grpc_mock::google::rpc::Status {
1623 code: 0,
1624 message: "OK".into(),
1625 details: vec![],
1626 }),
1627 ..Default::default()
1628 }))
1629 });
1630
1631 let (db_client, _server) = setup_db_client(mock).await;
1632
1633 let transaction = ReadWriteTransactionBuilder::new(db_client)
1634 .with_begin_transaction_option(begin_transaction_option)
1635 .build(None)
1636 .await?;
1637
1638 let counts = transaction.execute_batch_update(batch).await?;
1639
1640 assert_eq!(counts, vec![1, 1]);
1641 Ok(())
1642 }
1643
1644 #[tokio_test_no_panics]
1645 async fn read_write_transaction_execute_batch_update_partial_failure_explicit()
1646 -> anyhow::Result<()> {
1647 run_read_write_transaction_execute_batch_update_partial_failure(
1648 BeginTransactionOption::ExplicitBegin,
1649 )
1650 .await
1651 }
1652
1653 #[tokio_test_no_panics]
1654 async fn read_write_transaction_execute_batch_update_partial_failure_inline()
1655 -> anyhow::Result<()> {
1656 run_read_write_transaction_execute_batch_update_partial_failure(
1657 BeginTransactionOption::InlineBegin,
1658 )
1659 .await
1660 }
1661
1662 async fn run_read_write_transaction_execute_batch_update_partial_failure(
1663 begin_transaction_option: BeginTransactionOption,
1664 ) -> anyhow::Result<()> {
1665 let mut mock = create_session_mock();
1666
1667 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1668 mock.expect_begin_transaction().once().returning(|_| {
1669 Ok(tonic::Response::new(v1::Transaction {
1670 id: vec![7, 8, 9],
1671 ..Default::default()
1672 }))
1673 });
1674 }
1675
1676 mock.expect_execute_batch_dml()
1677 .once()
1678 .returning(move |req| {
1679 let req = req.into_inner();
1680 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1681 let selector = req
1682 .transaction
1683 .expect("missing transaction selector")
1684 .selector
1685 .expect("missing selector");
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![7, 8, 9],
1698 ..Default::default()
1699 });
1700 }
1701
1702 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
1703 result_sets: vec![v1::ResultSet {
1704 metadata: Some(metadata),
1705 stats: Some(v1::ResultSetStats {
1706 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1707 ..Default::default()
1708 }),
1709 ..Default::default()
1710 }],
1711 status: Some(spanner_grpc_mock::google::rpc::Status {
1712 code: gaxi::grpc::tonic::Code::AlreadyExists as i32,
1713 message: "row already exists".into(),
1714 details: vec![],
1715 }),
1716 ..Default::default()
1717 }))
1718 });
1719
1720 let (db_client, _server) = setup_db_client(mock).await;
1721
1722 let tx = ReadWriteTransactionBuilder::new(db_client)
1723 .with_begin_transaction_option(begin_transaction_option)
1724 .build(None)
1725 .await?;
1726
1727 let batch = BatchDml::builder()
1728 .add_statement("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1729 .add_statement("INSERT INTO Users (Id) VALUES (2)");
1730
1731 let res = tx.execute_batch_update(batch.build()).await;
1732
1733 let err = res.expect_err("expected error");
1734 use std::error::Error;
1735 let batch_err = err
1736 .source()
1737 .and_then(|e| e.downcast_ref::<BatchUpdateError>())
1738 .expect("should be BatchUpdateError");
1739 assert_eq!(batch_err.update_counts, vec![1]);
1740 assert_eq!(
1741 batch_err.status.status().expect("status").code,
1742 (gaxi::grpc::tonic::Code::AlreadyExists as i32).into()
1743 );
1744 Ok(())
1745 }
1746
1747 #[tokio_test_no_panics]
1748 async fn read_write_transaction_execute_multiple_updates_explicit() -> anyhow::Result<()> {
1749 run_read_write_transaction_execute_multiple_updates(BeginTransactionOption::ExplicitBegin)
1750 .await
1751 }
1752
1753 #[tokio_test_no_panics]
1754 async fn read_write_transaction_execute_multiple_updates_inline() -> anyhow::Result<()> {
1755 run_read_write_transaction_execute_multiple_updates(BeginTransactionOption::InlineBegin)
1756 .await
1757 }
1758
1759 async fn run_read_write_transaction_execute_multiple_updates(
1760 begin_transaction_option: BeginTransactionOption,
1761 ) -> anyhow::Result<()> {
1762 let mut mock = create_session_mock();
1763
1764 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
1765 mock.expect_begin_transaction().once().returning(|req| {
1766 let req = req.into_inner();
1767 assert_eq!(
1768 req.session,
1769 "projects/p/instances/i/databases/d/sessions/123"
1770 );
1771 Ok(tonic::Response::new(v1::Transaction {
1772 id: vec![4, 5, 6],
1773 ..Default::default()
1774 }))
1775 });
1776 }
1777
1778 let counter = Arc::new(AtomicI64::new(1));
1779 mock.expect_execute_sql().times(3).returning(move |req| {
1780 let req = req.into_inner();
1781 assert_eq!(req.sql, "UPDATE Users SET Name = 'Alice' WHERE Id = 1");
1782 let c = counter.fetch_add(1, Ordering::SeqCst);
1783 assert_eq!(req.seqno, c);
1784
1785 let mut metadata = v1::ResultSetMetadata {
1786 ..Default::default()
1787 };
1788
1789 if begin_transaction_option == BeginTransactionOption::InlineBegin {
1790 if c == 1 {
1791 let selector = req
1792 .transaction
1793 .expect("missing transaction selector")
1794 .selector
1795 .expect("missing selector");
1796 assert!(matches!(
1797 selector,
1798 v1::transaction_selector::Selector::Begin(_)
1799 ));
1800 metadata.transaction = Some(v1::Transaction {
1801 id: vec![4, 5, 6],
1802 ..Default::default()
1803 });
1804 } else {
1805 let selector = req
1806 .transaction
1807 .expect("missing transaction selector")
1808 .selector
1809 .expect("missing selector");
1810 match selector {
1811 v1::transaction_selector::Selector::Id(id) => {
1812 assert_eq!(id, vec![4, 5, 6]);
1813 }
1814 _ => panic!("Expected Selector::Id"),
1815 }
1816 }
1817 }
1818
1819 Ok(tonic::Response::new(v1::ResultSet {
1820 metadata: Some(metadata),
1821 stats: Some(v1::ResultSetStats {
1822 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
1823 ..Default::default()
1824 }),
1825 ..Default::default()
1826 }))
1827 });
1828
1829 let (db_client, _server) = setup_db_client(mock).await;
1830
1831 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1832 .with_begin_transaction_option(begin_transaction_option)
1833 .build(None)
1834 .await
1835 .expect("Failed to build transaction");
1836
1837 for i in 1..=3 {
1838 let count = tx
1839 .execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
1840 .await
1841 .unwrap_or_else(|_| panic!("Failed to execute update {}", i));
1842 assert_eq!(count, 1);
1843 }
1844 Ok(())
1845 }
1846
1847 #[tokio_test_no_panics]
1848 async fn read_write_transaction_execute_query() {
1849 use crate::statement::Statement;
1850 let mut mock = create_session_mock();
1851
1852 mock.expect_begin_transaction().once().returning(|req| {
1853 let req = req.into_inner();
1854 assert_eq!(
1855 req.session,
1856 "projects/p/instances/i/databases/d/sessions/123"
1857 );
1858 Ok(tonic::Response::new(v1::Transaction {
1859 id: vec![7, 8, 9],
1860 ..Default::default()
1861 }))
1862 });
1863
1864 mock.expect_execute_streaming_sql().once().returning(|req| {
1865 let req = req.into_inner();
1866 assert_eq!(req.sql, "SELECT 1", "SQL statement mismatch");
1867 assert_eq!(req.seqno, 1, "ExecuteSqlRequest seqno mismatch");
1869
1870 assert_eq!(
1871 req.transaction,
1872 Some(v1::TransactionSelector {
1873 selector: Some(v1::transaction_selector::Selector::Id(vec![7, 8, 9]))
1874 })
1875 );
1876
1877 let prs = v1::PartialResultSet {
1878 metadata: Some(v1::ResultSetMetadata {
1879 row_type: Some(v1::StructType { fields: vec![] }),
1880 ..Default::default()
1881 }),
1882 ..Default::default()
1883 };
1884 Ok(tonic::Response::from(crate::result_set::tests::adapt([
1885 Ok(prs),
1886 ])))
1887 });
1888
1889 let (db_client, _server) = setup_db_client(mock).await;
1890
1891 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
1892 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
1893 .build(None)
1894 .await
1895 .expect("Failed to build transaction");
1896
1897 let mut rs = tx
1898 .execute_query(Statement::builder("SELECT 1").build())
1899 .await
1900 .expect("Failed to execute query");
1901
1902 let result = rs.next().await;
1903 assert!(result.is_none(), "expected None, got empty stream");
1904 }
1905
1906 #[tokio_test_no_panics]
1907 async fn read_write_transaction_with_options() {
1908 let mut mock = create_session_mock();
1909
1910 mock.expect_begin_transaction().once().returning(|req| {
1911 let req = req.into_inner();
1912 assert_eq!(
1913 req.session,
1914 "projects/p/instances/i/databases/d/sessions/123"
1915 );
1916
1917 let options = req.options.expect("missing transaction options");
1918 let mode = options.mode.expect("missing mode");
1919 match mode {
1920 v1::transaction_options::Mode::ReadWrite(rw) => {
1921 assert_eq!(
1922 rw.read_lock_mode,
1923 v1::transaction_options::read_write::ReadLockMode::Pessimistic as i32
1924 );
1925 }
1926 _ => panic!("Expected ReadWrite transaction mode"),
1927 }
1928 assert_eq!(
1930 options.isolation_level,
1931 v1::transaction_options::IsolationLevel::Serializable as i32
1932 );
1933
1934 Ok(tonic::Response::new(v1::Transaction {
1935 id: vec![9, 9, 9],
1936 ..Default::default()
1937 }))
1938 });
1939
1940 let (db_client, _server) = setup_db_client(mock).await;
1941
1942 let _tx = ReadWriteTransactionBuilder::new(db_client.clone())
1943 .set_isolation_level(IsolationLevel::Serializable)
1944 .set_read_lock_mode(ReadLockMode::Pessimistic)
1945 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
1946 .build(None)
1947 .await
1948 .expect("Failed to build transaction");
1949 }
1950
1951 #[tokio_test_no_panics]
1952 async fn read_write_transaction_with_exclude_txn_from_change_streams() {
1953 let mut mock = create_session_mock();
1954
1955 mock.expect_begin_transaction().once().returning(|req| {
1956 let req = req.into_inner();
1957 let options = req.options.expect("missing transaction options");
1958 assert!(options.exclude_txn_from_change_streams);
1959
1960 Ok(tonic::Response::new(v1::Transaction {
1961 id: vec![9, 9, 9],
1962 ..Default::default()
1963 }))
1964 });
1965
1966 let (db_client, _server) = setup_db_client(mock).await;
1967
1968 let _tx = ReadWriteTransactionBuilder::new(db_client.clone())
1969 .set_exclude_txn_from_change_streams(true)
1970 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
1971 .build(None)
1972 .await
1973 .expect("Failed to build transaction");
1974 }
1975
1976 #[tokio_test_no_panics]
1977 async fn read_write_transaction_tracks_highest_precommit_token() {
1978 let mut mock = create_session_mock();
1979
1980 mock.expect_begin_transaction().once().returning(|_| {
1981 Ok(tonic::Response::new(v1::Transaction {
1982 id: vec![4, 2],
1983 ..Default::default()
1984 }))
1985 });
1986
1987 let tokens_iter = vec![2, 5, 3].into_iter();
1989 let counter_mutex = Mutex::new(tokens_iter);
1990
1991 mock.expect_execute_sql().times(3).returning(move |_req| {
1992 let seq = counter_mutex
1993 .lock()
1994 .expect("Failed to lock mutex")
1995 .next()
1996 .expect("Failed to get next token");
1997 Ok(tonic::Response::new(v1::ResultSet {
1998 stats: Some(v1::ResultSetStats {
1999 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2000 ..Default::default()
2001 }),
2002 precommit_token: Some(v1::MultiplexedSessionPrecommitToken {
2003 precommit_token: vec![seq as u8],
2004 seq_num: seq,
2005 }),
2006 ..Default::default()
2007 }))
2008 });
2009
2010 mock.expect_commit().once().returning(|req| {
2012 let req = req.into_inner();
2013 assert_eq!(
2014 req.precommit_token,
2015 Some(v1::MultiplexedSessionPrecommitToken {
2016 precommit_token: vec![5],
2017 seq_num: 5,
2018 })
2019 );
2020 Ok(tonic::Response::new(v1::CommitResponse {
2021 commit_timestamp: Some(prost_types::Timestamp {
2022 seconds: 12345,
2023 nanos: 0,
2024 }),
2025 ..Default::default()
2026 }))
2027 });
2028
2029 let (db_client, _server) = setup_db_client(mock).await;
2030 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
2031 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
2032 .build(None)
2033 .await
2034 .expect("Failed to build transaction");
2035
2036 for _ in 0..3 {
2037 tx.execute_update("UPDATE Y")
2038 .await
2039 .expect("Failed to execute update");
2040 }
2041 let ts = tx.commit().await.expect("Failed to commit transaction");
2042 assert_eq!(
2043 ts.commit_timestamp
2044 .expect("Commit timestamp should be present")
2045 .seconds(),
2046 12345
2047 );
2048 }
2049
2050 #[tokio_test_no_panics]
2051 async fn read_write_transaction_commit_retry_exactly_once_explicit() -> anyhow::Result<()> {
2052 run_read_write_transaction_commit_retry_exactly_once(BeginTransactionOption::ExplicitBegin)
2053 .await
2054 }
2055
2056 #[tokio_test_no_panics]
2057 async fn read_write_transaction_commit_retry_exactly_once_inline() -> anyhow::Result<()> {
2058 run_read_write_transaction_commit_retry_exactly_once(BeginTransactionOption::InlineBegin)
2059 .await
2060 }
2061
2062 async fn run_read_write_transaction_commit_retry_exactly_once(
2063 begin_transaction_option: BeginTransactionOption,
2064 ) -> anyhow::Result<()> {
2065 let mut mock = create_session_mock();
2066
2067 let transaction_id = vec![7, 7];
2068
2069 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
2070 let id = transaction_id.clone();
2071 mock.expect_begin_transaction().once().returning(move |_| {
2072 Ok(tonic::Response::new(v1::Transaction {
2073 id: id.clone(),
2074 ..Default::default()
2075 }))
2076 });
2077 } else {
2078 let id = transaction_id.clone();
2079 mock.expect_execute_sql().once().returning(move |req| {
2080 let req = req.into_inner();
2081 let transaction = req
2082 .transaction
2083 .as_ref()
2084 .expect("transaction options required for inline begin");
2085 let selector = transaction.selector.as_ref().expect("selector required");
2086 assert!(matches!(
2087 selector,
2088 v1::transaction_selector::Selector::Begin(_)
2089 ));
2090
2091 Ok(tonic::Response::new(v1::ResultSet {
2092 metadata: Some(v1::ResultSetMetadata {
2093 transaction: Some(v1::Transaction {
2094 id: id.clone(),
2095 ..Default::default()
2096 }),
2097 ..Default::default()
2098 }),
2099 stats: Some(v1::ResultSetStats {
2100 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2101 ..Default::default()
2102 }),
2103 ..Default::default()
2104 }))
2105 });
2106 }
2107
2108 let mut seq = mockall::Sequence::new();
2109
2110 mock.expect_commit()
2112 .once()
2113 .in_sequence(&mut seq)
2114 .returning(|_| {
2115 Ok(tonic::Response::new(v1::CommitResponse {
2116 commit_timestamp: Some(prost_types::Timestamp {
2117 seconds: 1000,
2118 nanos: 0,
2119 }),
2120 multiplexed_session_retry: Some(
2121 v1::commit_response::MultiplexedSessionRetry::PrecommitToken(
2122 v1::MultiplexedSessionPrecommitToken {
2123 precommit_token: vec![2],
2124 seq_num: 2,
2125 },
2126 ),
2127 ),
2128 ..Default::default()
2129 }))
2130 });
2131
2132 mock.expect_commit()
2135 .once()
2136 .in_sequence(&mut seq)
2137 .returning(|req| {
2138 let req = req.into_inner();
2139 assert_eq!(
2140 req.precommit_token
2141 .as_ref()
2142 .expect("Missing precommit token in retry req")
2143 .seq_num,
2144 2
2145 );
2146
2147 Ok(tonic::Response::new(v1::CommitResponse {
2148 commit_timestamp: Some(prost_types::Timestamp {
2149 seconds: 9999,
2150 nanos: 0,
2151 }),
2152 multiplexed_session_retry: Some(
2153 v1::commit_response::MultiplexedSessionRetry::PrecommitToken(
2154 v1::MultiplexedSessionPrecommitToken {
2155 precommit_token: vec![3],
2156 seq_num: 3,
2157 },
2158 ),
2159 ),
2160 ..Default::default()
2161 }))
2162 });
2163
2164 let (db_client, _server) = setup_db_client(mock).await;
2165 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
2166 .with_begin_transaction_option(begin_transaction_option)
2167 .build(None)
2168 .await?;
2169
2170 if begin_transaction_option == BeginTransactionOption::InlineBegin {
2171 tx.execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
2172 .await?;
2173 }
2174
2175 let ts = tx.commit().await.expect("Failed to commit transaction");
2176 assert_eq!(
2177 ts.commit_timestamp
2178 .as_ref()
2179 .expect("timestamp should be present")
2180 .seconds(),
2181 9999
2182 );
2183 Ok(())
2184 }
2185
2186 #[tokio_test_no_panics]
2187 async fn read_write_transaction_commit_with_max_commit_delay_explicit() -> anyhow::Result<()> {
2188 run_read_write_transaction_commit_with_max_commit_delay(
2189 BeginTransactionOption::ExplicitBegin,
2190 )
2191 .await
2192 }
2193
2194 #[tokio_test_no_panics]
2195 async fn read_write_transaction_commit_with_max_commit_delay_inline() -> anyhow::Result<()> {
2196 run_read_write_transaction_commit_with_max_commit_delay(BeginTransactionOption::InlineBegin)
2197 .await
2198 }
2199
2200 async fn run_read_write_transaction_commit_with_max_commit_delay(
2201 begin_transaction_option: BeginTransactionOption,
2202 ) -> anyhow::Result<()> {
2203 let mut mock = create_session_mock();
2204
2205 if begin_transaction_option == BeginTransactionOption::ExplicitBegin {
2206 mock.expect_begin_transaction().once().returning(|_| {
2207 Ok(tonic::Response::new(v1::Transaction {
2208 id: vec![1, 2, 3],
2209 ..Default::default()
2210 }))
2211 });
2212 } else {
2213 mock.expect_execute_sql().once().returning(|req| {
2214 let req = req.into_inner();
2215 let transaction = req
2216 .transaction
2217 .as_ref()
2218 .expect("transaction options required for inline begin");
2219 let selector = transaction.selector.as_ref().expect("selector required");
2220 assert!(matches!(
2221 selector,
2222 v1::transaction_selector::Selector::Begin(_)
2223 ));
2224
2225 Ok(tonic::Response::new(v1::ResultSet {
2226 metadata: Some(v1::ResultSetMetadata {
2227 transaction: Some(v1::Transaction {
2228 id: vec![1, 2, 3],
2229 ..Default::default()
2230 }),
2231 ..Default::default()
2232 }),
2233 stats: Some(v1::ResultSetStats {
2234 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2235 ..Default::default()
2236 }),
2237 ..Default::default()
2238 }))
2239 });
2240 }
2241
2242 mock.expect_commit().once().returning(|req| {
2243 let req = req.into_inner();
2244 assert_eq!(
2245 req.max_commit_delay,
2246 Some(::prost_types::Duration {
2247 seconds: 0,
2248 nanos: 200_000_000, })
2250 );
2251 Ok(tonic::Response::new(v1::CommitResponse {
2252 commit_timestamp: Some(prost_types::Timestamp {
2253 seconds: 123456789,
2254 nanos: 0,
2255 }),
2256 ..Default::default()
2257 }))
2258 });
2259
2260 let (db_client, _server) = setup_db_client(mock).await;
2261
2262 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
2263 .set_max_commit_delay(Duration::new(0, 200_000_000).unwrap())
2264 .with_begin_transaction_option(begin_transaction_option)
2265 .build(None)
2266 .await
2267 .expect("Failed to build transaction");
2268
2269 if begin_transaction_option == BeginTransactionOption::InlineBegin {
2270 tx.execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
2271 .await?;
2272 }
2273
2274 let ts = tx.commit().await.expect("Failed to commit");
2275 assert_eq!(
2276 ts.commit_timestamp
2277 .expect("Commit timestamp should be present")
2278 .seconds(),
2279 123456789
2280 );
2281 Ok(())
2282 }
2283
2284 #[tokio_test_no_panics]
2285 async fn read_write_transaction_execute_update_no_fallback() {
2286 let mut mock = create_session_mock();
2287
2288 mock.expect_execute_sql().once().returning(|req| {
2290 let req = req.into_inner();
2291 assert_eq!(req.sql, "UPDATE Users SET Name = 'Alice' WHERE Id = 1");
2292
2293 let selector = req
2294 .transaction
2295 .expect("missing transaction selector")
2296 .selector
2297 .expect("missing selector");
2298 match selector {
2299 v1::transaction_selector::Selector::Begin(_) => {}
2300 _ => panic!("Expected Selector::Begin"),
2301 }
2302
2303 Err(tonic::Status::new(tonic::Code::Internal, "internal error"))
2304 });
2305
2306 let (db_client, _server) = setup_db_client(mock).await;
2307
2308 let tx = ReadWriteTransactionBuilder::new(db_client.clone())
2309 .build(None)
2310 .await
2311 .expect("Failed to build transaction");
2312
2313 let err = tx
2315 .execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 1")
2316 .await
2317 .expect_err("Expected update statement to fail");
2318 assert_eq!(err.status().unwrap().code, Code::Internal);
2319
2320 let err2 = tx
2322 .execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 2")
2323 .await
2324 .expect_err("Expected second statement to fail with Aborted");
2325 assert_eq!(err2.status().unwrap().code, Code::Aborted);
2326 assert_eq!(
2327 err2.status().unwrap().message,
2328 "Aborted due to failed initial statement"
2329 );
2330 }
2331
2332 #[tokio_test_no_panics]
2333 async fn read_write_transaction_execute_batch_update_no_fallback() -> anyhow::Result<()> {
2334 let mut mock = create_session_mock();
2335
2336 mock.expect_execute_batch_dml().once().returning(|req| {
2338 let req = req.into_inner();
2339 let selector = req
2340 .transaction
2341 .expect("missing transaction selector")
2342 .selector
2343 .expect("missing selector");
2344 match selector {
2345 v1::transaction_selector::Selector::Begin(_) => {}
2346 _ => panic!("Expected Selector::Begin"),
2347 }
2348
2349 Err(tonic::Status::new(tonic::Code::Internal, "internal error"))
2350 });
2351
2352 let (db_client, _server) = setup_db_client(mock).await;
2353
2354 let tx = ReadWriteTransactionBuilder::new(db_client)
2355 .build(None)
2356 .await?;
2357
2358 let batch =
2359 BatchDml::builder().add_statement("UPDATE Users SET Name = 'Alice' WHERE Id = 1");
2360
2361 let err = tx
2363 .execute_batch_update(batch.build())
2364 .await
2365 .expect_err("Expected batch update to fail");
2366 assert_eq!(err.status().unwrap().code, Code::Internal);
2367
2368 let err2 = tx
2370 .execute_update("UPDATE Users SET Name = 'Alice' WHERE Id = 2")
2371 .await
2372 .expect_err("Expected statement to fail with Aborted");
2373 assert_eq!(err2.status().unwrap().code, Code::Aborted);
2374
2375 Ok(())
2376 }
2377
2378 #[tokio_test_no_panics]
2379 async fn leader_aware_routing_enabled_by_default() -> anyhow::Result<()> {
2380 let mut mock = create_session_mock();
2381 mock.expect_begin_transaction().once().returning(|req| {
2382 assert_eq!(
2383 req.metadata()
2384 .get("x-goog-spanner-route-to-leader")
2385 .expect("header required")
2386 .to_str()
2387 .unwrap(),
2388 "true"
2389 );
2390 Ok(tonic::Response::new(v1::Transaction {
2391 id: vec![1, 2, 3],
2392 ..Default::default()
2393 }))
2394 });
2395 mock.expect_execute_sql().once().returning(|req| {
2396 assert_eq!(
2397 req.metadata()
2398 .get("x-goog-spanner-route-to-leader")
2399 .expect("header required")
2400 .to_str()
2401 .unwrap(),
2402 "true"
2403 );
2404 Ok(tonic::Response::new(v1::ResultSet {
2405 stats: Some(v1::ResultSetStats {
2406 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2407 ..Default::default()
2408 }),
2409 ..Default::default()
2410 }))
2411 });
2412 mock.expect_commit().once().returning(|req| {
2413 assert_eq!(
2414 req.metadata()
2415 .get("x-goog-spanner-route-to-leader")
2416 .expect("header required")
2417 .to_str()
2418 .unwrap(),
2419 "true"
2420 );
2421 Ok(tonic::Response::new(v1::CommitResponse {
2422 commit_timestamp: Some(prost_types::Timestamp {
2423 seconds: 1,
2424 nanos: 0,
2425 }),
2426 ..Default::default()
2427 }))
2428 });
2429
2430 let (db_client, _server) = setup_db_client(mock).await;
2431 let tx = ReadWriteTransactionBuilder::new(db_client)
2432 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
2433 .build(None)
2434 .await?;
2435 let count = tx.execute_update("UPDATE Users SET active = true").await?;
2436 assert_eq!(count, 1);
2437 let _ = tx.commit().await?;
2438 Ok(())
2439 }
2440
2441 #[tokio_test_no_panics]
2442 async fn leader_aware_routing_disabled() -> anyhow::Result<()> {
2443 use crate::client::Spanner;
2444 use google_cloud_auth::credentials::anonymous::Builder as Anonymous;
2445
2446 let mut mock = create_session_mock();
2447 mock.expect_begin_transaction().once().returning(|req| {
2448 assert!(
2449 req.metadata()
2450 .get("x-goog-spanner-route-to-leader")
2451 .is_none()
2452 );
2453 Ok(tonic::Response::new(v1::Transaction {
2454 id: vec![1, 2, 3],
2455 ..Default::default()
2456 }))
2457 });
2458 mock.expect_execute_sql().once().returning(|req| {
2459 assert!(
2460 req.metadata()
2461 .get("x-goog-spanner-route-to-leader")
2462 .is_none()
2463 );
2464 Ok(tonic::Response::new(v1::ResultSet {
2465 stats: Some(v1::ResultSetStats {
2466 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2467 ..Default::default()
2468 }),
2469 ..Default::default()
2470 }))
2471 });
2472 mock.expect_commit().once().returning(|req| {
2473 assert!(
2474 req.metadata()
2475 .get("x-goog-spanner-route-to-leader")
2476 .is_none()
2477 );
2478 Ok(tonic::Response::new(v1::CommitResponse {
2479 commit_timestamp: Some(prost_types::Timestamp {
2480 seconds: 1,
2481 nanos: 0,
2482 }),
2483 ..Default::default()
2484 }))
2485 });
2486
2487 let (address, _server) = spanner_grpc_mock::start("0.0.0.0:0", mock).await.unwrap();
2488 let spanner = Spanner::builder()
2489 .with_endpoint(address)
2490 .with_credentials(Anonymous::new().build())
2491 .build()
2492 .await?;
2493 let db_client = spanner
2494 .database_client("projects/p/instances/i/databases/d")
2495 .with_leader_aware_routing(false)
2496 .build()
2497 .await?;
2498
2499 let tx = ReadWriteTransactionBuilder::new(db_client)
2500 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
2501 .build(None)
2502 .await?;
2503 let count = tx.execute_update("UPDATE Users SET active = true").await?;
2504 assert_eq!(count, 1);
2505 let _ = tx.commit().await?;
2506 Ok(())
2507 }
2508
2509 #[tokio_test_no_panics]
2510 async fn leader_aware_routing_query_in_read_write() -> anyhow::Result<()> {
2511 let mut mock = create_session_mock();
2512 mock.expect_begin_transaction().once().returning(|req| {
2513 assert_eq!(
2514 req.metadata()
2515 .get("x-goog-spanner-route-to-leader")
2516 .expect("header required")
2517 .to_str()
2518 .unwrap(),
2519 "true"
2520 );
2521 Ok(tonic::Response::new(v1::Transaction {
2522 id: vec![1, 2, 3],
2523 ..Default::default()
2524 }))
2525 });
2526 mock.expect_execute_streaming_sql().once().returning(|req| {
2527 assert_eq!(
2528 req.metadata()
2529 .get("x-goog-spanner-route-to-leader")
2530 .expect("header required")
2531 .to_str()
2532 .unwrap(),
2533 "true"
2534 );
2535 let stream = adapt([Ok(v1::PartialResultSet {
2536 metadata: Some(v1::ResultSetMetadata {
2537 row_type: Some(v1::StructType { fields: vec![] }),
2538 ..Default::default()
2539 }),
2540 ..Default::default()
2541 })]);
2542 Ok(tonic::Response::from(stream))
2543 });
2544
2545 let (db_client, _server) = setup_db_client(mock).await;
2546 let tx = ReadWriteTransactionBuilder::new(db_client)
2547 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
2548 .build(None)
2549 .await?;
2550 let _rs = tx
2551 .execute_query(Statement::builder("SELECT 1").build())
2552 .await?;
2553 Ok(())
2554 }
2555
2556 #[tokio_test_no_panics]
2557 async fn leader_aware_routing_merges_custom_headers() -> anyhow::Result<()> {
2558 let mut mock = create_session_mock();
2559 mock.expect_begin_transaction().once().returning(|req| {
2560 assert_eq!(
2561 req.metadata()
2562 .get("x-goog-spanner-route-to-leader")
2563 .expect("header required")
2564 .to_str()
2565 .unwrap(),
2566 "true"
2567 );
2568 Ok(tonic::Response::new(v1::Transaction {
2569 id: vec![1, 2, 3],
2570 ..Default::default()
2571 }))
2572 });
2573 mock.expect_execute_sql().once().returning(|req| {
2574 assert_eq!(
2575 req.metadata()
2576 .get("x-goog-spanner-route-to-leader")
2577 .expect("header required")
2578 .to_str()
2579 .unwrap(),
2580 "true"
2581 );
2582 assert_eq!(
2583 req.metadata()
2584 .get("x-custom-user-header")
2585 .expect("custom header required")
2586 .to_str()
2587 .unwrap(),
2588 "custom-value"
2589 );
2590 Ok(tonic::Response::new(v1::ResultSet {
2591 stats: Some(v1::ResultSetStats {
2592 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2593 ..Default::default()
2594 }),
2595 ..Default::default()
2596 }))
2597 });
2598
2599 let (db_client, _server) = setup_db_client(mock).await;
2600 let tx = ReadWriteTransactionBuilder::new(db_client)
2601 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
2602 .build(None)
2603 .await?;
2604
2605 let mut custom_headers = http::HeaderMap::new();
2606 custom_headers.insert(
2607 "x-custom-user-header",
2608 http::HeaderValue::from_static("custom-value"),
2609 );
2610
2611 let mut stmt = Statement::builder("UPDATE Users SET active = true").build();
2612 let opts = stmt.gax_options().clone().insert_extension(custom_headers);
2613 stmt = stmt.with_gax_options(opts);
2614
2615 let count = tx.execute_update(stmt).await?;
2616 assert_eq!(count, 1);
2617 Ok(())
2618 }
2619
2620 #[tokio_test_no_panics]
2621 async fn leader_aware_routing_implicit_begin_runner_retry() -> anyhow::Result<()> {
2622 let mut mock = create_session_mock();
2623 let mut seq = mockall::Sequence::new();
2624
2625 mock.expect_execute_sql()
2628 .once()
2629 .in_sequence(&mut seq)
2630 .returning(|req| {
2631 assert_eq!(
2632 req.metadata()
2633 .get("x-goog-spanner-route-to-leader")
2634 .expect("header required on initial execute")
2635 .to_str()
2636 .unwrap(),
2637 "true"
2638 );
2639 let req = req.into_inner();
2640 let transaction = req.transaction.unwrap();
2641 assert!(matches!(
2642 transaction.selector.unwrap(),
2643 v1::transaction_selector::Selector::Begin(_)
2644 ));
2645 Err(tonic::Status::new(
2646 tonic::Code::Internal,
2647 "transient internal error",
2648 ))
2649 });
2650
2651 mock.expect_begin_transaction()
2655 .once()
2656 .in_sequence(&mut seq)
2657 .returning(|req| {
2658 assert_eq!(
2659 req.metadata()
2660 .get("x-goog-spanner-route-to-leader")
2661 .expect("header required on explicit begin fallback")
2662 .to_str()
2663 .unwrap(),
2664 "true"
2665 );
2666 Ok(tonic::Response::new(v1::Transaction {
2667 id: vec![42],
2668 ..Default::default()
2669 }))
2670 });
2671
2672 mock.expect_execute_sql()
2675 .once()
2676 .in_sequence(&mut seq)
2677 .returning(|req| {
2678 assert_eq!(
2679 req.metadata()
2680 .get("x-goog-spanner-route-to-leader")
2681 .expect("header required on retry execute")
2682 .to_str()
2683 .unwrap(),
2684 "true"
2685 );
2686 let req = req.into_inner();
2687 let transaction = req.transaction.unwrap();
2688 assert!(matches!(
2689 transaction.selector.unwrap(),
2690 v1::transaction_selector::Selector::Id(id) if id == vec![42]
2691 ));
2692 Ok(tonic::Response::new(v1::ResultSet {
2693 metadata: Some(v1::ResultSetMetadata {
2694 transaction: Some(v1::Transaction {
2695 id: vec![42],
2696 ..Default::default()
2697 }),
2698 ..Default::default()
2699 }),
2700 stats: Some(v1::ResultSetStats {
2701 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2702 ..Default::default()
2703 }),
2704 ..Default::default()
2705 }))
2706 });
2707
2708 mock.expect_commit().once().returning(|_req| {
2709 Ok(tonic::Response::new(v1::CommitResponse {
2710 commit_timestamp: Some(prost_types::Timestamp {
2711 seconds: 123,
2712 nanos: 0,
2713 }),
2714 ..Default::default()
2715 }))
2716 });
2717
2718 let (db_client, _server) = setup_db_client(mock).await;
2719
2720 let runner = db_client
2721 .read_write_transaction()
2722 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
2723 .build()
2724 .await?;
2725
2726 let attempt = Arc::new(AtomicU32::new(0));
2727 let att_clone = attempt.clone();
2728 let res = runner
2729 .run(async |tx| {
2730 let current_attempt = att_clone.fetch_add(1, Ordering::SeqCst) + 1;
2731 let res = tx.execute_update("UPDATE Users SET active = true").await;
2732 if current_attempt == 1 {
2733 assert!(res.is_err());
2734 let err = res.unwrap_err();
2735 assert_eq!(err.status().map(|s| s.code), Some(Code::Internal));
2736 Ok(())
2739 } else {
2740 assert_eq!(res.unwrap(), 1);
2741 Ok(())
2742 }
2743 })
2744 .await?;
2745
2746 assert!(res.commit_response.commit_timestamp.is_some());
2747 Ok(())
2748 }
2749
2750 #[tokio_test_no_panics]
2751 async fn read_write_transaction_tag_forwarded_on_runner_retry() -> anyhow::Result<()> {
2752 let mut mock = create_session_mock();
2753 let mut seq = mockall::Sequence::new();
2754
2755 mock.expect_execute_sql()
2758 .once()
2759 .in_sequence(&mut seq)
2760 .returning(|req| {
2761 let req = req.into_inner();
2762 assert_eq!(
2763 req.request_options
2764 .as_ref()
2765 .expect("Missing request_options on initial RPC")
2766 .transaction_tag,
2767 "fallback-test-tag"
2768 );
2769 assert!(matches!(
2770 req.transaction.unwrap().selector.unwrap(),
2771 v1::transaction_selector::Selector::Begin(_)
2772 ));
2773 Err(tonic::Status::new(
2774 tonic::Code::Internal,
2775 "transient internal error",
2776 ))
2777 });
2778
2779 mock.expect_begin_transaction()
2782 .once()
2783 .in_sequence(&mut seq)
2784 .returning(|req| {
2785 let req = req.into_inner();
2786 assert_eq!(
2787 req.request_options
2788 .as_ref()
2789 .expect("Missing request_options on explicit begin")
2790 .transaction_tag,
2791 "fallback-test-tag"
2792 );
2793 Ok(tonic::Response::new(v1::Transaction {
2794 id: vec![7, 7, 7],
2795 ..Default::default()
2796 }))
2797 });
2798
2799 mock.expect_execute_sql()
2802 .once()
2803 .in_sequence(&mut seq)
2804 .returning(|req| {
2805 let req = req.into_inner();
2806 assert_eq!(
2807 req.request_options
2808 .as_ref()
2809 .expect("Missing request_options on retried RPC")
2810 .transaction_tag,
2811 "fallback-test-tag"
2812 );
2813 assert!(matches!(
2814 req.transaction.unwrap().selector.unwrap(),
2815 v1::transaction_selector::Selector::Id(id) if id == vec![7, 7, 7]
2816 ));
2817 Ok(tonic::Response::new(v1::ResultSet {
2818 metadata: Some(v1::ResultSetMetadata {
2819 transaction: Some(v1::Transaction {
2820 id: vec![7, 7, 7],
2821 ..Default::default()
2822 }),
2823 ..Default::default()
2824 }),
2825 stats: Some(v1::ResultSetStats {
2826 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
2827 ..Default::default()
2828 }),
2829 ..Default::default()
2830 }))
2831 });
2832
2833 mock.expect_commit().once().returning(|req| {
2834 let req = req.into_inner();
2835 assert_eq!(
2836 req.request_options
2837 .as_ref()
2838 .expect("Missing request_options on commit")
2839 .transaction_tag,
2840 "fallback-test-tag"
2841 );
2842 Ok(tonic::Response::new(v1::CommitResponse {
2843 commit_timestamp: Some(prost_types::Timestamp {
2844 seconds: 123,
2845 nanos: 0,
2846 }),
2847 ..Default::default()
2848 }))
2849 });
2850
2851 let (db_client, _server) = setup_db_client(mock).await;
2852 let runner = db_client
2853 .read_write_transaction()
2854 .set_transaction_tag("fallback-test-tag")
2855 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
2856 .build()
2857 .await?;
2858
2859 let attempt = Arc::new(AtomicU32::new(0));
2860 let att_clone = attempt.clone();
2861 let res = runner
2862 .run(async |tx| {
2863 let current_attempt = att_clone.fetch_add(1, Ordering::SeqCst) + 1;
2864 let res = tx.execute_update("UPDATE Users SET active = true").await;
2865 if current_attempt == 1 {
2866 assert!(res.is_err());
2867 let err = res.unwrap_err();
2868 assert_eq!(err.status().map(|s| s.code), Some(Code::Internal));
2869 Ok(())
2871 } else {
2872 assert_eq!(res.unwrap(), 1);
2873 Ok(())
2874 }
2875 })
2876 .await?;
2877
2878 assert!(res.commit_response.commit_timestamp.is_some());
2879 Ok(())
2880 }
2881
2882 #[tokio_test_no_panics]
2883 async fn read_write_transaction_mutation_only_inline_begin_commit() -> anyhow::Result<()> {
2884 let mut mock = create_session_mock();
2885
2886 mock.expect_begin_transaction().once().returning(|req| {
2888 let req = req.into_inner();
2889 assert_eq!(
2890 req.session,
2891 "projects/p/instances/i/databases/d/sessions/123"
2892 );
2893 assert!(
2894 req.mutation_key.is_some(),
2895 "mutation_key should be populated when starting transaction at commit time"
2896 );
2897 let key = req
2898 .mutation_key
2899 .as_ref()
2900 .expect("mutation_key is populated");
2901 assert!(
2902 key.operation.is_some(),
2903 "mutation_key should have an operation"
2904 );
2905 Ok(tonic::Response::new(v1::Transaction {
2906 id: vec![7, 7, 7],
2907 ..Default::default()
2908 }))
2909 });
2910
2911 mock.expect_commit().once().returning(|req| {
2912 let req = req.into_inner();
2913 assert_eq!(
2914 req.session,
2915 "projects/p/instances/i/databases/d/sessions/123"
2916 );
2917 assert_eq!(
2918 req.transaction,
2919 Some(v1::commit_request::Transaction::TransactionId(vec![
2920 7, 7, 7
2921 ]))
2922 );
2923 assert_eq!(req.mutations.len(), 1);
2924 Ok(tonic::Response::new(v1::CommitResponse {
2925 commit_timestamp: Some(prost_types::Timestamp {
2926 seconds: 5000,
2927 nanos: 0,
2928 }),
2929 ..Default::default()
2930 }))
2931 });
2932
2933 let (db_client, _server) = setup_db_client(mock).await;
2934 let tx = ReadWriteTransactionBuilder::new(db_client)
2935 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2936 .build(None)
2937 .await
2938 .expect("Transaction build should succeed");
2939
2940 let mutation = Mutation::new_insert_builder("Users")
2941 .set("UserId")
2942 .to(&1)
2943 .build();
2944 tx.buffer([mutation]).expect("Buffer should succeed");
2945
2946 let response = tx.commit().await.expect("Commit should succeed");
2947 assert_eq!(
2948 response
2949 .commit_timestamp
2950 .expect("timestamp present")
2951 .seconds(),
2952 5000
2953 );
2954 Ok(())
2955 }
2956
2957 #[tokio_test_no_panics]
2958 async fn transaction_runner_batch_dml_aborted_retry() -> anyhow::Result<()> {
2959 let mut mock = create_session_mock();
2960 let mut sequence = mockall::Sequence::new();
2961
2962 mock.expect_execute_batch_dml()
2964 .times(1)
2965 .in_sequence(&mut sequence)
2966 .returning(|req| {
2967 let req = req.into_inner();
2968 assert!(matches!(
2969 req.transaction.unwrap().selector.unwrap(),
2970 v1::transaction_selector::Selector::Begin(_)
2971 ));
2972 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
2973 result_sets: vec![],
2974 status: Some(spanner_grpc_mock::google::rpc::Status {
2975 code: tonic::Code::Aborted as i32,
2976 message: "concurrent lock abort".into(),
2977 details: vec![],
2978 }),
2979 ..Default::default()
2980 }))
2981 });
2982
2983 mock.expect_execute_batch_dml()
2985 .times(1)
2986 .in_sequence(&mut sequence)
2987 .returning(|req| {
2988 let req = req.into_inner();
2989 assert!(matches!(
2990 req.transaction.unwrap().selector.unwrap(),
2991 v1::transaction_selector::Selector::Begin(_)
2992 ));
2993 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
2994 result_sets: vec![v1::ResultSet {
2995 metadata: Some(v1::ResultSetMetadata {
2996 transaction: Some(v1::Transaction {
2997 id: vec![9, 9, 9],
2998 ..Default::default()
2999 }),
3000 ..Default::default()
3001 }),
3002 stats: Some(v1::ResultSetStats {
3003 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
3004 ..Default::default()
3005 }),
3006 ..Default::default()
3007 }],
3008 status: Some(spanner_grpc_mock::google::rpc::Status {
3009 code: 0,
3010 message: "OK".into(),
3011 details: vec![],
3012 }),
3013 ..Default::default()
3014 }))
3015 });
3016
3017 mock.expect_commit().once().returning(|req| {
3018 let req = req.into_inner();
3019 assert_eq!(
3020 req.transaction,
3021 Some(v1::commit_request::Transaction::TransactionId(vec![
3022 9, 9, 9
3023 ]))
3024 );
3025 Ok(tonic::Response::new(v1::CommitResponse {
3026 commit_timestamp: Some(prost_types::Timestamp {
3027 seconds: 999,
3028 nanos: 0,
3029 }),
3030 ..Default::default()
3031 }))
3032 });
3033
3034 let (db_client, _server) = setup_db_client(mock).await;
3035
3036 let runner = db_client
3037 .read_write_transaction()
3038 .with_retry_policy(
3039 BasicTransactionRetryPolicy::new()
3040 .with_max_attempts(3)
3041 .with_total_timeout(std::time::Duration::from_secs(5)),
3042 )
3043 .build()
3044 .await?;
3045
3046 runner
3047 .run(async |tx| {
3048 let batch = BatchDml::builder()
3049 .add_statement("UPDATE Users SET active = true WHERE id = 1");
3050 tx.execute_batch_update(batch.build()).await?;
3051 Ok(())
3052 })
3053 .await?;
3054
3055 Ok(())
3056 }
3057
3058 #[tokio_test_no_panics]
3059 async fn read_write_transaction_first_dml_aborted_and_continue_success() -> anyhow::Result<()> {
3060 let mut mock = create_session_mock();
3061 let mut sequence = mockall::Sequence::new();
3062
3063 mock.expect_execute_sql()
3065 .times(1)
3066 .in_sequence(&mut sequence)
3067 .returning(|req| {
3068 let req = req.into_inner();
3069 assert!(matches!(
3070 req.transaction.unwrap().selector.unwrap(),
3071 v1::transaction_selector::Selector::Begin(_)
3072 ));
3073 Err(tonic::Status::new(
3074 tonic::Code::Aborted,
3075 "concurrent lock abort",
3076 ))
3077 });
3078
3079 mock.expect_execute_sql()
3081 .times(1)
3082 .in_sequence(&mut sequence)
3083 .returning(|req| {
3084 let req = req.into_inner();
3085 assert!(matches!(
3086 req.transaction.unwrap().selector.unwrap(),
3087 v1::transaction_selector::Selector::Begin(_)
3088 ));
3089 Ok(tonic::Response::new(v1::ResultSet {
3090 metadata: Some(v1::ResultSetMetadata {
3091 transaction: Some(v1::Transaction {
3092 id: vec![9, 9, 9],
3093 ..Default::default()
3094 }),
3095 ..Default::default()
3096 }),
3097 stats: Some(v1::ResultSetStats {
3098 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
3099 ..Default::default()
3100 }),
3101 ..Default::default()
3102 }))
3103 });
3104
3105 mock.expect_commit().once().returning(|req| {
3107 let req = req.into_inner();
3108 assert_eq!(
3109 req.transaction,
3110 Some(v1::commit_request::Transaction::TransactionId(vec![
3111 9, 9, 9
3112 ]))
3113 );
3114 Ok(tonic::Response::new(v1::CommitResponse {
3115 commit_timestamp: Some(prost_types::Timestamp {
3116 seconds: 999,
3117 nanos: 0,
3118 }),
3119 ..Default::default()
3120 }))
3121 });
3122
3123 let (db_client, _server) = setup_db_client(mock).await;
3124
3125 let runner = db_client
3126 .read_write_transaction()
3127 .with_retry_policy(
3128 BasicTransactionRetryPolicy::new()
3129 .with_max_attempts(1)
3130 .with_total_timeout(std::time::Duration::from_secs(5)),
3131 )
3132 .build()
3133 .await?;
3134
3135 runner
3136 .run(async |tx| {
3137 let res = tx
3139 .execute_update("UPDATE Users SET active = true WHERE id = 1")
3140 .await;
3141 assert!(res.is_err(), "First statement must return error");
3142 assert!(is_aborted(&res.unwrap_err()), "Error must be Aborted");
3143
3144 let count = tx
3146 .execute_update("UPDATE Users SET active = true WHERE id = 2")
3147 .await?;
3148 assert_eq!(count, 1);
3149 Ok(())
3150 })
3151 .await?;
3152
3153 Ok(())
3154 }
3155
3156 #[tokio_test_no_panics]
3157 async fn read_write_transaction_first_batch_dml_aborted_and_continue_success()
3158 -> anyhow::Result<()> {
3159 let mut mock = create_session_mock();
3160 let mut sequence = mockall::Sequence::new();
3161
3162 mock.expect_execute_batch_dml()
3164 .times(1)
3165 .in_sequence(&mut sequence)
3166 .returning(|req| {
3167 let req = req.into_inner();
3168 assert!(matches!(
3169 req.transaction.unwrap().selector.unwrap(),
3170 v1::transaction_selector::Selector::Begin(_)
3171 ));
3172 Err(tonic::Status::new(
3173 tonic::Code::Aborted,
3174 "concurrent lock abort",
3175 ))
3176 });
3177
3178 mock.expect_execute_sql()
3180 .times(1)
3181 .in_sequence(&mut sequence)
3182 .returning(|req| {
3183 let req = req.into_inner();
3184 assert!(matches!(
3185 req.transaction.unwrap().selector.unwrap(),
3186 v1::transaction_selector::Selector::Begin(_)
3187 ));
3188 Ok(tonic::Response::new(v1::ResultSet {
3189 metadata: Some(v1::ResultSetMetadata {
3190 transaction: Some(v1::Transaction {
3191 id: vec![9, 9, 9],
3192 ..Default::default()
3193 }),
3194 ..Default::default()
3195 }),
3196 stats: Some(v1::ResultSetStats {
3197 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
3198 ..Default::default()
3199 }),
3200 ..Default::default()
3201 }))
3202 });
3203
3204 mock.expect_commit().once().returning(|req| {
3206 let req = req.into_inner();
3207 assert_eq!(
3208 req.transaction,
3209 Some(v1::commit_request::Transaction::TransactionId(vec![
3210 9, 9, 9
3211 ]))
3212 );
3213 Ok(tonic::Response::new(v1::CommitResponse {
3214 commit_timestamp: Some(prost_types::Timestamp {
3215 seconds: 999,
3216 nanos: 0,
3217 }),
3218 ..Default::default()
3219 }))
3220 });
3221
3222 let (db_client, _server) = setup_db_client(mock).await;
3223
3224 let runner = db_client
3225 .read_write_transaction()
3226 .with_retry_policy(
3227 BasicTransactionRetryPolicy::new()
3228 .with_max_attempts(1)
3229 .with_total_timeout(std::time::Duration::from_secs(5)),
3230 )
3231 .build()
3232 .await?;
3233
3234 runner
3235 .run(async |tx| {
3236 let batch = BatchDml::builder()
3238 .add_statement("UPDATE Users SET active = true WHERE id = 1");
3239 let res = tx.execute_batch_update(batch.build()).await;
3240 assert!(res.is_err(), "First statement must return error");
3241 assert!(is_aborted(&res.unwrap_err()), "Error must be Aborted");
3242
3243 let count = tx
3245 .execute_update("UPDATE Users SET active = true WHERE id = 2")
3246 .await?;
3247 assert_eq!(count, 1);
3248 Ok(())
3249 })
3250 .await?;
3251
3252 Ok(())
3253 }
3254
3255 #[tokio_test_no_panics]
3256 async fn read_write_transaction_first_query_failed_and_continue_retry() -> anyhow::Result<()> {
3257 let mut mock = create_session_mock();
3258 let mut sequence = mockall::Sequence::new();
3259
3260 mock.expect_execute_streaming_sql()
3262 .once()
3263 .in_sequence(&mut sequence)
3264 .returning(|_| {
3265 Err(tonic::Status::new(
3266 tonic::Code::Internal,
3267 "transient internal error",
3268 ))
3269 });
3270
3271 mock.expect_begin_transaction()
3274 .once()
3275 .in_sequence(&mut sequence)
3276 .returning(|_| {
3277 Ok(tonic::Response::new(v1::Transaction {
3278 id: vec![42],
3279 ..Default::default()
3280 }))
3281 });
3282
3283 mock.expect_execute_streaming_sql()
3285 .once()
3286 .in_sequence(&mut sequence)
3287 .returning(|_| {
3288 let rx = adapt([Ok(v1::PartialResultSet {
3289 metadata: Some(v1::ResultSetMetadata {
3290 row_type: Some(v1::StructType {
3291 fields: vec![v1::struct_type::Field {
3292 name: "Name".to_string(),
3293 r#type: Some(v1::Type {
3294 code: v1::TypeCode::String as i32,
3295 ..Default::default()
3296 }),
3297 }],
3298 }),
3299 transaction: Some(v1::Transaction {
3300 id: vec![42],
3301 ..Default::default()
3302 }),
3303 ..Default::default()
3304 }),
3305 values: vec![string_val("alice")],
3306 ..Default::default()
3307 })]);
3308 Ok(tonic::Response::from(rx))
3309 });
3310
3311 mock.expect_commit().once().returning(|_| {
3312 Ok(tonic::Response::new(v1::CommitResponse {
3313 commit_timestamp: Some(prost_types::Timestamp {
3314 seconds: 1234,
3315 nanos: 0,
3316 }),
3317 ..Default::default()
3318 }))
3319 });
3320
3321 let (db_client, _server) = setup_db_client(mock).await;
3322 let runner = db_client
3323 .read_write_transaction()
3324 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
3325 .build()
3326 .await?;
3327
3328 let attempt = Arc::new(AtomicU32::new(0));
3329 let att_clone = attempt.clone();
3330 let res = runner
3331 .run(async |tx| {
3332 let current_attempt = att_clone.fetch_add(1, Ordering::SeqCst) + 1;
3333 let res = tx
3334 .execute_query("SELECT Name FROM Users WHERE Id = 1")
3335 .await;
3336 if current_attempt == 1 {
3337 assert!(res.is_err());
3338 let err = res.unwrap_err();
3339 assert_eq!(err.status().map(|s| s.code), Some(Code::Internal));
3340 Ok(())
3342 } else {
3343 let mut rs = res.unwrap();
3344 let row = rs.next().await.unwrap().unwrap();
3345 assert_eq!(row.get::<String, _>(0), "alice");
3346 Ok(())
3347 }
3348 })
3349 .await?;
3350
3351 assert!(res.commit_response.commit_timestamp.is_some());
3352 Ok(())
3353 }
3354
3355 #[tokio_test_no_panics]
3356 async fn read_write_transaction_first_query_failed_propagate_no_retry() -> anyhow::Result<()> {
3357 let mut mock = create_session_mock();
3358
3359 mock.expect_execute_streaming_sql().once().returning(|_| {
3361 Err(tonic::Status::new(
3362 tonic::Code::Internal,
3363 "transient internal error",
3364 ))
3365 });
3366
3367 let (db_client, _server) = setup_db_client(mock).await;
3368 let runner = db_client
3369 .read_write_transaction()
3370 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
3371 .build()
3372 .await?;
3373
3374 let res = runner
3375 .run(async |tx| {
3376 let _ = tx
3377 .execute_query("SELECT Name FROM Users WHERE Id = 1")
3378 .await?;
3379 Ok(())
3380 })
3381 .await;
3382
3383 assert!(res.is_err());
3384 let err = res.unwrap_err();
3385 assert_eq!(err.status().map(|s| s.code), Some(Code::Internal));
3386 Ok(())
3387 }
3388
3389 #[tokio_test_no_panics]
3390 async fn read_write_transaction_query_failed_after_id_received_no_retry() -> anyhow::Result<()>
3391 {
3392 let mut mock = create_session_mock();
3393 let mut sequence = mockall::Sequence::new();
3394
3395 mock.expect_execute_streaming_sql()
3397 .once()
3398 .in_sequence(&mut sequence)
3399 .returning(|_| {
3400 let rx = adapt([
3401 Ok(v1::PartialResultSet {
3402 metadata: Some(v1::ResultSetMetadata {
3403 row_type: Some(v1::StructType {
3404 fields: vec![v1::struct_type::Field {
3405 name: "Name".to_string(),
3406 r#type: Some(v1::Type {
3407 code: v1::TypeCode::String as i32,
3408 ..Default::default()
3409 }),
3410 }],
3411 }),
3412 transaction: Some(v1::Transaction {
3413 id: vec![42],
3414 ..Default::default()
3415 }),
3416 ..Default::default()
3417 }),
3418 values: vec![string_val("alice")],
3419 resume_token: b"resume-1".to_vec(),
3420 ..Default::default()
3421 }),
3422 Err(tonic::Status::new(tonic::Code::Internal, "stream broken")),
3423 ]);
3424 Ok(tonic::Response::from(rx))
3425 });
3426
3427 mock.expect_commit()
3429 .once()
3430 .in_sequence(&mut sequence)
3431 .returning(|req| {
3432 let req = req.into_inner();
3433 assert_eq!(
3434 req.transaction,
3435 Some(v1::commit_request::Transaction::TransactionId(vec![42]))
3436 );
3437 Err(tonic::Status::new(tonic::Code::Internal, "commit failed"))
3438 });
3439
3440 let (db_client, _server) = setup_db_client(mock).await;
3441 let runner = db_client
3442 .read_write_transaction()
3443 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
3444 .build()
3445 .await?;
3446
3447 let attempt = Arc::new(AtomicU32::new(0));
3448 let att_clone = attempt.clone();
3449 let res = runner
3450 .run(async |tx| {
3451 let _attempt = att_clone.fetch_add(1, Ordering::SeqCst) + 1;
3452 let mut rs = tx
3453 .execute_query("SELECT Name FROM Users WHERE Id = 1")
3454 .await?;
3455 let row = rs.next().await.unwrap().unwrap();
3457 assert_eq!(row.get::<String, _>(0), "alice");
3458 let next_res = rs.next().await.unwrap();
3460 assert!(next_res.is_err());
3461 assert_eq!(
3462 next_res.unwrap_err().status().map(|s| s.code),
3463 Some(Code::Internal)
3464 );
3465 Ok(())
3467 })
3468 .await;
3469
3470 assert!(res.is_err());
3471 let err = res.unwrap_err();
3472 assert_eq!(err.status().map(|s| s.code), Some(Code::Internal));
3473 assert_eq!(
3474 attempt.load(Ordering::SeqCst),
3475 1,
3476 "Should not retry since transaction ID was received"
3477 );
3478 Ok(())
3479 }
3480
3481 #[tokio_test_no_panics]
3482 async fn read_write_transaction_batch_dml_failed_after_id_received_no_retry()
3483 -> anyhow::Result<()> {
3484 let mut mock = create_session_mock();
3485 let mut sequence = mockall::Sequence::new();
3486
3487 mock.expect_execute_batch_dml()
3490 .once()
3491 .in_sequence(&mut sequence)
3492 .returning(|_| {
3493 Ok(tonic::Response::new(v1::ExecuteBatchDmlResponse {
3494 result_sets: vec![v1::ResultSet {
3495 metadata: Some(v1::ResultSetMetadata {
3496 transaction: Some(v1::Transaction {
3497 id: vec![42],
3498 ..Default::default()
3499 }),
3500 ..Default::default()
3501 }),
3502 stats: Some(v1::ResultSetStats {
3503 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
3504 ..Default::default()
3505 }),
3506 ..Default::default()
3507 }],
3508 status: Some(spanner_grpc_mock::google::rpc::Status {
3509 code: tonic::Code::InvalidArgument as i32,
3510 message: "invalid argument on statement 2".into(),
3511 details: vec![],
3512 }),
3513 ..Default::default()
3514 }))
3515 });
3516
3517 mock.expect_commit()
3519 .once()
3520 .in_sequence(&mut sequence)
3521 .returning(|req| {
3522 let req = req.into_inner();
3523 assert_eq!(
3524 req.transaction,
3525 Some(v1::commit_request::Transaction::TransactionId(vec![42]))
3526 );
3527 Err(tonic::Status::new(tonic::Code::Internal, "commit failed"))
3528 });
3529
3530 let (db_client, _server) = setup_db_client(mock).await;
3531 let runner = db_client
3532 .read_write_transaction()
3533 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
3534 .build()
3535 .await?;
3536
3537 let attempt = Arc::new(AtomicU32::new(0));
3538 let att_clone = attempt.clone();
3539 let res = runner
3540 .run(async |tx| {
3541 let _attempt = att_clone.fetch_add(1, Ordering::SeqCst) + 1;
3542 let batch = BatchDml::builder()
3543 .add_statement("UPDATE Users SET active = true WHERE id = 1")
3544 .add_statement("UPDATE Users SET active = true WHERE id = invalid_val");
3545 let res = tx.execute_batch_update(batch.build()).await;
3546 assert!(res.is_err());
3547 let err = res.unwrap_err();
3548 assert!(crate::error::BatchUpdateError::extract(&err).is_some());
3549 Ok(())
3551 })
3552 .await;
3553
3554 assert!(res.is_err());
3555 let err = res.unwrap_err();
3556 assert_eq!(err.status().map(|s| s.code), Some(Code::Internal));
3557 assert_eq!(
3558 attempt.load(Ordering::SeqCst),
3559 1,
3560 "Should not retry since transaction ID was received"
3561 );
3562 Ok(())
3563 }
3564
3565 fn parse_grpc_timeout(metadata: &MetadataMap) -> Option<StdDuration> {
3566 let timeout_header = metadata.get("grpc-timeout")?.to_str().ok()?;
3567 let numeric_part: String = timeout_header
3568 .chars()
3569 .take_while(|c| c.is_ascii_digit())
3570 .collect();
3571 let value = numeric_part.parse::<u64>().ok()?;
3572 let unit = timeout_header.trim_start_matches(&numeric_part);
3573 let duration = match unit {
3574 "u" => StdDuration::from_micros(value),
3575 "m" => StdDuration::from_millis(value),
3576 "S" => StdDuration::from_secs(value),
3577 "M" => StdDuration::from_secs(value * 60),
3578 "H" => StdDuration::from_secs(value * 3600),
3579 _ => return None,
3580 };
3581 Some(duration)
3582 }
3583
3584 #[tokio_test_no_panics]
3585 async fn read_write_transaction_lazy_begin_never_retry() -> anyhow::Result<()> {
3586 let mut mock = create_session_mock();
3587 let mut sequence = mockall::Sequence::new();
3588
3589 mock.expect_execute_sql()
3591 .once()
3592 .in_sequence(&mut sequence)
3593 .withf(|req| {
3594 matches!(
3595 req.get_ref()
3596 .transaction
3597 .as_ref()
3598 .and_then(|t| t.selector.as_ref()),
3599 Some(v1::transaction_selector::Selector::Begin(_))
3600 )
3601 })
3602 .returning(move |_req| Err(tonic::Status::unavailable("transient error")));
3603
3604 mock.expect_begin_transaction()
3608 .once()
3609 .in_sequence(&mut sequence)
3610 .returning(move |_req| Err(tonic::Status::unavailable("transient error")));
3611
3612 let (db_client, _server) = setup_db_client(mock).await;
3613
3614 let runner = db_client
3615 .read_write_transaction()
3616 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
3617 .with_begin_retry_policy(NeverRetry)
3618 .build()
3619 .await?;
3620
3621 let res = runner
3622 .run(async |tx| {
3623 let mut stmt_opts = crate::RequestOptions::default();
3624 stmt_opts.set_retry_policy(NeverRetry);
3625 let stmt = Statement::builder("UPDATE Users SET active = true WHERE id = 1")
3626 .build()
3627 .with_gax_options(stmt_opts);
3628 let res = tx.execute_update(stmt).await;
3629 assert!(res.is_err());
3630 let err = res.unwrap_err();
3631 assert_eq!(err.status().map(|s| s.code), Some(Code::Unavailable));
3632 Ok(())
3634 })
3635 .await;
3636
3637 assert!(
3638 res.is_err(),
3639 "Should fail because explicit BeginTransaction on retry failed with Unavailable and NeverRetry was configured"
3640 );
3641 let err = res.unwrap_err();
3642 assert_eq!(err.status().map(|s| s.code), Some(Code::Unavailable));
3643
3644 Ok(())
3645 }
3646
3647 #[tokio_test_no_panics]
3648 async fn read_write_transaction_commit_under_deadline_delegates_to_custom_retry_policy()
3649 -> anyhow::Result<()> {
3650 let mut mock = create_session_mock();
3651 mock.expect_begin_transaction().once().returning(|_| {
3652 Ok(tonic::Response::new(v1::Transaction {
3653 id: vec![8, 8, 8],
3654 ..Default::default()
3655 }))
3656 });
3657
3658 mock.expect_commit()
3660 .once()
3661 .returning(|_| Err(tonic::Status::unavailable("transient error")));
3662
3663 let (db_client, _server) = setup_db_client(mock).await;
3664
3665 let runner = db_client
3666 .read_write_transaction()
3667 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
3668 .with_commit_retry_policy(NeverRetry)
3669 .with_transaction_timeout(StdDuration::from_secs(5))
3670 .build()
3671 .await?;
3672
3673 let res = runner.run(async |_tx| Ok(())).await;
3674
3675 assert!(
3676 res.is_err(),
3677 "Should fail because NeverRetry aborted retries"
3678 );
3679 let err = res.unwrap_err();
3680 assert_eq!(
3681 err.status().map(|s| s.code),
3682 Some(Code::Unavailable),
3683 "Error code should be Unavailable"
3684 );
3685 Ok(())
3686 }
3687
3688 #[tokio_test_no_panics]
3689 async fn read_write_transaction_commit_timeout_combination() -> anyhow::Result<()> {
3690 let mut mock = create_session_mock();
3691 mock.expect_begin_transaction().once().returning(|_| {
3692 Ok(tonic::Response::new(v1::Transaction {
3693 id: vec![8, 8, 8],
3694 ..Default::default()
3695 }))
3696 });
3697
3698 mock.expect_commit()
3700 .once()
3701 .withf(|req| {
3702 let duration =
3703 parse_grpc_timeout(req.metadata()).expect("valid grpc-timeout header");
3704 assert_eq!(
3705 duration,
3706 StdDuration::from_secs(2),
3707 "Timeout duration should be exactly 2 seconds"
3708 );
3709 true
3710 })
3711 .returning(|_| {
3712 Ok(tonic::Response::new(v1::CommitResponse {
3713 commit_timestamp: Some(Timestamp {
3714 seconds: 999,
3715 nanos: 0,
3716 }),
3717 ..Default::default()
3718 }))
3719 });
3720
3721 let (db_client, _server) = setup_db_client(mock).await;
3722
3723 let runner = db_client
3724 .read_write_transaction()
3725 .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
3726 .with_commit_attempt_timeout(StdDuration::from_secs(2))
3727 .with_transaction_timeout(StdDuration::from_secs(10))
3728 .build()
3729 .await?;
3730
3731 let res = runner.run(async |_tx| Ok(())).await?;
3732
3733 assert!(res.commit_response.commit_timestamp.is_some());
3734 Ok(())
3735 }
3736
3737 #[tokio_test_no_panics]
3738 async fn read_write_transaction_fallback_begin_under_deadline() -> anyhow::Result<()> {
3739 let mut mock = create_session_mock();
3740 let mut sequence = mockall::Sequence::new();
3741
3742 mock.expect_execute_sql()
3744 .once()
3745 .in_sequence(&mut sequence)
3746 .withf(|req| {
3747 matches!(
3748 req.get_ref()
3749 .transaction
3750 .as_ref()
3751 .and_then(|t| t.selector.as_ref()),
3752 Some(v1::transaction_selector::Selector::Begin(_))
3753 )
3754 })
3755 .returning(move |_req| Err(tonic::Status::unavailable("transient error")));
3756
3757 mock.expect_begin_transaction()
3761 .once()
3762 .in_sequence(&mut sequence)
3763 .withf(|req| {
3764 let duration =
3765 parse_grpc_timeout(req.metadata()).expect("valid grpc-timeout header");
3766 assert!(
3767 duration >= StdDuration::from_millis(4000)
3768 && duration <= StdDuration::from_millis(6000),
3769 "Retry begin timeout is wrong: {:?}",
3770 duration
3771 );
3772 true
3773 })
3774 .returning(move |_req| {
3775 Ok(tonic::Response::new(v1::Transaction {
3776 id: vec![42],
3777 ..Default::default()
3778 }))
3779 });
3780
3781 mock.expect_execute_sql()
3783 .once()
3784 .in_sequence(&mut sequence)
3785 .withf(|req| {
3786 matches!(
3787 req.get_ref()
3788 .transaction
3789 .as_ref()
3790 .and_then(|t| t.selector.as_ref()),
3791 Some(v1::transaction_selector::Selector::Id(id)) if id == &vec![42]
3792 )
3793 })
3794 .returning(move |_req| {
3795 Ok(tonic::Response::new(v1::ResultSet {
3796 metadata: Some(v1::ResultSetMetadata {
3797 transaction: Some(v1::Transaction {
3798 id: vec![42],
3799 ..Default::default()
3800 }),
3801 ..Default::default()
3802 }),
3803 stats: Some(v1::ResultSetStats {
3804 row_count: Some(v1::result_set_stats::RowCount::RowCountExact(1)),
3805 ..Default::default()
3806 }),
3807 ..Default::default()
3808 }))
3809 });
3810
3811 mock.expect_commit()
3813 .once()
3814 .in_sequence(&mut sequence)
3815 .returning(move |_req| {
3816 Ok(tonic::Response::new(v1::CommitResponse {
3817 commit_timestamp: Some(Timestamp {
3818 seconds: 1234,
3819 nanos: 0,
3820 }),
3821 ..Default::default()
3822 }))
3823 });
3824
3825 let (db_client, _server) = setup_db_client(mock).await;
3826
3827 let runner = db_client
3828 .read_write_transaction()
3829 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
3830 .with_retry_policy(BasicTransactionRetryPolicy::new().with_max_attempts(2))
3831 .with_transaction_timeout(StdDuration::from_secs(5))
3832 .build()
3833 .await?;
3834
3835 let attempt = Arc::new(AtomicU32::new(0));
3836 let att_clone = attempt.clone();
3837 let res = runner
3838 .run(async |tx| {
3839 let current_attempt = att_clone.fetch_add(1, Ordering::SeqCst) + 1;
3840 let mut query_opts = crate::RequestOptions::default();
3841 query_opts.set_retry_policy(NeverRetry);
3842 let stmt = Statement::builder("UPDATE Users SET active = true WHERE id = 1")
3843 .build()
3844 .with_gax_options(query_opts);
3845 let res = tx.execute_update(stmt).await;
3846 if current_attempt == 1 {
3847 assert!(res.is_err());
3848 let err = res.unwrap_err();
3849 assert_eq!(err.status().map(|s| s.code), Some(Code::Unavailable));
3850 Ok(())
3852 } else {
3853 assert_eq!(res.unwrap(), 1);
3854 Ok(())
3855 }
3856 })
3857 .await?;
3858
3859 assert!(res.commit_response.commit_timestamp.is_some());
3860 Ok(())
3861 }
3862
3863 #[tokio_test_no_panics]
3864 async fn read_write_transaction_commit_fallback_begin_under_deadline() -> anyhow::Result<()> {
3865 let mut mock = create_session_mock();
3866 let mut sequence = mockall::Sequence::new();
3867
3868 mock.expect_begin_transaction()
3871 .once()
3872 .in_sequence(&mut sequence)
3873 .withf(|req| {
3874 let duration =
3875 parse_grpc_timeout(req.metadata()).expect("valid grpc-timeout header");
3876 assert!(
3877 duration >= StdDuration::from_millis(4000)
3878 && duration <= StdDuration::from_millis(6000),
3879 "Fallback begin timeout inside commit is wrong: {:?}",
3880 duration
3881 );
3882 true
3883 })
3884 .returning(move |_req| {
3885 Ok(tonic::Response::new(v1::Transaction {
3886 id: vec![42],
3887 ..Default::default()
3888 }))
3889 });
3890
3891 mock.expect_commit()
3893 .once()
3894 .in_sequence(&mut sequence)
3895 .returning(move |_req| {
3896 Ok(tonic::Response::new(v1::CommitResponse {
3897 commit_timestamp: Some(Timestamp {
3898 seconds: 5678,
3899 nanos: 0,
3900 }),
3901 ..Default::default()
3902 }))
3903 });
3904
3905 let (db_client, _server) = setup_db_client(mock).await;
3906
3907 let runner = db_client
3908 .read_write_transaction()
3909 .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
3910 .with_transaction_timeout(StdDuration::from_secs(5))
3911 .build()
3912 .await?;
3913
3914 let res = runner.run(async |_tx| Ok(())).await?;
3915
3916 assert!(res.commit_response.commit_timestamp.is_some());
3917 Ok(())
3918 }
3919
3920 #[test]
3921 fn test_amend_gax_options() {
3922 let mut options = RequestOptions::default();
3924 options.set_attempt_timeout(StdDuration::from_secs(4));
3925 amend_gax_options(false, None, &mut options);
3926 assert_eq!(*options.attempt_timeout(), Some(StdDuration::from_secs(4)));
3927 assert!(options.retry_policy().is_none());
3928
3929 let mut options = RequestOptions::default();
3931 amend_gax_options(true, None, &mut options);
3932 let headers = options
3934 .get_extension::<HeaderMap>()
3935 .expect("HeaderMap extension missing");
3936 assert_eq!(
3937 headers
3938 .get("x-goog-spanner-route-to-leader")
3939 .unwrap()
3940 .to_str()
3941 .unwrap(),
3942 "true"
3943 );
3944
3945 let mut options = RequestOptions::default();
3950 let deadline = Instant::now() + StdDuration::from_secs(5);
3951 amend_gax_options(false, Some(deadline), &mut options);
3952 let timeout = options.attempt_timeout().expect("attempt timeout missing");
3953 assert!(
3954 timeout >= StdDuration::from_millis(4500) && timeout <= StdDuration::from_millis(5500)
3955 );
3956 assert!(
3957 options.retry_policy().is_some(),
3958 "retry policy should be wrapped"
3959 );
3960
3961 let mut options = RequestOptions::default();
3965 options.set_attempt_timeout(StdDuration::from_secs(2));
3966 let deadline = Instant::now() + StdDuration::from_secs(10);
3967 amend_gax_options(false, Some(deadline), &mut options);
3968 assert_eq!(*options.attempt_timeout(), Some(StdDuration::from_secs(2)));
3969
3970 let mut options = RequestOptions::default();
3974 options.set_attempt_timeout(StdDuration::from_secs(10));
3975 let deadline = Instant::now() + StdDuration::from_secs(2);
3976 amend_gax_options(false, Some(deadline), &mut options);
3977 let timeout = options.attempt_timeout().expect("attempt timeout missing");
3978 assert!(
3979 timeout >= StdDuration::from_millis(1500) && timeout <= StdDuration::from_millis(2500)
3980 );
3981 }
3982
3983 #[test]
3984 fn test_transaction_bounded_retry_policy_throttle_delegation() {
3985 #[derive(Debug)]
3986 struct ThrottleTestPolicy;
3987 impl RetryPolicy for ThrottleTestPolicy {
3988 fn on_error(&self, _state: &RetryState, error: GaxError) -> RetryResult {
3989 RetryResult::Continue(error)
3990 }
3991 fn on_throttle(&self, _state: &RetryState, error: GaxError) -> ThrottleResult {
3992 ThrottleResult::Exhausted(error)
3993 }
3994 }
3995
3996 let inner = Arc::new(ThrottleTestPolicy);
3997 let deadline = Instant::now() + StdDuration::from_secs(10);
3998 let bounded = TransactionBoundedRetryPolicy { inner, deadline };
3999
4000 let state = RetryState::new(true);
4001 let status = Status::default()
4002 .set_code(Code::Unavailable)
4003 .set_message("error");
4004 let error = GaxError::service(status);
4005
4006 let res = bounded.on_throttle(&state, error);
4007 assert!(matches!(res, ThrottleResult::Exhausted(_)));
4008 }
4009
4010 #[test]
4011 fn test_transaction_bounded_retry_policy_remaining_time_capping() {
4012 #[derive(Debug)]
4013 struct RemainingTimeTestPolicy {
4014 timeout: Option<StdDuration>,
4015 }
4016 impl RetryPolicy for RemainingTimeTestPolicy {
4017 fn on_error(&self, _state: &RetryState, error: GaxError) -> RetryResult {
4018 RetryResult::Continue(error)
4019 }
4020 fn remaining_time(&self, _state: &RetryState) -> Option<StdDuration> {
4021 self.timeout
4022 }
4023 }
4024
4025 let state = RetryState::new(true);
4026
4027 let inner = Arc::new(RemainingTimeTestPolicy {
4029 timeout: Some(StdDuration::from_secs(3)),
4030 });
4031 let deadline = Instant::now() + StdDuration::from_secs(10);
4032 let bounded = TransactionBoundedRetryPolicy { inner, deadline };
4033 let remaining = bounded.remaining_time(&state).expect("remaining time");
4034 assert!(
4035 remaining >= StdDuration::from_millis(2500)
4036 && remaining <= StdDuration::from_millis(3500)
4037 );
4038
4039 let inner = Arc::new(RemainingTimeTestPolicy {
4041 timeout: Some(StdDuration::from_secs(10)),
4042 });
4043 let deadline = Instant::now() + StdDuration::from_secs(2);
4044 let bounded = TransactionBoundedRetryPolicy { inner, deadline };
4045 let remaining = bounded.remaining_time(&state).expect("remaining time");
4046 assert!(
4047 remaining >= StdDuration::from_millis(1500)
4048 && remaining <= StdDuration::from_millis(2500)
4049 );
4050
4051 let inner = Arc::new(RemainingTimeTestPolicy { timeout: None });
4053 let deadline = Instant::now() + StdDuration::from_secs(10);
4054 let bounded = TransactionBoundedRetryPolicy { inner, deadline };
4055 let remaining = bounded.remaining_time(&state).expect("remaining time");
4056 assert!(
4057 remaining >= StdDuration::from_millis(9500)
4058 && remaining <= StdDuration::from_millis(10500)
4059 );
4060 }
4061}