Skip to main content

google_cloud_spanner/
read_only_transaction.rs

1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::database_client::DatabaseClient;
16use crate::error::internal_error;
17use crate::model::TransactionOptions;
18use crate::model::TransactionSelector;
19use crate::model::transaction_options::{Mode, ReadOnly};
20use crate::precommit::PrecommitTokenTracker;
21use crate::result_set::{ResultSet, ResultSetParams, StreamOperation};
22use crate::statement::Statement;
23use crate::timestamp_bound::TimestampBound;
24use crate::transaction_retry_policy::is_aborted;
25use google_cloud_gax::backoff_policy::BackoffPolicyArg;
26use google_cloud_gax::options::internal::RequestOptionsExt as _;
27use google_cloud_gax::retry_policy::RetryPolicyArg;
28use std::mem::replace;
29use std::sync::{Arc, Mutex};
30use std::time::Duration;
31use tokio::sync::Notify;
32
33/// A builder for [SingleUseReadOnlyTransaction].
34///
35/// # Example
36/// ```
37/// # use google_cloud_spanner::client::Spanner;
38/// # use google_cloud_spanner::transaction::TimestampBound;
39/// # async fn build_tx(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
40/// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
41/// let read_only_tx = db_client.single_use()
42///     .set_timestamp_bound(TimestampBound::strong())
43///     .build();
44/// # Ok(())
45/// # }
46/// ```
47pub struct SingleUseReadOnlyTransactionBuilder {
48    client: DatabaseClient,
49    timestamp_bound: Option<TimestampBound>,
50}
51
52impl SingleUseReadOnlyTransactionBuilder {
53    pub(crate) fn new(client: DatabaseClient) -> Self {
54        Self {
55            client,
56            timestamp_bound: None,
57        }
58    }
59
60    /// Sets the timestamp bound for the read-only transaction.
61    ///
62    /// # Example
63    /// ```
64    /// # use google_cloud_spanner::client::Spanner;
65    /// # use google_cloud_spanner::transaction::TimestampBound;
66    /// # async fn set_bound(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
67    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
68    /// let builder = db_client.single_use().set_timestamp_bound(TimestampBound::strong());
69    /// # Ok(())
70    /// # }
71    /// ```
72    /// When reading data in Spanner in a read-only transaction, you can set a timestamp bound,
73    /// which tells Spanner how to choose a timestamp at which to read the data.
74    ///
75    /// See <https://docs.cloud.google.com/spanner/docs/timestamp-bounds> for more information.
76    pub fn set_timestamp_bound(mut self, bound: TimestampBound) -> Self {
77        self.timestamp_bound = Some(bound);
78        self
79    }
80
81    /// Builds the [SingleUseReadOnlyTransaction].
82    ///
83    /// # Example
84    ///
85    /// ```
86    /// # use google_cloud_spanner::client::Spanner;
87    /// # async fn build(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
88    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
89    /// let tx = db_client.single_use().build();
90    /// # Ok(())
91    /// # }
92    /// ```
93    pub fn build(self) -> SingleUseReadOnlyTransaction {
94        let read_only = match self.timestamp_bound {
95            Some(b) => ReadOnly::default().set_timestamp_bound(b.0),
96            None => ReadOnly::default().set_strong(true),
97        };
98        let transaction_selector = crate::model::TransactionSelector::default()
99            .set_single_use(TransactionOptions::default().set_read_only(read_only));
100
101        let session_name = self.client.session_name();
102        let channel_hint = self.client.spanner.next_channel_hint();
103        SingleUseReadOnlyTransaction {
104            context: ReadContext {
105                session_name,
106                client: self.client,
107                transaction_selector: ReadContextTransactionSelector::Fixed(
108                    transaction_selector,
109                    None,
110                ),
111                precommit_token_tracker: PrecommitTokenTracker::new_noop(),
112                transaction_tag: None,
113                channel_hint,
114                begin_transaction_request_options: None,
115            },
116        }
117    }
118}
119
120/// A single-use read-only transaction. A single-use read-only transaction is the most
121/// efficient way to execute a single query or read operation.
122///
123/// # Example
124/// ```
125/// # use google_cloud_spanner::client::Spanner;
126/// # use google_cloud_spanner::statement::Statement;
127/// # async fn run(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
128/// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
129/// let tx = db_client.single_use().build();
130/// let stmt = Statement::builder("SELECT * FROM users WHERE id = @id")
131///     .add_param("id", &42)
132///     .build();
133/// let rs = tx.execute_query(stmt).await?;
134/// # Ok(())
135/// # }
136/// ```
137#[derive(Debug)]
138pub struct SingleUseReadOnlyTransaction {
139    context: ReadContext,
140}
141
142impl SingleUseReadOnlyTransaction {
143    /// Executes a query.
144    ///
145    /// # Example
146    /// ```
147    /// # use google_cloud_spanner::client::Spanner;
148    /// # use google_cloud_spanner::statement::Statement;
149    /// # async fn run(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
150    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
151    /// let tx = db_client.single_use().build();
152    /// let stmt = Statement::builder("SELECT * FROM users WHERE id = @id")
153    ///     .add_param("id", &42)
154    ///     .build();
155    /// let mut rs = tx.execute_query(stmt).await?;
156    /// while let Some(row) = rs.next().await {
157    ///     let _row = row?;
158    ///     // process row
159    /// }
160    /// # Ok(())
161    /// # }
162    /// ```
163    pub async fn execute_query<T: Into<Statement>>(
164        &self,
165        statement: T,
166    ) -> crate::Result<ResultSet> {
167        self.context.execute_query(statement, None).await
168    }
169
170    /// Reads rows from the database using key lookups and scans, as a simple key/value style alternative to execute_query.
171    ///
172    /// # Example
173    /// ```
174    /// # use google_cloud_spanner::client::Spanner;
175    /// # use google_cloud_spanner::key::KeySet;
176    /// # use google_cloud_spanner::read::ReadRequest;
177    /// # use google_cloud_spanner::key;
178    /// # async fn run(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
179    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
180    /// let transaction = db_client.single_use().build();
181    ///
182    /// // Read using the primary key
183    /// let read_by_pk = ReadRequest::builder("Users", vec!["Id", "Name"]).with_keys(KeySet::all()).build();
184    /// let mut result_set = transaction.execute_read(read_by_pk).await?;
185    /// while let Some(row) = result_set.next().await {
186    ///     let _row = row?;
187    ///     // process row
188    /// }
189    ///
190    /// // Read using a secondary index
191    /// let read_by_index = ReadRequest::builder("Users", vec!["Id", "Name"])
192    ///     .with_index("UsersByIndex", key![1_i64]).build();
193    /// let mut result_set = transaction.execute_read(read_by_index).await?;
194    /// while let Some(row) = result_set.next().await {
195    ///     let _row = row?;
196    ///     // process row
197    /// }
198    /// # Ok(())
199    /// # }
200    /// ```
201    pub async fn execute_read<T: Into<crate::read::ReadRequest>>(
202        &self,
203        read: T,
204    ) -> crate::Result<ResultSet> {
205        self.context.execute_read(read).await
206    }
207}
208
209/// Options for how to start a transaction.
210#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
211#[non_exhaustive]
212pub enum BeginTransactionOption {
213    /// The transaction will be started inlined with the first statement.
214    /// This reduces the number of round-trips to Spanner by one.
215    #[default]
216    InlineBegin,
217    /// The transaction will be started explicitly using a `BeginTransaction` RPC.
218    ExplicitBegin,
219}
220
221/// A builder for [MultiUseReadOnlyTransaction].
222///
223/// # Example
224/// ```
225/// # use google_cloud_spanner::client::Spanner;
226/// # use google_cloud_spanner::transaction::TimestampBound;
227/// # async fn build_tx(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
228/// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
229/// let read_only_tx = db_client.read_only_transaction()
230///     .set_timestamp_bound(TimestampBound::strong())
231///     .build()
232///     .await?;
233/// # Ok(())
234/// # }
235/// ```
236pub struct MultiUseReadOnlyTransactionBuilder {
237    client: DatabaseClient,
238    timestamp_bound: Option<TimestampBound>,
239    begin_transaction_option: BeginTransactionOption,
240    begin_gax_options: Option<crate::RequestOptions>,
241}
242
243impl MultiUseReadOnlyTransactionBuilder {
244    pub(crate) fn new(client: DatabaseClient) -> Self {
245        Self {
246            client,
247            timestamp_bound: None,
248            begin_transaction_option: BeginTransactionOption::InlineBegin,
249            begin_gax_options: None,
250        }
251    }
252
253    /// Sets the option for how to start a transaction.
254    ///
255    /// # Example
256    /// ```
257    /// # use google_cloud_spanner::client::Spanner;
258    /// # use google_cloud_spanner::transaction::BeginTransactionOption;
259    /// # use google_cloud_spanner::statement::Statement;
260    /// # async fn set_begin_option(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
261    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
262    /// let transaction = db_client.read_only_transaction().with_begin_transaction_option(BeginTransactionOption::ExplicitBegin).build().await?;
263    /// let statement = Statement::builder("SELECT * FROM users").build();
264    /// let result_set = transaction.execute_query(statement).await?;
265    /// # Ok(())
266    /// # }
267    /// ```
268    ///
269    /// By default, the Spanner client will inline the `BeginTransaction` call with the first query
270    /// in the transaction. This reduces the number of round-trips to Spanner that are needed for a
271    /// transaction. Setting this option to `ExplicitBegin` can be beneficial for specific transaction
272    /// shapes:
273    ///
274    /// 1. When the transaction executes multiple parallel queries at the start of the transaction.
275    ///    Only one query can include a `BeginTransaction` option, and all other queries must wait for
276    ///    the first query to return the first result before they can proceed to execute. A
277    ///    `BeginTransaction` RPC will quickly return a transaction ID and allow all queries to start
278    ///    execution in parallel once the transaction ID has been returned.
279    /// 2. When the first query in the transaction could fail. If the query fails, then it will also
280    ///    not start a transaction and return a transaction ID. The transaction will then fall back to
281    ///    executing a `BeginTransaction` RPC and retry the first query.
282    ///
283    /// Default is `BeginTransactionOption::InlineBegin`.
284    pub fn with_begin_transaction_option(mut self, option: BeginTransactionOption) -> Self {
285        self.begin_transaction_option = option;
286        self
287    }
288
289    /// Sets the per-attempt timeout for the BeginTransaction RPC.
290    ///
291    /// # Example
292    /// ```
293    /// # use google_cloud_spanner::client::Spanner;
294    /// # use std::time::Duration;
295    /// # async fn sample(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
296    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
297    /// let transaction = db_client.read_only_transaction()
298    ///     .with_begin_attempt_timeout(Duration::from_secs(10))
299    ///     .build()
300    ///     .await?;
301    /// # Ok(())
302    /// # }
303    /// ```
304    ///
305    /// Note: This timeout is only used if the transaction uses the `ExplicitBegin` transaction option.
306    pub fn with_begin_attempt_timeout(mut self, timeout: Duration) -> Self {
307        self.begin_gax_options
308            .get_or_insert_with(crate::RequestOptions::default)
309            .set_attempt_timeout(timeout);
310        self
311    }
312
313    /// Sets the retry policy for the BeginTransaction RPC.
314    ///
315    /// # Example
316    /// ```
317    /// # use google_cloud_spanner::client::Spanner;
318    /// # use google_cloud_gax::retry_policy::NeverRetry;
319    /// # async fn sample(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
320    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
321    /// let transaction = db_client.read_only_transaction()
322    ///     .with_begin_retry_policy(NeverRetry)
323    ///     .build()
324    ///     .await?;
325    /// # Ok(())
326    /// # }
327    /// ```
328    ///
329    /// Note: This policy is only used if the transaction uses the `ExplicitBegin` transaction option.
330    pub fn with_begin_retry_policy(mut self, policy: impl Into<RetryPolicyArg>) -> Self {
331        self.begin_gax_options
332            .get_or_insert_with(crate::RequestOptions::default)
333            .set_retry_policy(policy);
334        self
335    }
336
337    /// Sets the backoff policy for the BeginTransaction RPC.
338    ///
339    /// # Example
340    /// ```
341    /// # use google_cloud_spanner::client::Spanner;
342    /// # use google_cloud_gax::exponential_backoff::ExponentialBackoff;
343    /// # async fn sample(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
344    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
345    /// let transaction = db_client.read_only_transaction()
346    ///     .with_begin_backoff_policy(ExponentialBackoff::default())
347    ///     .build()
348    ///     .await?;
349    /// # Ok(())
350    /// # }
351    /// ```
352    ///
353    /// Note: This policy is only used if the transaction uses the `ExplicitBegin` transaction option.
354    pub fn with_begin_backoff_policy(mut self, policy: impl Into<BackoffPolicyArg>) -> Self {
355        self.begin_gax_options
356            .get_or_insert_with(crate::RequestOptions::default)
357            .set_backoff_policy(policy);
358        self
359    }
360
361    /// Sets the timestamp bound for the read-only transaction.
362    ///
363    /// # Example
364    /// ```
365    /// # use google_cloud_spanner::client::Spanner;
366    /// # use google_cloud_spanner::transaction::TimestampBound;
367    /// # async fn set_bound(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
368    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
369    /// let builder = db_client.read_only_transaction().set_timestamp_bound(TimestampBound::strong());
370    /// # Ok(())
371    /// # }
372    /// ```
373    pub fn set_timestamp_bound(mut self, bound: TimestampBound) -> Self {
374        self.timestamp_bound = Some(bound);
375        self
376    }
377
378    async fn begin(
379        &self,
380        session_name: String,
381        options: TransactionOptions,
382        channel_hint: usize,
383        request_options: crate::RequestOptions,
384    ) -> crate::Result<ReadContextTransactionSelector> {
385        let response = execute_begin_transaction(
386            &self.client,
387            session_name,
388            options,
389            None,
390            channel_hint,
391            request_options,
392            None,
393        )
394        .await?;
395
396        let transaction_selector = crate::model::TransactionSelector::default().set_id(response.id);
397
398        Ok(ReadContextTransactionSelector::Fixed(
399            transaction_selector,
400            response.read_timestamp,
401        ))
402    }
403
404    /// Builds the [MultiUseReadOnlyTransaction] and starts the transaction
405    /// by calling the `BeginTransaction` RPC.
406    ///
407    /// # Example
408    /// ```
409    /// # use google_cloud_spanner::client::Spanner;
410    /// # async fn build(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
411    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
412    /// let tx = db_client.read_only_transaction().build().await?;
413    /// # Ok(())
414    /// # }
415    /// ```
416    pub async fn build(self) -> crate::Result<MultiUseReadOnlyTransaction> {
417        let read_only = ReadOnly::default().set_return_read_timestamp(true);
418        let read_only = match self.timestamp_bound.as_ref() {
419            Some(b) => read_only.set_timestamp_bound(b.0.clone()),
420            None => read_only.set_strong(true),
421        };
422        let options = TransactionOptions::default().set_read_only(read_only);
423
424        let session_name = self.client.session_name();
425        let channel_hint = self.client.spanner.next_channel_hint();
426        let selector = match self.begin_transaction_option {
427            BeginTransactionOption::ExplicitBegin => {
428                self.begin(
429                    session_name.clone(),
430                    options,
431                    channel_hint,
432                    self.begin_gax_options.clone().unwrap_or_default(),
433                )
434                .await?
435            }
436            BeginTransactionOption::InlineBegin => ReadContextTransactionSelector::Lazy(Arc::new(
437                Mutex::new(TransactionState::NotStarted(options)),
438            )),
439        };
440
441        Ok(MultiUseReadOnlyTransaction {
442            context: ReadContext {
443                session_name,
444                client: self.client,
445                transaction_selector: selector,
446                precommit_token_tracker: PrecommitTokenTracker::new_noop(),
447                transaction_tag: None,
448                channel_hint,
449                begin_transaction_request_options: self.begin_gax_options.clone(),
450            },
451        })
452    }
453}
454
455/// A multi-use read-only transaction. This transaction can be used for multiple read queries
456/// ensuring consistency across all queries.
457///
458/// # Example
459/// ```
460/// # use google_cloud_spanner::client::Spanner;
461/// # use google_cloud_spanner::statement::Statement;
462/// # async fn run(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
463/// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
464/// let tx = db_client.read_only_transaction().build().await?;
465/// let stmt1 = Statement::builder("SELECT * FROM users WHERE id = @id")
466///     .add_param("id", &42)
467///     .build();
468/// let mut rs1 = tx.execute_query(stmt1).await?;
469///
470/// let stmt2 = Statement::builder("SELECT * FROM other_table")
471///     .build();
472/// let mut rs2 = tx.execute_query(stmt2).await?;
473/// # Ok(())
474/// # }
475/// ```
476#[derive(Debug)]
477pub struct MultiUseReadOnlyTransaction {
478    pub(crate) context: ReadContext,
479}
480
481impl MultiUseReadOnlyTransaction {
482    /// Returns the read timestamp chosen for the transaction.
483    pub fn read_timestamp(&self) -> Option<wkt::Timestamp> {
484        self.context.transaction_selector.read_timestamp()
485    }
486
487    /// Executes a query using this transaction.
488    ///
489    /// # Example
490    /// ```
491    /// # use google_cloud_spanner::client::Spanner;
492    /// # use google_cloud_spanner::statement::Statement;
493    /// # async fn run(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
494    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
495    /// let tx = db_client.read_only_transaction().build().await?;
496    /// let stmt = Statement::builder("SELECT * FROM users WHERE id = @id")
497    ///     .add_param("id", &42)
498    ///     .build();
499    /// let mut rs = tx.execute_query(stmt).await?;
500    /// while let Some(row) = rs.next().await {
501    ///     let _row = row?;
502    ///     // process row
503    /// }
504    /// # Ok(())
505    /// # }
506    /// ```
507    pub async fn execute_query<T: Into<Statement>>(
508        &self,
509        statement: T,
510    ) -> crate::Result<ResultSet> {
511        self.context.execute_query(statement, None).await
512    }
513
514    /// Reads rows from the database using key lookups and scans, as a simple key/value style alternative to execute_query.
515    ///
516    /// # Example
517    /// ```
518    /// # use google_cloud_spanner::client::Spanner;
519    /// # use google_cloud_spanner::key::KeySet;
520    /// # use google_cloud_spanner::read::ReadRequest;
521    /// # use google_cloud_spanner::key;
522    /// # async fn run(spanner: Spanner) -> Result<(), google_cloud_spanner::Error> {
523    /// let db_client = spanner.database_client("projects/p/instances/i/databases/d").build().await?;
524    /// let transaction = db_client.read_only_transaction().build().await?;
525    ///
526    /// // Read using the primary key
527    /// let read_by_pk = ReadRequest::builder("Users", vec!["Id", "Name"]).with_keys(KeySet::all()).build();
528    /// let mut result_set = transaction.execute_read(read_by_pk).await?;
529    /// while let Some(row) = result_set.next().await {
530    ///     let _row = row?;
531    ///     // process row
532    /// }
533    ///
534    /// // Read using a secondary index
535    /// let read_by_index = ReadRequest::builder("Users", vec!["Id", "Name"])
536    ///     .with_index("UsersByIndex", key![1_i64]).build();
537    /// let mut result_set = transaction.execute_read(read_by_index).await?;
538    /// while let Some(row) = result_set.next().await {
539    ///     let _row = row?;
540    ///     // process row
541    /// }
542    /// # Ok(())
543    /// # }
544    /// ```
545    pub async fn execute_read<T: Into<crate::read::ReadRequest>>(
546        &self,
547        read: T,
548    ) -> crate::Result<ResultSet> {
549        self.context.execute_read(read).await
550    }
551}
552
553/// Executes an explicit `BeginTransaction` RPC on Spanner.
554pub(crate) async fn execute_begin_transaction(
555    client: &crate::database_client::DatabaseClient,
556    session_name: String,
557    options: crate::model::TransactionOptions,
558    transaction_tag: Option<String>,
559    channel_hint: usize,
560    request_options: crate::RequestOptions,
561    mutation_key: Option<crate::model::Mutation>,
562) -> crate::Result<crate::model::Transaction> {
563    let mut request = crate::model::BeginTransactionRequest::default()
564        .set_session(session_name)
565        .set_options(options)
566        .set_or_clear_mutation_key(mutation_key);
567    if let Some(tag) = transaction_tag {
568        request = request
569            .set_request_options(crate::model::RequestOptions::default().set_transaction_tag(tag));
570    }
571
572    client
573        .spanner
574        .begin_transaction(request, request_options, channel_hint)
575        .await
576}
577
578#[derive(Clone, Debug)]
579pub(crate) enum ReadContextTransactionSelector {
580    Fixed(crate::model::TransactionSelector, Option<wkt::Timestamp>),
581    Lazy(Arc<Mutex<TransactionState>>),
582}
583
584#[derive(Clone, Debug)]
585pub(crate) enum TransactionState {
586    NotStarted(crate::model::TransactionOptions),
587    Starting(crate::model::TransactionOptions, Arc<Notify>),
588    Started(crate::model::TransactionSelector, Option<wkt::Timestamp>),
589    Failed(Arc<crate::Error>),
590    FirstStatementFailed,
591}
592
593enum SelectorStatus {
594    Ready(crate::model::TransactionSelector),
595    Wait(std::sync::Arc<tokio::sync::Notify>),
596}
597
598fn to_start_error(err: &crate::Error) -> crate::Error {
599    if let Some(status) = err.status() {
600        crate::Error::service(status.clone())
601    } else {
602        crate::error::internal_error(format!("Transaction failed to start: {}", err))
603    }
604}
605
606impl ReadContextTransactionSelector {
607    pub(crate) async fn selector(&self) -> crate::Result<crate::model::TransactionSelector> {
608        match self {
609            Self::Fixed(selector, _) => Ok(selector.clone()),
610            Self::Lazy(_) => loop {
611                match self.poll_selector_status()? {
612                    SelectorStatus::Ready(selector) => return Ok(selector),
613                    SelectorStatus::Wait(notify) => notify.notified().await,
614                }
615            },
616        }
617    }
618
619    /// Inspects the current lazy selector state returning whether it is ready,
620    /// failed, or needs to wait for the transaction to start.
621    fn poll_selector_status(&self) -> crate::Result<SelectorStatus> {
622        let Self::Lazy(lazy) = self else {
623            unreachable!("poll_selector_status called on non-Lazy selector");
624        };
625        let mut guard = lazy.lock().expect("transaction state mutex poisoned");
626
627        // Fast path: Transaction is already started.
628        if let TransactionState::Started(selector, _) = &*guard {
629            return Ok(SelectorStatus::Ready(selector.clone()));
630        }
631
632        // If the transaction has not started, extract options and transition the state to Starting.
633        let pending_options = if let TransactionState::NotStarted(options) = &*guard {
634            Some(options.clone())
635        } else {
636            // The state is either Starting or Failed. Concurrent threads will yield None here
637            // and fall through to either wait for the leader or fail immediately.
638            None
639        };
640        if let Some(options) = pending_options {
641            // This thread becomes the "leader" and will start the transaction.
642            let notify = Arc::new(Notify::new());
643            *guard = TransactionState::Starting(options.clone(), Arc::clone(&notify));
644            return Ok(SelectorStatus::Ready(
645                crate::model::TransactionSelector::default().set_begin(options),
646            ));
647        }
648
649        // Handle other states: yield error or wait.
650        match &*guard {
651            // Note: Failed will only be reached if the following happens:
652            // 1. The first query fails and the transaction falls back to an explicit BeginTransaction RPC.
653            // 2. The BeginTransaction RPC fails. This is the error that will be returned to all the waiting queries.
654            TransactionState::Failed(err) => Err(to_start_error(err)),
655            // FirstStatementFailed is reached if the initial statement fails in a Read-Write transaction using inline-begin.
656            // When in this state, the transaction must first call an explicit BeginTransaction RPC.
657            // Any subsequent statements or commit attempts should fail fast.
658            // Returning Aborted signals the TransactionRunner to retry the transaction with an explicit BeginTransaction RPC.
659            TransactionState::FirstStatementFailed => {
660                let status = google_cloud_gax::error::rpc::Status::default()
661                    .set_code(google_cloud_gax::error::rpc::Code::Aborted)
662                    .set_message("Aborted due to failed initial statement");
663                Err(crate::Error::service(status))
664            }
665            // Transaction is starting. Wait until a transaction ID is returned.
666            TransactionState::Starting(_, notify) => Ok(SelectorStatus::Wait(Arc::clone(notify))),
667            TransactionState::Started(_, _) | TransactionState::NotStarted(_) => unreachable!(),
668        }
669    }
670}
671
672pub(crate) struct ExplicitBeginParams {
673    pub(crate) client: crate::database_client::DatabaseClient,
674    pub(crate) session_name: String,
675    pub(crate) transaction_tag: Option<String>,
676    pub(crate) channel_hint: usize,
677    pub(crate) request_options: crate::RequestOptions,
678    pub(crate) is_stream_fallback: bool,
679    pub(crate) precommit_token_tracker: crate::precommit::PrecommitTokenTracker,
680    pub(crate) mutation_key: Option<crate::model::Mutation>,
681}
682
683impl ReadContextTransactionSelector {
684    /// Explicitly begins a transaction if the transaction selector is a `Lazy`
685    /// selector and the transaction has not yet been started. This is used by
686    /// the client to force the start of a transaction if the first statement
687    /// failed.
688    pub(crate) async fn begin_explicitly(&self, params: ExplicitBeginParams) -> crate::Result<()> {
689        let Self::Lazy(lazy) = self else {
690            return Ok(());
691        };
692
693        enum FallbackAction {
694            Begin(
695                crate::model::TransactionOptions,
696                Option<Arc<tokio::sync::Notify>>,
697            ),
698            Wait(Arc<tokio::sync::Notify>),
699            None,
700        }
701
702        let action = {
703            let mut guard = lazy
704                .lock()
705                .map_err(|_| internal_error("transaction state mutex poisoned"))?;
706            match &*guard {
707                TransactionState::NotStarted(options) => {
708                    // The transaction has not started yet. This thread becomes the "leader"
709                    // and transitions the state to Starting before performing the BeginTransaction RPC.
710                    let options = options.clone();
711                    let notify = Arc::new(tokio::sync::Notify::new());
712                    *guard = TransactionState::Starting(options.clone(), Arc::clone(&notify));
713                    FallbackAction::Begin(options, Some(notify))
714                }
715                TransactionState::Starting(options, notify) => {
716                    // The transaction is already in the process of starting. If this call originated from
717                    // an explicit begin request (`is_stream_fallback = false`), this thread is a follower
718                    // and must wait for the leader. If this call originated from a stream resume fallback
719                    // (`is_stream_fallback = true`), this thread is the stream leader whose initial query failed,
720                    // and it must proceed with an explicit BeginTransaction RPC.
721                    if !params.is_stream_fallback {
722                        FallbackAction::Wait(Arc::clone(notify))
723                    } else {
724                        FallbackAction::Begin(options.clone(), Some(Arc::clone(notify)))
725                    }
726                }
727                TransactionState::Started(_, _)
728                | TransactionState::Failed(_)
729                | TransactionState::FirstStatementFailed => {
730                    // The transaction has already reached a terminal state (Started or Failed).
731                    // No further action is needed in this explicit begin attempt.
732                    FallbackAction::None
733                }
734            }
735        };
736
737        let (options, notify_opt) = match action {
738            FallbackAction::None => return Ok(()),
739            FallbackAction::Wait(notify) => {
740                notify.notified().await;
741                return Ok(());
742            }
743            FallbackAction::Begin(opts, notif) => (opts, notif),
744        };
745
746        // Only the leader thread will reach this point to perform the explicit begin.
747        // Waiters are blocked in `poll_selector_status` waiting for the result,
748        // and already completed states return early above.
749        let response = match execute_begin_transaction(
750            &params.client,
751            params.session_name,
752            options,
753            params.transaction_tag,
754            params.channel_hint,
755            params.request_options,
756            params.mutation_key,
757        )
758        .await
759        {
760            Ok(r) => r,
761            Err(e) => {
762                let mut guard = lazy.lock().expect("transaction state mutex poisoned");
763                let error = Arc::new(e);
764                *guard = TransactionState::Failed(Arc::clone(&error));
765                // Release the lock and notify all the waiting queries that
766                // the transaction has failed.
767                drop(guard);
768                if let Some(notify) = notify_opt {
769                    notify.notify_waiters();
770                }
771
772                return Err(to_start_error(&error));
773            }
774        };
775
776        self.update(response.id, response.read_timestamp)?;
777        params
778            .precommit_token_tracker
779            .update(response.precommit_token);
780
781        Ok(())
782    }
783
784    /// Updates the transaction state to `Started` with the given transaction ID and optional
785    /// read timestamp.
786    ///
787    /// This method is called when a transaction has successfully been initiated (either via
788    /// an inline begin in a query or an explicit `BeginTransaction` RPC).
789    ///
790    /// If the previous state was `Starting`, it will notify all concurrent threads that were
791    /// waiting for the transaction to start.
792    pub(crate) fn update(
793        &self,
794        id: bytes::Bytes,
795        timestamp: Option<wkt::Timestamp>,
796    ) -> crate::Result<()> {
797        let Self::Lazy(lazy) = self else {
798            return Ok(());
799        };
800        let mut guard = lazy.lock().expect("transaction state mutex poisoned");
801
802        if matches!(
803            &*guard,
804            TransactionState::NotStarted(_) | TransactionState::Starting(_, _)
805        ) {
806            // Atomically transition the state to Started and extract the previous state.
807            // We do this to take ownership of the Notify handle (if it was Starting)
808            // so we can notify waiters after dropping the lock.
809            let previous_state = replace(
810                &mut *guard,
811                TransactionState::Started(TransactionSelector::default().set_id(id), timestamp),
812            );
813            drop(guard);
814
815            // Notify all queries that are waiting for the transaction.
816            if let TransactionState::Starting(_, notify) = previous_state {
817                notify.notify_waiters();
818            }
819            Ok(())
820        } else if let TransactionState::Started(existing_selector, _) = &*guard {
821            // Spanner returns the transaction ID on all statements executed within that transaction
822            // when using multiplexed sessions.If the transaction has already started with the same ID,
823            // this is expected behavior and can be ignored.
824            if existing_selector.id() == Some(&id) {
825                Ok(())
826            } else {
827                Err(crate::error::internal_error(
828                    "got a transaction id for an already Started or Failed transaction",
829                ))
830            }
831        } else {
832            // This should never happen.
833            Err(crate::error::internal_error(
834                "got a transaction id for an already Started or Failed transaction",
835            ))
836        }
837    }
838
839    /// Returns the transaction ID if it is already available, without waiting.
840    ///
841    /// This method inspects the selector and returns the transaction ID if the
842    /// transaction has already started. It returns `None` if the transaction
843    /// has not yet started or is in a state without an ID.
844    pub(crate) fn get_id_no_wait(&self) -> crate::Result<Option<bytes::Bytes>> {
845        use crate::model::transaction_selector::Selector;
846        match self {
847            Self::Fixed(selector, _) => {
848                if let Some(Selector::Id(id)) = &selector.selector {
849                    return Ok(Some(id.clone()));
850                }
851            }
852            Self::Lazy(lazy) => {
853                let guard = lazy
854                    .lock()
855                    .map_err(|_| internal_error("transaction state mutex poisoned"))?;
856                if let TransactionState::Started(selector, _) = &*guard
857                    && let Some(Selector::Id(id)) = &selector.selector
858                {
859                    return Ok(Some(id.clone()));
860                }
861            }
862        }
863        Ok(None)
864    }
865
866    /// Returns whether the transaction selector is currently in the `Starting` state.
867    pub(crate) fn is_starting(&self) -> crate::Result<bool> {
868        match self {
869            Self::Lazy(lazy) => {
870                let guard = lazy
871                    .lock()
872                    .map_err(|_| internal_error("transaction state mutex poisoned"))?;
873                Ok(matches!(&*guard, TransactionState::Starting(_, _)))
874            }
875            _ => Ok(false),
876        }
877    }
878
879    /// Resets the selector state from `Starting` back to `NotStarted`.
880    ///
881    /// This is used during stream resume fallbacks when the first query stream
882    /// fails before yielding a transaction ID. It unlocks any parked waiters
883    /// allowing them (or the retry attempt) to include the begin option again.
884    /// Only one of the waiters will win that 'race' and include a new
885    /// BeginTransaction option. All the others will continue to wait.
886    pub(crate) fn maybe_reset_starting(&self) {
887        let Self::Lazy(lazy) = self else {
888            return;
889        };
890
891        let mut guard = lazy.lock().expect("transaction state mutex poisoned");
892        if let TransactionState::Starting(options, notify) = &*guard {
893            let options = options.clone();
894            let notify = Arc::clone(notify);
895            *guard = TransactionState::NotStarted(options);
896            drop(guard);
897            notify.notify_waiters();
898        }
899    }
900
901    pub(crate) fn is_first_statement_failed(&self) -> bool {
902        let Self::Lazy(lazy) = self else {
903            return false;
904        };
905        let guard = lazy.lock().expect("transaction state mutex poisoned");
906        matches!(&*guard, TransactionState::FirstStatementFailed)
907    }
908
909    pub(crate) fn is_read_write(&self) -> bool {
910        let Self::Lazy(lazy) = self else {
911            return false;
912        };
913        let guard = lazy.lock().expect("transaction state mutex poisoned");
914        match &*guard {
915            TransactionState::NotStarted(options) | TransactionState::Starting(options, _) => {
916                matches!(&options.mode, Some(Mode::ReadWrite(_)))
917            }
918            _ => false,
919        }
920    }
921
922    pub(crate) fn set_failed(&self, err: &crate::Error) {
923        let Self::Lazy(lazy) = self else {
924            return;
925        };
926        let mut guard = lazy.lock().expect("transaction state mutex poisoned");
927        if let TransactionState::Starting(options, notify) = &*guard {
928            let notify = Arc::clone(notify);
929            if is_aborted(err) {
930                *guard = TransactionState::NotStarted(options.clone());
931            } else {
932                *guard = TransactionState::FirstStatementFailed;
933            }
934            drop(guard);
935            notify.notify_waiters();
936        }
937    }
938
939    pub(crate) fn check_failed(&self) -> crate::Result<()> {
940        let Self::Lazy(lazy) = self else {
941            return Ok(());
942        };
943        let guard = lazy.lock().expect("transaction state mutex poisoned");
944        match &*guard {
945            TransactionState::Failed(err) => Err(to_start_error(err)),
946            TransactionState::FirstStatementFailed => {
947                Err(crate::error::aborted_due_to_failed_initial_statement())
948            }
949            _ => Ok(()),
950        }
951    }
952
953    /// Returns the read timestamp of the transaction, if available.
954    ///
955    /// For `Fixed` transactions, this returns the timestamp stored in the variant.
956    /// For `Lazy` transactions, this returns the timestamp once the transaction has successfully started
957    /// and yielded a timestamp. Returns `None` if the transaction has not started or did not yield a timestamp.
958    pub(crate) fn read_timestamp(&self) -> Option<wkt::Timestamp> {
959        match self {
960            Self::Fixed(_, timestamp) => *timestamp,
961            Self::Lazy(lazy) => {
962                let guard = lazy.lock().expect("transaction state mutex poisoned");
963                if let TransactionState::Started(_, timestamp) = &*guard {
964                    *timestamp
965                } else {
966                    None
967                }
968            }
969        }
970    }
971}
972
973#[derive(Clone, Debug)]
974pub(crate) struct ReadContext {
975    pub(crate) session_name: String,
976    pub(crate) client: DatabaseClient,
977    pub(crate) transaction_selector: ReadContextTransactionSelector,
978    pub(crate) precommit_token_tracker: PrecommitTokenTracker,
979    pub(crate) transaction_tag: Option<String>,
980    pub(crate) channel_hint: usize,
981    pub(crate) begin_transaction_request_options: Option<crate::RequestOptions>,
982}
983
984impl ReadContext {
985    /// Amends the given request options with the transaction tag if present.
986    ///
987    /// This method returns the `RequestOptions` that should be used for the request.
988    /// If no `transaction_tag` has been set, the given `RequestOptions` is returned unchanged.
989    /// If a `transaction_tag` has been set, the given `RequestOptions` is modified to include the tag
990    /// (or a new `RequestOptions` is created if `None` was passed in).
991    pub(crate) fn amend_request_options(
992        &self,
993        mut options: Option<crate::model::RequestOptions>,
994    ) -> Option<crate::model::RequestOptions> {
995        if let Some(tag) = &self.transaction_tag {
996            options
997                .get_or_insert_with(crate::model::RequestOptions::default)
998                .transaction_tag = tag.clone();
999        }
1000        options
1001    }
1002
1003    /// Attempts to execute an explicit `begin_transaction` RPC if the current transaction
1004    /// selector is still in the `Lazy(NotStarted)` state. This is used as a
1005    /// fallback mechanism when an initial implicit begin attempt failed.
1006    pub(crate) async fn begin_explicitly_if_not_started(
1007        &self,
1008        fallback_options: crate::RequestOptions,
1009        is_stream_fallback: bool,
1010        mutation_key: Option<crate::model::Mutation>,
1011    ) -> crate::Result<bool> {
1012        let ReadContextTransactionSelector::Lazy(lazy) = &self.transaction_selector else {
1013            return Ok(false);
1014        };
1015        let is_started = matches!(&*lazy.lock().unwrap(), TransactionState::Started(_, _));
1016        if is_started {
1017            return Ok(false);
1018        }
1019
1020        let options = merge_request_options(
1021            fallback_options,
1022            self.begin_transaction_request_options.as_ref(),
1023        );
1024
1025        self.transaction_selector
1026            .begin_explicitly(ExplicitBeginParams {
1027                client: self.client.clone(),
1028                session_name: self.session_name.clone(),
1029                transaction_tag: self.transaction_tag.clone(),
1030                channel_hint: self.channel_hint,
1031                request_options: options,
1032                is_stream_fallback,
1033                precommit_token_tracker: self.precommit_token_tracker.clone(),
1034                mutation_key,
1035            })
1036            .await?;
1037        Ok(true)
1038    }
1039}
1040
1041/// Merges the configured fields from a `source` `RequestOptions` into a `destination` `RequestOptions`.
1042/// Configured options in `source` will override those in `destination`.
1043fn merge_request_options(
1044    mut destination: crate::RequestOptions,
1045    source: Option<&crate::RequestOptions>,
1046) -> crate::RequestOptions {
1047    let Some(source) = source else {
1048        return destination;
1049    };
1050
1051    if let Some(timeout) = source.attempt_timeout() {
1052        destination.set_attempt_timeout(*timeout);
1053    }
1054    if let Some(retry) = source.retry_policy() {
1055        destination.set_retry_policy(retry.clone());
1056    }
1057    if let Some(backoff) = source.backoff_policy() {
1058        destination.set_backoff_policy(backoff.clone());
1059    }
1060    if let Some(src_headers) = source.get_extension::<http::HeaderMap>() {
1061        let mut dest_headers = destination
1062            .get_extension::<http::HeaderMap>()
1063            .cloned()
1064            .unwrap_or_default();
1065        for (name, value) in src_headers.iter() {
1066            dest_headers.insert(name.clone(), value.clone());
1067        }
1068        destination = destination.insert_extension(dest_headers);
1069    }
1070    destination
1071}
1072
1073/// Helper macro to execute a streaming SQL or streaming read RPC with retry logic.
1074macro_rules! execute_stream_with_retry {
1075    ($self:expr, $request:ident, $gax_options:ident, $rpc_method:ident, $operation_variant:path) => {{
1076        let stream = match $self
1077            .client
1078            .spanner
1079            .$rpc_method($request.clone(), $gax_options.clone(), $self.channel_hint)
1080            .send()
1081            .await
1082        {
1083            Ok(s) => s,
1084            Err(e) => {
1085                let is_starting = matches!(
1086                    $request
1087                        .transaction
1088                        .as_ref()
1089                        .and_then(|t| t.selector.as_ref()),
1090                    Some(crate::model::transaction_selector::Selector::Begin(_))
1091                );
1092                if is_starting {
1093                    if $self.transaction_selector.is_read_write() {
1094                        $self.transaction_selector.set_failed(&e);
1095                        return Err(e);
1096                    } else {
1097                        if is_aborted(&e) {
1098                            return Err(e);
1099                        }
1100                        if $self
1101                            .begin_explicitly_if_not_started($gax_options.clone(), true, None)
1102                            .await?
1103                        {
1104                            $request.transaction =
1105                                Some($self.transaction_selector.selector().await?);
1106                            $self
1107                                .client
1108                                .spanner
1109                                .$rpc_method(
1110                                    $request.clone(),
1111                                    $gax_options.clone(),
1112                                    $self.channel_hint,
1113                                )
1114                                .send()
1115                                .await?
1116                        } else {
1117                            return Err(e);
1118                        }
1119                    }
1120                } else {
1121                    return Err(e);
1122                }
1123            }
1124        };
1125
1126        ResultSet::create(ResultSetParams {
1127            stream,
1128            transaction_selector: Some($self.transaction_selector.clone()),
1129            precommit_token_tracker: $self.precommit_token_tracker.clone(),
1130            client: $self.client.clone(),
1131            session_name: $self.session_name.clone(),
1132            transaction_tag: $self.transaction_tag.clone(),
1133            operation: $operation_variant($request),
1134            channel_hint: $self.channel_hint,
1135            gax_options: $gax_options,
1136        })
1137        .await
1138    }};
1139}
1140
1141impl ReadContext {
1142    pub(crate) async fn execute_query<T: Into<Statement>>(
1143        &self,
1144        statement: T,
1145        seqno: Option<i64>,
1146    ) -> crate::Result<ResultSet> {
1147        let statement = statement.into();
1148        let gax_options = statement.gax_options().clone();
1149        let mut request = statement
1150            .into_request()
1151            .set_session(self.session_name.clone())
1152            .set_transaction(self.transaction_selector.selector().await?)
1153            .set_seqno(seqno.unwrap_or(0));
1154        request.request_options = self.amend_request_options(request.request_options);
1155
1156        execute_stream_with_retry!(
1157            self,
1158            request,
1159            gax_options,
1160            execute_streaming_sql,
1161            StreamOperation::Query
1162        )
1163    }
1164
1165    pub(crate) async fn execute_read<T: Into<crate::read::ReadRequest>>(
1166        &self,
1167        read: T,
1168    ) -> crate::Result<ResultSet> {
1169        let read = read.into();
1170        let gax_options = read.gax_options.clone();
1171        let mut request = read
1172            .into_request()
1173            .set_session(self.session_name.clone())
1174            .set_transaction(self.transaction_selector.selector().await?);
1175        request.request_options = self.amend_request_options(request.request_options);
1176
1177        execute_stream_with_retry!(
1178            self,
1179            request,
1180            gax_options,
1181            streaming_read,
1182            StreamOperation::Read
1183        )
1184    }
1185}
1186
1187#[cfg(test)]
1188pub(crate) mod tests {
1189    use super::*;
1190    use crate::result_set::tests::adapt;
1191    use crate::result_set::tests::string_val;
1192    use crate::statement::Statement;
1193    use crate::value::Value;
1194    use gaxi::grpc::tonic::{self, Code, Response, Status};
1195    use google_cloud_gax::error::rpc::Code as GaxCode;
1196    use google_cloud_gax::exponential_backoff::ExponentialBackoff;
1197    use google_cloud_gax::retry_policy::NeverRetry;
1198    use google_cloud_test_macros::tokio_test_no_panics;
1199    use http::{HeaderMap, HeaderName, HeaderValue};
1200    use mock_v1::transaction_selector::Selector;
1201    use spanner_grpc_mock::MockSpanner;
1202    use spanner_grpc_mock::google::spanner::v1 as mock_v1;
1203    use std::sync::mpsc::channel as std_channel;
1204    use std::sync::{Arc, Mutex as StdMutex};
1205    use tokio::sync::oneshot::channel as oneshot_channel;
1206    use tokio::sync::{Barrier, Mutex, Notify, mpsc};
1207
1208    #[test]
1209    fn auto_traits() {
1210        static_assertions::assert_impl_all!(SingleUseReadOnlyTransactionBuilder: Send, Sync);
1211        static_assertions::assert_impl_all!(SingleUseReadOnlyTransaction: Send, Sync, std::fmt::Debug);
1212        static_assertions::assert_impl_all!(MultiUseReadOnlyTransactionBuilder: Send, Sync);
1213        static_assertions::assert_impl_all!(MultiUseReadOnlyTransaction: Send, Sync, std::fmt::Debug);
1214        static_assertions::assert_impl_all!(ReadContext: Send, Sync, std::fmt::Debug);
1215    }
1216
1217    pub(crate) fn create_session_mock() -> spanner_grpc_mock::MockSpanner {
1218        let mut mock = spanner_grpc_mock::MockSpanner::new();
1219        mock.expect_create_session().once().returning(|_| {
1220            Ok(Response::new(mock_v1::Session {
1221                name: "projects/p/instances/i/databases/d/sessions/123".to_string(),
1222                ..Default::default()
1223            }))
1224        });
1225        mock
1226    }
1227
1228    fn setup_select1() -> spanner_grpc_mock::google::spanner::v1::PartialResultSet {
1229        spanner_grpc_mock::google::spanner::v1::PartialResultSet {
1230            metadata: Some(spanner_grpc_mock::google::spanner::v1::ResultSetMetadata {
1231                row_type: Some(spanner_grpc_mock::google::spanner::v1::StructType {
1232                    fields: vec![Default::default()],
1233                }),
1234                ..Default::default()
1235            }),
1236            values: vec![prost_types::Value {
1237                kind: Some(prost_types::value::Kind::StringValue("1".to_string())),
1238            }],
1239            last: true,
1240            ..Default::default()
1241        }
1242    }
1243
1244    pub(crate) async fn setup_db_client(
1245        mock: spanner_grpc_mock::MockSpanner,
1246    ) -> (DatabaseClient, tokio::task::JoinHandle<()>) {
1247        use crate::client::Spanner;
1248        use google_cloud_auth::credentials::anonymous::Builder as Anonymous;
1249        let (address, server) = spanner_grpc_mock::start("0.0.0.0:0", mock)
1250            .await
1251            .expect("Failed to start mock server");
1252
1253        let spanner = Spanner::builder()
1254            .with_endpoint(address)
1255            .with_credentials(Anonymous::new().build())
1256            .build()
1257            .await
1258            .expect("Failed to build client");
1259
1260        let db_client = spanner
1261            .database_client("projects/p/instances/i/databases/d")
1262            .build()
1263            .await
1264            .expect("Failed to create DatabaseClient");
1265
1266        (db_client, server)
1267    }
1268
1269    #[tokio_test_no_panics]
1270    async fn single_use_builder() {
1271        let mock = create_session_mock();
1272
1273        let (db_client, _server) = setup_db_client(mock).await;
1274
1275        let tx = db_client.single_use().build();
1276        let selector = tx
1277            .context
1278            .transaction_selector
1279            .selector()
1280            .await
1281            .expect("Failed to get selector");
1282        let ro = selector
1283            .single_use()
1284            .expect("Expected SingleUse selector")
1285            .read_only()
1286            .expect("Expected ReadOnly mode");
1287        assert_eq!(
1288            ro.timestamp_bound,
1289            Some(crate::model::transaction_options::read_only::TimestampBound::Strong(true))
1290        );
1291
1292        let tx2 = db_client
1293            .single_use()
1294            .set_timestamp_bound(crate::timestamp_bound::TimestampBound::max_staleness(
1295                std::time::Duration::from_secs(10),
1296            ))
1297            .build();
1298        let selector = tx2
1299            .context
1300            .transaction_selector
1301            .selector()
1302            .await
1303            .expect("Failed to get selector");
1304        let ro2 = selector
1305            .single_use()
1306            .expect("Expected SingleUse selector")
1307            .read_only()
1308            .expect("Expected ReadOnly mode");
1309        assert_eq!(
1310            ro2.timestamp_bound,
1311            Some(
1312                crate::model::transaction_options::read_only::TimestampBound::MaxStaleness(
1313                    Box::new(wkt::Duration::new(10, 0).expect("failed to create Duration"))
1314                )
1315            )
1316        );
1317    }
1318
1319    #[tokio_test_no_panics]
1320    async fn execute_single_query() {
1321        use super::super::result_set::tests::string_val;
1322        use crate::statement::Statement;
1323        use crate::value::Value;
1324
1325        let mut mock = create_session_mock();
1326
1327        mock.expect_execute_streaming_sql().once().returning(|req| {
1328            let req = req.into_inner();
1329            assert_eq!(
1330                req.session,
1331                "projects/p/instances/i/databases/d/sessions/123"
1332            );
1333            assert_eq!(req.sql, "SELECT 1");
1334
1335            Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1336                setup_select1(),
1337            )])))
1338        });
1339
1340        let (db_client, _server) = setup_db_client(mock).await;
1341
1342        let tx = db_client.single_use().build();
1343        let mut rs = tx
1344            .execute_query(Statement::builder("SELECT 1").build())
1345            .await
1346            .expect("Failed to execute query");
1347
1348        let row = rs.next().await.expect("has row").expect("has valid row");
1349        assert_eq!(row.raw_values(), [Value(string_val("1"))]);
1350        let result = rs.next().await;
1351        assert!(result.is_none(), "expected None, got {result:?}");
1352    }
1353
1354    #[tokio_test_no_panics]
1355    async fn execute_multi_query() {
1356        use super::super::result_set::tests::string_val;
1357        use crate::statement::Statement;
1358        use crate::value::Value;
1359        use spanner_grpc_mock::google::spanner::v1 as mock_v1;
1360
1361        let mut mock = create_session_mock();
1362
1363        mock.expect_begin_transaction().once().returning(|req| {
1364            let req = req.into_inner();
1365            assert_eq!(
1366                req.session,
1367                "projects/p/instances/i/databases/d/sessions/123"
1368            );
1369            Ok(tonic::Response::new(mock_v1::Transaction {
1370                id: vec![1, 2, 3],
1371                // prost_types::Timestamp fields need to be explicitly set because default is 0 for both
1372                read_timestamp: Some(prost_types::Timestamp {
1373                    seconds: 123456789,
1374                    nanos: 0,
1375                }),
1376                ..Default::default()
1377            }))
1378        });
1379
1380        mock.expect_execute_streaming_sql()
1381            .times(2)
1382            .returning(|req| {
1383                let req = req.into_inner();
1384                assert_eq!(
1385                    req.session,
1386                    "projects/p/instances/i/databases/d/sessions/123"
1387                );
1388                assert_eq!(
1389                    req.transaction
1390                        .expect("transaction should be present")
1391                        .selector
1392                        .expect("selector should be present"),
1393                    mock_v1::transaction_selector::Selector::Id(vec![1, 2, 3])
1394                );
1395
1396                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1397                    setup_select1(),
1398                )])))
1399            });
1400
1401        let (db_client, _server) = setup_db_client(mock).await;
1402
1403        let tx = db_client
1404            .read_only_transaction()
1405            .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
1406            .build()
1407            .await
1408            .expect("Failed to start tx");
1409        assert_eq!(
1410            tx.read_timestamp()
1411                .expect("expected read timestamp")
1412                .seconds(),
1413            123456789
1414        );
1415
1416        for _ in 0..2 {
1417            let mut rs = tx
1418                .execute_query(Statement::builder("SELECT 1").build())
1419                .await
1420                .expect("Failed to execute query");
1421
1422            let row = rs.next().await.expect("has row").expect("has valid row");
1423            assert_eq!(row.raw_values(), [Value(string_val("1"))]);
1424
1425            let result = rs.next().await;
1426            assert!(result.is_none(), "expected None, got {result:?}");
1427        }
1428    }
1429
1430    #[tokio_test_no_panics]
1431    async fn execute_multi_query_inline_begin() -> anyhow::Result<()> {
1432        use super::super::result_set::tests::string_val;
1433        use crate::statement::Statement;
1434        use crate::value::Value;
1435        use spanner_grpc_mock::google::spanner::v1 as mock_v1;
1436
1437        let mut mock = create_session_mock();
1438
1439        // No explicit begin_transaction should be called.
1440        mock.expect_begin_transaction().never();
1441
1442        let mut seq = mockall::Sequence::new();
1443
1444        mock.expect_execute_streaming_sql()
1445            .times(1)
1446            .in_sequence(&mut seq)
1447            .returning(move |req| {
1448                let req = req.into_inner();
1449                assert_eq!(
1450                    req.session,
1451                    "projects/p/instances/i/databases/d/sessions/123"
1452                );
1453
1454                // First call: Should have Selector::Begin
1455                match req.transaction.unwrap().selector.unwrap() {
1456                    mock_v1::transaction_selector::Selector::Begin(_) => {}
1457                    _ => panic!("Expected Selector::Begin"),
1458                }
1459                let mut rs = setup_select1();
1460                rs.metadata.as_mut().unwrap().transaction = Some(mock_v1::Transaction {
1461                    id: vec![4, 5, 6],
1462                    read_timestamp: Some(prost_types::Timestamp {
1463                        seconds: 987654321,
1464                        nanos: 0,
1465                    }),
1466                    ..Default::default()
1467                });
1468                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(rs)])))
1469            });
1470
1471        mock.expect_execute_streaming_sql()
1472            .times(1)
1473            .in_sequence(&mut seq)
1474            .returning(move |req| {
1475                let req = req.into_inner();
1476                // Second call: Should have Selector::Id using the ID returned in the first call
1477                match req.transaction.unwrap().selector.unwrap() {
1478                    mock_v1::transaction_selector::Selector::Id(id) => {
1479                        assert_eq!(id, vec![4, 5, 6]);
1480                    }
1481                    _ => panic!("Expected Selector::Id"),
1482                }
1483                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1484                    setup_select1(),
1485                )])))
1486            });
1487
1488        let (db_client, _server) = setup_db_client(mock).await;
1489
1490        let tx = db_client
1491            .read_only_transaction()
1492            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1493            .build()
1494            .await?;
1495
1496        // The read timestamp is not available until the first query is executed.
1497        assert!(tx.read_timestamp().is_none());
1498
1499        for i in 0..2 {
1500            let mut rs = tx
1501                .execute_query(Statement::builder("SELECT 1").build())
1502                .await?;
1503
1504            let row = rs.next().await.expect("Expected a row")?;
1505            assert_eq!(row.raw_values(), [Value(string_val("1"))]);
1506
1507            let result = rs.next().await;
1508            assert!(result.is_none(), "Expected None, got {result:?}");
1509
1510            if i == 0 {
1511                // Read timestamp becomes available.
1512                assert_eq!(
1513                    tx.read_timestamp()
1514                        .expect("Expected read timestamp")
1515                        .seconds(),
1516                    987654321
1517                );
1518            }
1519        }
1520
1521        Ok(())
1522    }
1523
1524    #[tokio_test_no_panics]
1525    async fn execute_single_read() {
1526        use super::super::result_set::tests::string_val;
1527        use crate::key::KeySet;
1528        use crate::read::ReadRequest;
1529        use crate::value::Value;
1530
1531        let mut mock = create_session_mock();
1532
1533        mock.expect_streaming_read().once().returning(|req| {
1534            let req = req.into_inner();
1535            assert_eq!(
1536                req.session,
1537                "projects/p/instances/i/databases/d/sessions/123"
1538            );
1539            assert_eq!(req.table, "Users");
1540            assert_eq!(req.columns, vec!["Id".to_string(), "Name".to_string()]);
1541
1542            Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1543                setup_select1(),
1544            )])))
1545        });
1546
1547        let (db_client, _server) = setup_db_client(mock).await;
1548
1549        let tx = db_client.single_use().build();
1550        let read = ReadRequest::builder("Users", vec!["Id", "Name"])
1551            .with_keys(KeySet::all())
1552            .build();
1553        let mut rs = tx.execute_read(read).await.expect("Failed to execute read");
1554
1555        let row = rs.next().await.expect("has row").expect("has valid row");
1556        assert_eq!(row.raw_values(), [Value(string_val("1"))]);
1557        let result = rs.next().await;
1558        assert!(result.is_none(), "expected None, got {result:?}");
1559    }
1560
1561    #[tokio_test_no_panics]
1562    async fn execute_multi_read() -> anyhow::Result<()> {
1563        use super::super::result_set::tests::string_val;
1564        use crate::key::KeySet;
1565        use crate::read::ReadRequest;
1566        use crate::value::Value;
1567        use spanner_grpc_mock::google::spanner::v1 as mock_v1;
1568
1569        let mut mock = create_session_mock();
1570
1571        // No explicit begin_transaction should be called.
1572        mock.expect_begin_transaction().never();
1573
1574        let mut seq = mockall::Sequence::new();
1575
1576        mock.expect_streaming_read()
1577            .times(1)
1578            .in_sequence(&mut seq)
1579            .returning(move |req| {
1580                let req = req.into_inner();
1581                assert_eq!(
1582                    req.session,
1583                    "projects/p/instances/i/databases/d/sessions/123"
1584                );
1585
1586                // First call: Should have Selector::Begin
1587                match req.transaction.unwrap().selector.unwrap() {
1588                    mock_v1::transaction_selector::Selector::Begin(_) => {}
1589                    _ => panic!("Expected Selector::Begin"),
1590                }
1591                let mut rs = setup_select1();
1592                rs.metadata.as_mut().unwrap().transaction = Some(mock_v1::Transaction {
1593                    id: vec![4, 5, 6],
1594                    read_timestamp: Some(prost_types::Timestamp {
1595                        seconds: 987654321,
1596                        nanos: 0,
1597                    }),
1598                    ..Default::default()
1599                });
1600                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(rs)])))
1601            });
1602
1603        mock.expect_streaming_read()
1604            .times(1)
1605            .in_sequence(&mut seq)
1606            .returning(move |req| {
1607                let req = req.into_inner();
1608                // Second call: Should have Selector::Id using the ID returned in the first call
1609                match req.transaction.unwrap().selector.unwrap() {
1610                    mock_v1::transaction_selector::Selector::Id(id) => {
1611                        assert_eq!(id, vec![4, 5, 6]);
1612                    }
1613                    _ => panic!("Expected Selector::Id"),
1614                }
1615                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1616                    setup_select1(),
1617                )])))
1618            });
1619
1620        let (db_client, _server) = setup_db_client(mock).await;
1621
1622        let tx = db_client
1623            .read_only_transaction()
1624            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1625            .build()
1626            .await?;
1627
1628        // The read timestamp is not available until the first query is executed.
1629        assert!(tx.read_timestamp().is_none());
1630
1631        for i in 0..2 {
1632            let read = ReadRequest::builder("Users", vec!["Id", "Name"])
1633                .with_keys(KeySet::all())
1634                .build();
1635            let mut rs = tx.execute_read(read).await?;
1636
1637            let row = rs.next().await.expect("Expected a row")?;
1638            assert_eq!(row.raw_values(), [Value(string_val("1"))]);
1639
1640            let result = rs.next().await;
1641            assert!(result.is_none(), "Expected None, got {result:?}");
1642
1643            if i == 0 {
1644                // Read timestamp becomes available.
1645                assert_eq!(
1646                    tx.read_timestamp()
1647                        .expect("Expected read timestamp")
1648                        .seconds(),
1649                    987654321
1650                );
1651            }
1652        }
1653
1654        Ok(())
1655    }
1656
1657    #[tokio_test_no_panics]
1658    async fn inline_begin_failure_retry_success() -> anyhow::Result<()> {
1659        use crate::value::Value;
1660        use gaxi::grpc::tonic::Status;
1661        use tonic::Response;
1662
1663        let mut mock = create_session_mock();
1664        let mut seq = mockall::Sequence::new();
1665
1666        // 1. Initial query fails
1667        mock.expect_execute_streaming_sql()
1668            .times(1)
1669            .in_sequence(&mut seq)
1670            .returning(|_| Err(Status::internal("Internal error")));
1671
1672        // 2. Explicit begin transaction succeeds
1673        mock.expect_begin_transaction()
1674            .times(1)
1675            .in_sequence(&mut seq)
1676            .returning(|req| {
1677                let req = req.into_inner();
1678                assert_eq!(
1679                    req.session,
1680                    "projects/p/instances/i/databases/d/sessions/123"
1681                );
1682                // Return a transaction with ID
1683                Ok(Response::new(mock_v1::Transaction {
1684                    id: vec![7, 8, 9],
1685                    read_timestamp: Some(prost_types::Timestamp {
1686                        seconds: 123456789,
1687                        nanos: 0,
1688                    }),
1689                    ..Default::default()
1690                }))
1691            });
1692
1693        // 3. Retry of the query succeeds
1694        mock.expect_execute_streaming_sql()
1695            .times(1)
1696            .in_sequence(&mut seq)
1697            .returning(|req| {
1698                let req = req.into_inner();
1699                // Ensure it uses the new transaction ID
1700                match req.transaction.unwrap().selector.unwrap() {
1701                    mock_v1::transaction_selector::Selector::Id(id) => {
1702                        assert_eq!(id, vec![7, 8, 9]);
1703                    }
1704                    _ => panic!("Expected Selector::Id"),
1705                }
1706                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1707                    setup_select1(),
1708                )])))
1709            });
1710
1711        let (db_client, _server) = setup_db_client(mock).await;
1712        let tx = db_client
1713            .read_only_transaction()
1714            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1715            .build()
1716            .await?;
1717
1718        let mut rs = tx
1719            .execute_query(Statement::builder("SELECT 1").build())
1720            .await?;
1721
1722        let row = rs
1723            .next()
1724            .await
1725            .ok_or_else(|| anyhow::anyhow!("Expected a row but stream cleanly exhausted"))??;
1726        assert_eq!(
1727            row.raw_values(),
1728            [Value(string_val("1"))],
1729            "The parsed row value safely matched the underlying stream chunk"
1730        );
1731
1732        Ok(())
1733    }
1734
1735    #[tokio_test_no_panics]
1736    async fn inline_begin_failure_retry_failure() -> anyhow::Result<()> {
1737        use gaxi::grpc::tonic::Status;
1738        use tonic::Response;
1739
1740        let mut mock = create_session_mock();
1741        let mut seq = mockall::Sequence::new();
1742
1743        // 1. Initial query fails
1744        mock.expect_execute_streaming_sql()
1745            .times(1)
1746            .in_sequence(&mut seq)
1747            .returning(|_| Err(Status::internal("Internal error first")));
1748
1749        // 2. Explicit begin transaction succeeds
1750        mock.expect_begin_transaction()
1751            .times(1)
1752            .in_sequence(&mut seq)
1753            .returning(|_| {
1754                Ok(Response::new(mock_v1::Transaction {
1755                    id: vec![7, 8, 9],
1756                    read_timestamp: Some(prost_types::Timestamp {
1757                        seconds: 123456789,
1758                        nanos: 0,
1759                    }),
1760                    ..Default::default()
1761                }))
1762            });
1763
1764        // 3. Retry of the query fails again
1765        mock.expect_execute_streaming_sql()
1766            .times(1)
1767            .in_sequence(&mut seq)
1768            .returning(|_| Err(Status::internal("Internal error second")));
1769
1770        let (db_client, _server) = setup_db_client(mock).await;
1771        let tx = db_client
1772            .read_only_transaction()
1773            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1774            .build()
1775            .await?;
1776
1777        let rs_result = tx
1778            .execute_query(Statement::builder("SELECT 1").build())
1779            .await;
1780
1781        assert!(
1782            rs_result.is_err(),
1783            "The failed execution bubbled upwards securely"
1784        );
1785        let err_str = rs_result.unwrap_err().to_string();
1786        assert!(
1787            err_str.contains("Internal error second"),
1788            "Secondary error message accurately propagates: {}",
1789            err_str
1790        );
1791
1792        Ok(())
1793    }
1794
1795    #[tokio_test_no_panics]
1796    async fn inline_begin_failure_fallback_rpc_fails() -> anyhow::Result<()> {
1797        use gaxi::grpc::tonic::Status;
1798
1799        let mut mock = create_session_mock();
1800        let mut seq = mockall::Sequence::new();
1801
1802        // 1. Initial query fails
1803        mock.expect_execute_streaming_sql()
1804            .times(1)
1805            .in_sequence(&mut seq)
1806            .returning(|_| Err(Status::internal("Internal error query")));
1807
1808        // 2. Explicit begin transaction fails
1809        mock.expect_begin_transaction()
1810            .times(1)
1811            .in_sequence(&mut seq)
1812            .returning(|_| Err(Status::internal("Internal error begin tx")));
1813
1814        let (db_client, _server) = setup_db_client(mock).await;
1815        let tx = db_client
1816            .read_only_transaction()
1817            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1818            .build()
1819            .await?;
1820
1821        let rs_result = tx
1822            .execute_query(Statement::builder("SELECT 1").build())
1823            .await;
1824
1825        assert!(
1826            rs_result.is_err(),
1827            "The explicitly errored fallback boot securely propagated outwards"
1828        );
1829        let err_str = rs_result.unwrap_err().to_string();
1830        assert!(
1831            err_str.contains("Internal error begin tx"),
1832            "Natively propagated specific BeginTx bounds: {}",
1833            err_str
1834        );
1835
1836        Ok(())
1837    }
1838
1839    #[tokio_test_no_panics]
1840    async fn inline_begin_read_failure_retry_success() -> anyhow::Result<()> {
1841        use crate::key::KeySet;
1842        use crate::read::ReadRequest;
1843        use crate::value::Value;
1844        use gaxi::grpc::tonic::Status;
1845        use tonic::Response;
1846
1847        let mut mock = create_session_mock();
1848        let mut seq = mockall::Sequence::new();
1849
1850        // 1. Initial read fails
1851        mock.expect_streaming_read()
1852            .times(1)
1853            .in_sequence(&mut seq)
1854            .returning(|_| Err(Status::internal("Internal error")));
1855
1856        // 2. Explicit begin transaction succeeds
1857        mock.expect_begin_transaction()
1858            .times(1)
1859            .in_sequence(&mut seq)
1860            .returning(|_| {
1861                Ok(Response::new(mock_v1::Transaction {
1862                    id: vec![7, 8, 9],
1863                    read_timestamp: None,
1864                    ..Default::default()
1865                }))
1866            });
1867
1868        // 3. Retry of the read succeeds
1869        mock.expect_streaming_read()
1870            .times(1)
1871            .in_sequence(&mut seq)
1872            .returning(|req| {
1873                let req = req.into_inner();
1874                // Ensure it uses the new transaction ID
1875                match req.transaction.unwrap().selector.unwrap() {
1876                    mock_v1::transaction_selector::Selector::Id(id) => {
1877                        assert_eq!(id, vec![7, 8, 9]);
1878                    }
1879                    _ => panic!("Expected Selector::Id"),
1880                }
1881                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
1882                    setup_select1(),
1883                )])))
1884            });
1885
1886        let (db_client, _server) = setup_db_client(mock).await;
1887        let tx = db_client
1888            .read_only_transaction()
1889            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1890            .build()
1891            .await?;
1892
1893        let read = ReadRequest::builder("Users", vec!["Id", "Name"])
1894            .with_keys(KeySet::all())
1895            .build();
1896        let mut rs = tx.execute_read(read).await?;
1897
1898        let row = rs
1899            .next()
1900            .await
1901            .ok_or_else(|| anyhow::anyhow!("Expected a row uniquely returned"))??;
1902        assert_eq!(
1903            row.raw_values(),
1904            [Value(string_val("1"))],
1905            "The macro correctly unpacked read arrays seamlessly"
1906        );
1907
1908        Ok(())
1909    }
1910
1911    #[tokio_test_no_panics]
1912    async fn single_use_query_send_error_returns_immediately() -> anyhow::Result<()> {
1913        use crate::statement::Statement;
1914        use gaxi::grpc::tonic::Status;
1915
1916        let mut mock = create_session_mock();
1917
1918        mock.expect_execute_streaming_sql()
1919            .times(1)
1920            .returning(|_| Err(Status::internal("Internal error single use query")));
1921
1922        mock.expect_begin_transaction().never();
1923
1924        let (db_client, _server) = setup_db_client(mock).await;
1925        // single_use creates a Fixed selector
1926        let tx = db_client.single_use().build();
1927
1928        let rs_result = tx
1929            .execute_query(Statement::builder("SELECT 1").build())
1930            .await;
1931
1932        assert!(rs_result.is_err());
1933        let err_str = rs_result.unwrap_err().to_string();
1934        assert!(err_str.contains("Internal error single use query"));
1935
1936        Ok(())
1937    }
1938
1939    #[tokio_test_no_panics]
1940    async fn inline_begin_already_started_query_send_error_returns_immediately()
1941    -> anyhow::Result<()> {
1942        use crate::statement::Statement;
1943        use gaxi::grpc::tonic::Status;
1944        use spanner_grpc_mock::google::spanner::v1 as mock_v1;
1945
1946        let mut mock = create_session_mock();
1947        let mut seq = mockall::Sequence::new();
1948
1949        mock.expect_begin_transaction().never();
1950
1951        // 1. First query executes successfully and implicitly starts the transaction.
1952        mock.expect_execute_streaming_sql()
1953            .times(1)
1954            .in_sequence(&mut seq)
1955            .returning(move |_req| {
1956                let mut rs = setup_select1();
1957                rs.metadata.as_mut().unwrap().transaction = Some(mock_v1::Transaction {
1958                    id: vec![4, 5, 6],
1959                    read_timestamp: None,
1960                    ..Default::default()
1961                });
1962                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(rs)])))
1963            });
1964
1965        // 2. Second query fails immediately upon send()
1966        mock.expect_execute_streaming_sql()
1967            .times(1)
1968            .in_sequence(&mut seq)
1969            .returning(|_| Err(Status::internal("Internal error second query")));
1970
1971        let (db_client, _server) = setup_db_client(mock).await;
1972
1973        let tx = db_client
1974            .read_only_transaction()
1975            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
1976            .build()
1977            .await?;
1978
1979        // Run first query (starts tx)
1980        let mut rs = tx
1981            .execute_query(Statement::builder("SELECT 1").build())
1982            .await?;
1983        let _ = rs.next().await.expect("has row")?;
1984
1985        // Run second query (fails)
1986        let rs_result = tx
1987            .execute_query(Statement::builder("SELECT 2").build())
1988            .await;
1989
1990        assert!(rs_result.is_err());
1991        let err_str = rs_result.unwrap_err().to_string();
1992        assert!(err_str.contains("Internal error second query"));
1993
1994        Ok(())
1995    }
1996
1997    #[tokio_test_no_panics]
1998    async fn execute_concurrent_queries_inline_begin() -> anyhow::Result<()> {
1999        let mut mock = create_session_mock();
2000        mock.expect_begin_transaction().never();
2001
2002        let mut seq = mockall::Sequence::new();
2003        let (tx_sender, rx_receiver) = mpsc::channel(1);
2004        let rx_receiver = Arc::new(Mutex::new(Some(rx_receiver)));
2005
2006        let task1_ready = Arc::new(Notify::new());
2007        let task1_ready_clone = Arc::clone(&task1_ready);
2008        let tasks_started = Arc::new(Barrier::new(3));
2009
2010        // 1. First query: should include Selector::Begin
2011        mock.expect_execute_streaming_sql()
2012            .times(1)
2013            .in_sequence(&mut seq)
2014            .returning(move |req| {
2015                task1_ready_clone.notify_one();
2016                let req = req.into_inner();
2017                match req.transaction.unwrap().selector.unwrap() {
2018                    Selector::Begin(_) => {}
2019                    _ => panic!("Expected Selector::Begin for first query"),
2020                }
2021                let rx = rx_receiver
2022                    .try_lock()
2023                    .expect("mutex poisoned")
2024                    .take()
2025                    .unwrap();
2026                Ok(Response::from(rx))
2027            });
2028
2029        // 2. The other queries: should include populated Selector::Id
2030        mock.expect_execute_streaming_sql()
2031            .times(2)
2032            .in_sequence(&mut seq)
2033            .returning(move |req| {
2034                let req = req.into_inner();
2035                match req.transaction.unwrap().selector.unwrap() {
2036                    Selector::Id(id) => {
2037                        assert_eq!(id, vec![4, 5, 6]);
2038                    }
2039                    _ => panic!("Expected Selector::Id for other queries"),
2040                }
2041
2042                let (tx, rx) = mpsc::channel(1);
2043                tx.try_send(Ok(setup_select1()))
2044                    .expect("send should succeed");
2045                Ok(Response::from(rx))
2046            });
2047
2048        let (db_client, _server) = setup_db_client(mock).await;
2049        let tx = db_client
2050            .read_only_transaction()
2051            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2052            .build()
2053            .await?;
2054        let tx = Arc::new(tx);
2055
2056        // Spawn 3 concurrent queries.
2057        // Task 1 launches first and executes the first query.
2058        let tx1 = Arc::clone(&tx);
2059        let handle1 = tokio::spawn(async move {
2060            let mut rs = tx1
2061                .execute_query(Statement::builder("SELECT 1").build())
2062                .await?;
2063            // Read the first result to get the transaction ID.
2064            let _ = rs.next().await;
2065            Ok::<_, crate::Error>(rs)
2066        });
2067
2068        // Wait for Task 1 to reach the mock server.
2069        task1_ready.notified().await;
2070
2071        let tx2 = Arc::clone(&tx);
2072        let tasks_started2 = Arc::clone(&tasks_started);
2073        let handle2 = tokio::spawn(async move {
2074            tasks_started2.wait().await;
2075            tx2.execute_query(Statement::builder("SELECT 1").build())
2076                .await
2077        });
2078
2079        let tx3 = Arc::clone(&tx);
2080        let tasks_started3 = Arc::clone(&tasks_started);
2081        let handle3 = tokio::spawn(async move {
2082            tasks_started3.wait().await;
2083            tx3.execute_query(Statement::builder("SELECT 1").build())
2084                .await
2085        });
2086
2087        // Ensure both Tasks 2 and 3 have reached the barrier before proceeding.
2088        tasks_started.wait().await;
2089
2090        // Flush the scheduler on this single-threaded executor.
2091        // This guarantees that Tasks 2 & 3 run until they both hit the internal
2092        // selector Notify latch and become suspended.
2093        tokio::task::yield_now().await;
2094
2095        // Provide the first result (including the transaction ID) to Task 1.
2096        // This transitions the selector to 'Started' and unblocks Tasks 2 and 3.
2097        let mut rs = setup_select1();
2098        rs.metadata
2099            .as_mut()
2100            .expect("metadata should be present")
2101            .transaction = Some(mock_v1::Transaction {
2102            id: vec![4, 5, 6],
2103            read_timestamp: Some(prost_types::Timestamp {
2104                seconds: 987654321,
2105                nanos: 0,
2106            }),
2107            ..Default::default()
2108        });
2109        tx_sender.send(Ok(rs)).await.expect("channel broken");
2110        drop(tx_sender);
2111
2112        // Collect all results
2113        let mut rs1 = handle1.await??;
2114        let mut rs2 = handle2.await??;
2115        let mut rs3 = handle3.await??;
2116
2117        // Verify the query results
2118        assert!(rs1.next().await.is_none());
2119
2120        let row2 = rs2.next().await.expect("Expected a row")?;
2121        assert_eq!(row2.raw_values(), [Value(string_val("1"))]);
2122        assert!(rs2.next().await.is_none());
2123
2124        let row3 = rs3.next().await.expect("Expected a row")?;
2125        assert_eq!(row3.raw_values(), [Value(string_val("1"))]);
2126        assert!(rs3.next().await.is_none());
2127
2128        // Verify that the read timestamp was populated
2129        assert_eq!(
2130            tx.read_timestamp()
2131                .expect("read timestamp should be populated")
2132                .seconds(),
2133            987654321
2134        );
2135
2136        Ok(())
2137    }
2138
2139    #[tokio_test_no_panics]
2140    async fn execute_concurrent_queries_inline_begin_failed_cascade() -> anyhow::Result<()> {
2141        let mut mock = create_session_mock();
2142        let mut seq = mockall::Sequence::new();
2143
2144        let (tx_sender, rx_receiver) = mpsc::channel(1);
2145        let rx_receiver = Arc::new(Mutex::new(Some(rx_receiver)));
2146
2147        let task1_ready = Arc::new(Notify::new());
2148        let task1_ready_clone = Arc::clone(&task1_ready);
2149        let tasks_started = Arc::new(Barrier::new(3));
2150
2151        // 1. Return a stream connected to tx_sender.
2152        // We will use tx_sender later in the test to inject a failed first chunk.
2153        mock.expect_execute_streaming_sql()
2154            .times(1)
2155            .in_sequence(&mut seq)
2156            .returning(move |_req| {
2157                task1_ready_clone.notify_one();
2158                let rx = rx_receiver
2159                    .try_lock()
2160                    .expect("mutex poisoned")
2161                    .take()
2162                    .expect("receiver should be present");
2163                Ok(tonic::Response::from(rx))
2164            });
2165
2166        // 2. Fallback BeginTransaction RPC fails
2167        mock.expect_begin_transaction()
2168            .times(1)
2169            .in_sequence(&mut seq)
2170            .returning(|_| {
2171                Err(gaxi::grpc::tonic::Status::internal(
2172                    "Fallback BeginTransaction failed",
2173                ))
2174            });
2175
2176        // The other queries will never be executed.
2177        mock.expect_execute_streaming_sql().times(0).returning(|_| {
2178            panic!("Other queries should not launch after failure to start the transaction")
2179        });
2180
2181        let (db_client, _server) = setup_db_client(mock).await;
2182        let tx = db_client
2183            .read_only_transaction()
2184            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2185            .build()
2186            .await?;
2187        let tx = Arc::new(tx);
2188
2189        // Spawn 3 concurrent queries.
2190        let tx1 = Arc::clone(&tx);
2191        let handle1 = tokio::spawn(async move {
2192            let mut rs = tx1
2193                .execute_query(Statement::builder("SELECT 1").build())
2194                .await?;
2195            rs.next().await.ok_or_else(|| {
2196                crate::error::internal_error("stream exhausted (this should never happen)")
2197            })??;
2198            Ok::<_, crate::Error>(rs)
2199        });
2200
2201        // Wait for Task 1 to reach the mock and transition the selector to Starting.
2202        task1_ready.notified().await;
2203
2204        let tx2 = Arc::clone(&tx);
2205        let tasks_started2 = Arc::clone(&tasks_started);
2206        let handle2 = tokio::spawn(async move {
2207            tasks_started2.wait().await;
2208            tx2.execute_query(Statement::builder("SELECT 1").build())
2209                .await
2210        });
2211
2212        let tx3 = Arc::clone(&tx);
2213        let tasks_started3 = Arc::clone(&tasks_started);
2214        let handle3 = tokio::spawn(async move {
2215            tasks_started3.wait().await;
2216            tx3.execute_query(Statement::builder("SELECT 1").build())
2217                .await
2218        });
2219
2220        // Ensure both Tasks 2 and 3 have reached the barrier before proceeding.
2221        tasks_started.wait().await;
2222
2223        // Flush the scheduler on this single-threaded executor.
2224        // This guarantees that Tasks 2 & 3 run until they both hit the internal
2225        // selector Notify latch and become suspended.
2226        tokio::task::yield_now().await;
2227
2228        // Push error to channel failing first query stream!
2229        tx_sender
2230            .send(Err(gaxi::grpc::tonic::Status::internal(
2231                "Mocked boot failed",
2232            )))
2233            .await
2234            .expect("channel broken");
2235        drop(tx_sender);
2236
2237        // Collect all results - all should fail with identical cached error!
2238        let err1 = handle1
2239            .await?
2240            .expect_err("task 1 should have failed")
2241            .to_string();
2242        let err2 = handle2
2243            .await?
2244            .expect_err("task 2 should have failed")
2245            .to_string();
2246        let err3 = handle3
2247            .await?
2248            .expect_err("task 3 should have failed")
2249            .to_string();
2250
2251        assert!(
2252            err1.contains("Fallback BeginTransaction failed"),
2253            "err1: {}",
2254            err1
2255        );
2256        assert!(
2257            err2.contains("Fallback BeginTransaction failed"),
2258            "err2: {}",
2259            err2
2260        );
2261        assert!(
2262            err3.contains("Fallback BeginTransaction failed"),
2263            "err3: {}",
2264            err3
2265        );
2266
2267        Ok(())
2268    }
2269
2270    #[tokio_test_no_panics]
2271    async fn execute_concurrent_queries_inline_begin_stream_restart_deadlock_prevention()
2272    -> crate::Result<()> {
2273        let mut mock = create_session_mock();
2274        mock.expect_begin_transaction().never();
2275
2276        let mut seq = mockall::Sequence::new();
2277
2278        let (tx_sender, rx_receiver) = mpsc::channel(1);
2279        let rx_receiver = Arc::new(Mutex::new(Some(rx_receiver)));
2280
2281        let task1_ready = Arc::new(Notify::new());
2282        let task1_ready_clone = Arc::clone(&task1_ready);
2283        let tasks_started = Arc::new(Barrier::new(3));
2284
2285        // 1. Task 1 initial query: Return a stream connected to tx_sender for error injection.
2286        mock.expect_execute_streaming_sql()
2287            .times(1)
2288            .in_sequence(&mut seq)
2289            .returning(move |req| {
2290                let req = req.into_inner();
2291                // Return a stream connected to tx_sender.
2292                // We will use tx_sender later in the test to inject a transient error.
2293                task1_ready_clone.notify_one();
2294                match req
2295                    .transaction
2296                    .expect("transaction should be present")
2297                    .selector
2298                    .expect("selector should be present")
2299                {
2300                    Selector::Begin(_) => {}
2301                    _ => panic!("Expected Selector::Begin for first query"),
2302                }
2303                let rx = rx_receiver
2304                    .try_lock()
2305                    .expect("mutex poisoned")
2306                    .take()
2307                    .expect("receiver should be present");
2308                Ok(Response::from(rx))
2309            });
2310
2311        // 2. Task 1 restart query: should include Selector::Begin, since
2312        // it failed with a transient error.
2313        mock.expect_execute_streaming_sql()
2314            .times(1)
2315            .in_sequence(&mut seq)
2316            .returning(move |req| {
2317                let req = req.into_inner();
2318                match req
2319                    .transaction
2320                    .expect("transaction should be present")
2321                    .selector
2322                    .expect("selector should be present")
2323                {
2324                    Selector::Begin(_) => {
2325                        let mut rs = setup_select1();
2326                        rs.metadata
2327                            .as_mut()
2328                            .expect("metadata should be present")
2329                            .transaction = Some(mock_v1::Transaction {
2330                            id: vec![4, 5, 6],
2331                            ..Default::default()
2332                        });
2333                        let (tx, rx) = mpsc::channel(1);
2334                        tx.try_send(Ok(rs)).expect("send should succeed");
2335                        Ok(Response::from(rx))
2336                    }
2337                    _ => panic!("Expected Selector::Begin for stream restart query"),
2338                }
2339            });
2340
2341        // 3. Tasks 2 & 3: should include populated Selector::Id
2342        mock.expect_execute_streaming_sql()
2343            .times(2)
2344            .in_sequence(&mut seq)
2345            .returning(move |req| {
2346                let req = req.into_inner();
2347                match req
2348                    .transaction
2349                    .expect("transaction should be present")
2350                    .selector
2351                    .expect("selector should be present")
2352                {
2353                    Selector::Id(id) => {
2354                        assert_eq!(id, vec![4, 5, 6]);
2355                        let (tx, rx) = mpsc::channel(1);
2356                        tx.try_send(Ok(setup_select1()))
2357                            .expect("send should succeed");
2358                        Ok(Response::from(rx))
2359                    }
2360                    _ => panic!("Expected Selector::Id for concurrent queries"),
2361                }
2362            });
2363
2364        let (db_client, _server) = setup_db_client(mock).await;
2365        let tx = db_client
2366            .read_only_transaction()
2367            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2368            .build()
2369            .await?;
2370        let tx = Arc::new(tx);
2371
2372        let handle1_tx = Arc::clone(&tx);
2373        let handle1 = tokio::spawn(async move {
2374            let mut rs = handle1_tx
2375                .execute_query(Statement::builder("SELECT 1").build())
2376                .await?;
2377            let _ = rs.next().await.ok_or_else(|| {
2378                crate::error::internal_error("stream exhausted (this should never happen)")
2379            })??;
2380            Ok::<_, crate::Error>(rs)
2381        });
2382
2383        // Wait for Task 1 to reach the mock and transition the selector to Starting.
2384        task1_ready.notified().await;
2385
2386        let handle2_tx = Arc::clone(&tx);
2387        let tasks_started2 = Arc::clone(&tasks_started);
2388        let handle2 = tokio::spawn(async move {
2389            tasks_started2.wait().await;
2390            let mut rs = handle2_tx
2391                .execute_query(Statement::builder("SELECT 1").build())
2392                .await?;
2393            let _ = rs.next().await.ok_or_else(|| {
2394                crate::error::internal_error("stream exhausted (this should never happen)")
2395            })??;
2396            Ok::<_, crate::Error>(rs)
2397        });
2398
2399        let handle3_tx = Arc::clone(&tx);
2400        let tasks_started3 = Arc::clone(&tasks_started);
2401        let handle3 = tokio::spawn(async move {
2402            tasks_started3.wait().await;
2403            let mut rs = handle3_tx
2404                .execute_query(Statement::builder("SELECT 1").build())
2405                .await?;
2406            let _ = rs.next().await.ok_or_else(|| {
2407                crate::error::internal_error("stream exhausted (this should never happen)")
2408            })??;
2409            Ok::<_, crate::Error>(rs)
2410        });
2411
2412        // Ensure both Tasks 2 and 3 have reached the barrier before proceeding.
2413        tasks_started.wait().await;
2414
2415        // Flush the scheduler on this single-threaded executor.
2416        // This guarantees that Tasks 2 & 3 run until they both hit the internal
2417        // selector Notify latch and become suspended.
2418        tokio::task::yield_now().await;
2419
2420        let grpc_status = Status::new(gaxi::grpc::tonic::Code::Unavailable, "transient error");
2421        tx_sender.send(Err(grpc_status)).await.expect("send failed");
2422        drop(tx_sender);
2423
2424        // Collect and verify all results.
2425        // handle.await returns Result<Result<ResultSet, Error>, JoinError>.
2426        // The first ? handles the potential JoinError (panic in the task),
2427        // and the second ? handles the Spanner error.
2428        let mut rs1 = handle1.await.expect("Task 1 panicked")?;
2429        let mut rs2 = handle2.await.expect("Task 2 panicked")?;
2430        let mut rs3 = handle3.await.expect("Task 3 panicked")?;
2431
2432        // Verify that all results have been exhausted.
2433        // (The tasks themselves already successfully read the first row).
2434        assert!(rs1.next().await.is_none(), "Stream 1 should be exhausted");
2435        assert!(rs2.next().await.is_none(), "Stream 2 should be exhausted");
2436        assert!(rs3.next().await.is_none(), "Stream 3 should be exhausted");
2437
2438        Ok(())
2439    }
2440
2441    #[tokio_test_no_panics]
2442    async fn execute_concurrent_queries_late_arrival_failure() -> anyhow::Result<()> {
2443        let mut mock = create_session_mock();
2444        let mut seq = mockall::Sequence::new();
2445
2446        // 1. Initial query fails.
2447        mock.expect_execute_streaming_sql()
2448            .times(1)
2449            .in_sequence(&mut seq)
2450            .returning(|req| {
2451                let req = req.into_inner();
2452                match req
2453                    .transaction
2454                    .expect("transaction should be present")
2455                    .selector
2456                    .expect("selector should be present")
2457                {
2458                    Selector::Begin(_) => {}
2459                    _ => panic!("Expected Selector::Begin for first query"),
2460                }
2461                Err(Status::internal("Initial inline-begin failed"))
2462            });
2463
2464        // 2. Fallback BeginTransaction RPC also fails.
2465        mock.expect_begin_transaction()
2466            .times(1)
2467            .in_sequence(&mut seq)
2468            .returning(|_| Err(Status::internal("Fallback BeginTransaction failed")));
2469
2470        // Any further attempts would panic because we haven't mocked them.
2471        mock.expect_execute_streaming_sql().never();
2472
2473        let (db_client, _server) = setup_db_client(mock).await;
2474        let tx = db_client
2475            .read_only_transaction()
2476            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2477            .build()
2478            .await?;
2479
2480        // First query: triggers the failure and transitions the state to Failed.
2481        let err1 = tx
2482            .execute_query(Statement::builder("SELECT 1").build())
2483            .await
2484            .expect_err("First query should fail");
2485        assert!(
2486            err1.to_string()
2487                .contains("Fallback BeginTransaction failed")
2488        );
2489
2490        // Second query: starts AFTER the failure is already cached.
2491        // It should immediately return the same error without invoking the mock server.
2492        let err2 = tx
2493            .execute_query(Statement::builder("SELECT 1").build())
2494            .await
2495            .expect_err("Late query should fail immediately");
2496        assert!(
2497            err2.to_string()
2498                .contains("Fallback BeginTransaction failed")
2499        );
2500
2501        Ok(())
2502    }
2503
2504    #[tokio_test_no_panics]
2505    async fn execute_concurrent_reads_inline_begin() -> anyhow::Result<()> {
2506        use crate::key::KeySet;
2507        use crate::read::ReadRequest;
2508        let mut mock = create_session_mock();
2509        mock.expect_begin_transaction().never();
2510
2511        let mut seq = mockall::Sequence::new();
2512        let (tx_sender, rx_receiver) = mpsc::channel(1);
2513        let rx_receiver = Arc::new(Mutex::new(Some(rx_receiver)));
2514
2515        let task1_ready = Arc::new(Notify::new());
2516        let task1_ready_clone = Arc::clone(&task1_ready);
2517        let tasks_started = Arc::new(Barrier::new(3));
2518
2519        // 1. First read: should include Selector::Begin
2520        mock.expect_streaming_read()
2521            .times(1)
2522            .in_sequence(&mut seq)
2523            .returning(move |req| {
2524                task1_ready_clone.notify_one();
2525                let req = req.into_inner();
2526                match req
2527                    .transaction
2528                    .expect("transaction should be present")
2529                    .selector
2530                    .expect("selector should be present")
2531                {
2532                    mock_v1::transaction_selector::Selector::Begin(_) => {}
2533                    _ => panic!("Expected Selector::Begin for first read"),
2534                }
2535
2536                let rx = rx_receiver
2537                    .try_lock()
2538                    .expect("mutex poisoned")
2539                    .take()
2540                    .expect("receiver should be present");
2541                Ok(Response::from(rx))
2542            });
2543
2544        // 2. The other reads: should include populated Selector::Id
2545        mock.expect_streaming_read()
2546            .times(2)
2547            .in_sequence(&mut seq)
2548            .returning(move |req| {
2549                let req = req.into_inner();
2550                match req
2551                    .transaction
2552                    .expect("transaction should be present")
2553                    .selector
2554                    .expect("selector should be present")
2555                {
2556                    mock_v1::transaction_selector::Selector::Id(id) => {
2557                        assert_eq!(id, vec![4, 5, 6]);
2558                    }
2559                    _ => panic!("Expected Selector::Id for other reads"),
2560                }
2561
2562                let (tx, rx) = mpsc::channel(1);
2563                tx.try_send(Ok(setup_select1()))
2564                    .expect("send should succeed");
2565                Ok(Response::from(rx))
2566            });
2567
2568        let (db_client, _server) = setup_db_client(mock).await;
2569        let tx = db_client
2570            .read_only_transaction()
2571            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2572            .build()
2573            .await?;
2574        let tx = Arc::new(tx);
2575
2576        let read_req = ReadRequest::builder("Table", vec!["Col"])
2577            .with_keys(KeySet::all())
2578            .build();
2579
2580        // Spawn 3 concurrent reads.
2581        let tx1 = Arc::clone(&tx);
2582        let read1 = read_req.clone();
2583        let handle1 = tokio::spawn(async move {
2584            let mut rs = tx1.execute_read(read1).await?;
2585            let _ = rs.next().await;
2586            Ok::<_, crate::Error>(rs)
2587        });
2588
2589        task1_ready.notified().await;
2590
2591        let tx2 = Arc::clone(&tx);
2592        let read2 = read_req.clone();
2593        let tasks_started2 = Arc::clone(&tasks_started);
2594        let handle2 = tokio::spawn(async move {
2595            tasks_started2.wait().await;
2596            let mut rs = tx2.execute_read(read2).await?;
2597            let _ = rs.next().await;
2598            Ok::<_, crate::Error>(rs)
2599        });
2600
2601        let tx3 = Arc::clone(&tx);
2602        let read3 = read_req.clone();
2603        let tasks_started3 = Arc::clone(&tasks_started);
2604        let handle3 = tokio::spawn(async move {
2605            tasks_started3.wait().await;
2606            let mut rs = tx3.execute_read(read3).await?;
2607            let _ = rs.next().await;
2608            Ok::<_, crate::Error>(rs)
2609        });
2610
2611        tasks_started.wait().await;
2612        tokio::task::yield_now().await;
2613
2614        // Provide the transaction ID.
2615        let mut rs = setup_select1();
2616        rs.metadata
2617            .as_mut()
2618            .expect("metadata should be present")
2619            .transaction = Some(mock_v1::Transaction {
2620            id: vec![4, 5, 6],
2621            ..Default::default()
2622        });
2623        tx_sender.send(Ok(rs)).await.expect("send failed");
2624        drop(tx_sender);
2625
2626        let mut rs1 = handle1.await.expect("Task 1 panicked")?;
2627        let mut rs2 = handle2.await.expect("Task 2 panicked")?;
2628        let mut rs3 = handle3.await.expect("Task 3 panicked")?;
2629
2630        assert!(rs1.next().await.is_none());
2631        assert!(rs2.next().await.is_none());
2632        assert!(rs3.next().await.is_none());
2633
2634        Ok(())
2635    }
2636
2637    #[tokio_test_no_panics]
2638    async fn execute_inline_begin_idempotent_update() -> anyhow::Result<()> {
2639        let (db_client, _server) = setup_db_client(create_session_mock()).await;
2640        // Access internal state for unit testing.
2641        let tx = db_client
2642            .read_only_transaction()
2643            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2644            .build()
2645            .await?;
2646
2647        let id1 = bytes::Bytes::from_static(b"tx1");
2648        let id2 = bytes::Bytes::from_static(b"tx2");
2649
2650        // 1. Initial update.
2651        tx.context.transaction_selector.update(id1.clone(), None)?;
2652        assert_eq!(
2653            tx.context
2654                .transaction_selector
2655                .selector()
2656                .await?
2657                .id()
2658                .expect("ID should be present"),
2659            &id1
2660        );
2661
2662        // 2. Redundant update with same ID should succeed, as Spanner returns the
2663        // transaction ID on all statements executed within that transaction when
2664        // using multiplexed sessions.
2665        tx.context.transaction_selector.update(id1.clone(), None)?;
2666
2667        // 3. Update with DIFFERENT ID after already Started should fail.
2668        let err2 = tx
2669            .context
2670            .transaction_selector
2671            .update(id2, None)
2672            .expect_err("Update after Started should fail");
2673        assert!(err2.to_string().contains("already Started or Failed"));
2674
2675        Ok(())
2676    }
2677
2678    #[tokio_test_no_panics]
2679    async fn execute_inline_begin_with_transient_failure() -> anyhow::Result<()> {
2680        let mut mock = create_session_mock();
2681        let mut seq = mockall::Sequence::new();
2682
2683        // 1. First attempt fails transiently.
2684        mock.expect_execute_streaming_sql()
2685            .times(1)
2686            .in_sequence(&mut seq)
2687            .returning(|_| Err(Status::new(Code::Unavailable, "Transient 1")));
2688
2689        // 2. Fallback BeginTransaction succeeds.
2690        mock.expect_begin_transaction()
2691            .times(1)
2692            .in_sequence(&mut seq)
2693            .returning(|_| {
2694                Ok(Response::new(mock_v1::Transaction {
2695                    id: vec![7, 8, 9],
2696                    ..Default::default()
2697                }))
2698            });
2699
2700        // 3. The manual retry of the query (which happens after explicit begin fallback).
2701        mock.expect_execute_streaming_sql()
2702            .times(1)
2703            .in_sequence(&mut seq)
2704            .returning(|_| {
2705                let (tx, rx) = mpsc::channel(1);
2706                tx.try_send(Ok(setup_select1()))
2707                    .expect("send should succeed");
2708                Ok(Response::from(rx))
2709            });
2710
2711        let (db_client, _server) = setup_db_client(mock).await;
2712        let tx = db_client
2713            .read_only_transaction()
2714            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2715            .build()
2716            .await?;
2717
2718        let mut rs = tx
2719            .execute_query(Statement::builder("SELECT 1").build())
2720            .await?;
2721        assert!(rs.next().await.is_some());
2722        assert!(rs.next().await.is_none());
2723
2724        Ok(())
2725    }
2726
2727    #[tokio_test_no_panics]
2728    async fn leader_aware_routing_query_in_read_only() -> anyhow::Result<()> {
2729        let mut mock = create_session_mock();
2730        mock.expect_execute_streaming_sql().once().returning(|req| {
2731            assert!(
2732                req.metadata()
2733                    .get("x-goog-spanner-route-to-leader")
2734                    .is_none()
2735            );
2736            let stream = adapt([Ok(mock_v1::PartialResultSet {
2737                metadata: Some(mock_v1::ResultSetMetadata {
2738                    row_type: Some(mock_v1::StructType { fields: vec![] }),
2739                    ..Default::default()
2740                }),
2741                ..Default::default()
2742            })]);
2743            Ok(tonic::Response::from(stream))
2744        });
2745
2746        let (db_client, _server) = setup_db_client(mock).await;
2747        let tx = db_client.single_use().build();
2748        let _rs = tx
2749            .execute_query(Statement::builder("SELECT 1").build())
2750            .await?;
2751        Ok(())
2752    }
2753
2754    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2755    async fn execute_concurrent_begin_explicitly_redundancy_prevention() -> anyhow::Result<()> {
2756        let (tx_rpc, rx_rpc) = std_channel();
2757        let (tx_started, rx_started) = oneshot_channel();
2758        let tx_started_mutex = StdMutex::new(Some(tx_started));
2759
2760        let mut mock = create_session_mock();
2761        let mut seq = mockall::Sequence::new();
2762
2763        // Task 1 (leader) fires the initial query inline.
2764        mock.expect_execute_streaming_sql()
2765            .once()
2766            .in_sequence(&mut seq)
2767            .returning(move |_req| {
2768                if let Some(tx) = tx_started_mutex.lock().expect("mutex poisoned").take() {
2769                    let _ = tx.send(());
2770                }
2771                rx_rpc.recv().expect("channel broken");
2772                let (tx, rx) = mpsc::channel(1);
2773                let metadata = mock_v1::ResultSetMetadata {
2774                    transaction: Some(mock_v1::Transaction {
2775                        id: vec![42],
2776                        ..Default::default()
2777                    }),
2778                    ..Default::default()
2779                };
2780                let prs = mock_v1::PartialResultSet {
2781                    metadata: Some(metadata),
2782                    ..Default::default()
2783                };
2784                tx.try_send(Ok(prs)).expect("send should succeed");
2785                Ok(tonic::Response::new(rx))
2786            });
2787
2788        // Task 2 (follower) arrives while Task 1 is in flight, suspends until Task 1 completes,
2789        // and then successfully fires its query using the newly extracted transaction ID (vec![42]).
2790        mock.expect_execute_streaming_sql()
2791            .once()
2792            .in_sequence(&mut seq)
2793            .returning(move |req| {
2794                let req = req.into_inner();
2795                assert_eq!(
2796                    req.transaction,
2797                    Some(mock_v1::TransactionSelector {
2798                        selector: Some(mock_v1::transaction_selector::Selector::Id(vec![42])),
2799                    })
2800                );
2801                let (tx, rx) = mpsc::channel(1);
2802                let metadata = mock_v1::ResultSetMetadata {
2803                    row_type: Some(mock_v1::StructType { fields: vec![] }),
2804                    ..Default::default()
2805                };
2806                let prs = mock_v1::PartialResultSet {
2807                    metadata: Some(metadata),
2808                    ..Default::default()
2809                };
2810                tx.try_send(Ok(prs)).expect("send should succeed");
2811                Ok(tonic::Response::new(rx))
2812            });
2813
2814        let (db_client, _server) = setup_db_client(mock).await;
2815
2816        let tx = Arc::new(
2817            db_client
2818                .read_only_transaction()
2819                .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
2820                .build()
2821                .await?,
2822        );
2823
2824        let tx_leader = Arc::clone(&tx);
2825        let handle_leader = tokio::spawn(async move {
2826            let mut rs = tx_leader
2827                .execute_query(Statement::builder("SELECT 1").build())
2828                .await?;
2829            let _ = rs.next().await;
2830            Ok::<_, crate::Error>(())
2831        });
2832
2833        rx_started.await.expect("oneshot broken");
2834
2835        // Now the state is Starting and the leader is blocked inside execute_streaming_sql.
2836        // Task 2 executes a concurrent query, which must wait for the leader rather than firing a redundant RPC.
2837        let tx_follower = Arc::clone(&tx);
2838        let handle_follower = tokio::spawn(async move {
2839            let mut rs = tx_follower
2840                .execute_query(Statement::builder("SELECT 2").build())
2841                .await?;
2842            let _ = rs.next().await;
2843            Ok::<_, crate::Error>(())
2844        });
2845
2846        // Unblock the leader
2847        tx_rpc.send(()).expect("send failed");
2848
2849        handle_leader.await.expect("Task 1 panicked")?;
2850        handle_follower.await.expect("Task 2 panicked")?;
2851
2852        Ok(())
2853    }
2854
2855    #[tokio_test_no_panics]
2856    async fn execute_multi_query_redundant_transaction_id_explicit() -> anyhow::Result<()> {
2857        run_execute_multi_query_redundant_transaction_id(BeginTransactionOption::ExplicitBegin)
2858            .await
2859    }
2860
2861    #[tokio_test_no_panics]
2862    async fn execute_multi_query_redundant_transaction_id_inline() -> anyhow::Result<()> {
2863        run_execute_multi_query_redundant_transaction_id(BeginTransactionOption::InlineBegin).await
2864    }
2865
2866    async fn run_execute_multi_query_redundant_transaction_id(
2867        option: BeginTransactionOption,
2868    ) -> anyhow::Result<()> {
2869        let mut mock = create_session_mock();
2870        let mut sequence = mockall::Sequence::new();
2871
2872        if option == BeginTransactionOption::ExplicitBegin {
2873            mock.expect_begin_transaction()
2874                .once()
2875                .in_sequence(&mut sequence)
2876                .returning(|req| {
2877                    let req = req.into_inner();
2878                    assert_eq!(
2879                        req.session,
2880                        "projects/p/instances/i/databases/d/sessions/123"
2881                    );
2882                    Ok(tonic::Response::new(mock_v1::Transaction {
2883                        id: vec![4, 5, 6],
2884                        read_timestamp: Some(prost_types::Timestamp {
2885                            seconds: 123456789,
2886                            nanos: 0,
2887                        }),
2888                        ..Default::default()
2889                    }))
2890                });
2891
2892            mock.expect_execute_streaming_sql()
2893                .times(2)
2894                .returning(|req| {
2895                    let req = req.into_inner();
2896                    assert_eq!(
2897                        req.transaction
2898                            .expect("transaction should be present")
2899                            .selector
2900                            .expect("selector should be present"),
2901                        mock_v1::transaction_selector::Selector::Id(vec![4, 5, 6])
2902                    );
2903
2904                    let mut result_set_partial = setup_select1();
2905                    result_set_partial
2906                        .metadata
2907                        .as_mut()
2908                        .expect("metadata should be present")
2909                        .transaction = Some(mock_v1::Transaction {
2910                        id: vec![4, 5, 6],
2911                        read_timestamp: Some(prost_types::Timestamp {
2912                            seconds: 123456789,
2913                            nanos: 0,
2914                        }),
2915                        ..Default::default()
2916                    });
2917                    Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
2918                        result_set_partial,
2919                    )])))
2920                });
2921        } else {
2922            mock.expect_begin_transaction().never();
2923
2924            mock.expect_execute_streaming_sql()
2925                .times(1)
2926                .in_sequence(&mut sequence)
2927                .returning(|req| {
2928                    let req = req.into_inner();
2929                    assert_eq!(
2930                        req.session,
2931                        "projects/p/instances/i/databases/d/sessions/123"
2932                    );
2933
2934                    match req
2935                        .transaction
2936                        .expect("transaction should be present")
2937                        .selector
2938                        .expect("selector should be present")
2939                    {
2940                        mock_v1::transaction_selector::Selector::Begin(_) => {}
2941                        _ => panic!("Expected Selector::Begin"),
2942                    }
2943                    let mut result_set_partial = setup_select1();
2944                    result_set_partial
2945                        .metadata
2946                        .as_mut()
2947                        .expect("metadata should be present")
2948                        .transaction = Some(mock_v1::Transaction {
2949                        id: vec![4, 5, 6],
2950                        read_timestamp: Some(prost_types::Timestamp {
2951                            seconds: 987654321,
2952                            nanos: 0,
2953                        }),
2954                        ..Default::default()
2955                    });
2956                    Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
2957                        result_set_partial,
2958                    )])))
2959                });
2960
2961            mock.expect_execute_streaming_sql()
2962                .times(1)
2963                .in_sequence(&mut sequence)
2964                .returning(|req| {
2965                    let req = req.into_inner();
2966                    match req
2967                        .transaction
2968                        .expect("transaction should be present")
2969                        .selector
2970                        .expect("selector should be present")
2971                    {
2972                        mock_v1::transaction_selector::Selector::Id(id) => {
2973                            assert_eq!(id, vec![4, 5, 6]);
2974                        }
2975                        _ => panic!("Expected Selector::Id"),
2976                    }
2977                    let mut result_set_partial = setup_select1();
2978                    result_set_partial
2979                        .metadata
2980                        .as_mut()
2981                        .expect("metadata should be present")
2982                        .transaction = Some(mock_v1::Transaction {
2983                        id: vec![4, 5, 6],
2984                        read_timestamp: Some(prost_types::Timestamp {
2985                            seconds: 987654321,
2986                            nanos: 0,
2987                        }),
2988                        ..Default::default()
2989                    });
2990                    Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
2991                        result_set_partial,
2992                    )])))
2993                });
2994        }
2995
2996        let (db_client, _server) = setup_db_client(mock).await;
2997
2998        let transaction = db_client
2999            .read_only_transaction()
3000            .with_begin_transaction_option(option)
3001            .build()
3002            .await
3003            .expect("Failed to start transaction");
3004
3005        for _ in 0..2 {
3006            let mut result_set = transaction
3007                .execute_query(Statement::builder("SELECT 1").build())
3008                .await
3009                .expect("Failed to execute query");
3010
3011            let row = result_set
3012                .next()
3013                .await
3014                .expect("has row")
3015                .expect("has valid row");
3016            assert_eq!(row.raw_values(), [Value(string_val("1"))]);
3017
3018            let next_result = result_set.next().await;
3019            assert!(next_result.is_none(), "expected None, got {next_result:?}");
3020        }
3021
3022        Ok(())
3023    }
3024
3025    #[tokio_test_no_panics]
3026    async fn read_only_transaction_begin_with_never_retry() -> anyhow::Result<()> {
3027        let mut mock = MockSpanner::new();
3028        let mut sequence = mockall::Sequence::new();
3029
3030        mock.expect_begin_transaction()
3031            .once()
3032            .in_sequence(&mut sequence)
3033            .returning(|_| Err(tonic::Status::unavailable("transient error")));
3034
3035        mock.expect_create_session().returning(|_| {
3036            Ok(Response::new(mock_v1::Session {
3037                name: "session".to_string(),
3038                multiplexed: true,
3039                ..Default::default()
3040            }))
3041        });
3042
3043        let (db_client, _server) = setup_db_client(mock).await;
3044
3045        let res = db_client
3046            .read_only_transaction()
3047            .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
3048            .with_begin_retry_policy(NeverRetry)
3049            .build()
3050            .await;
3051
3052        assert!(res.is_err(), "should fail immediately without retry");
3053        let err = res.unwrap_err();
3054        assert_eq!(err.status().expect("status").code, GaxCode::Unavailable);
3055
3056        Ok(())
3057    }
3058
3059    #[tokio_test_no_panics]
3060    async fn read_only_transaction_lazy_begin_fallback_never_retry() -> anyhow::Result<()> {
3061        let mut mock = MockSpanner::new();
3062        let mut sequence = mockall::Sequence::new();
3063
3064        // 1. First query execution fails with Unavailable (transient error)
3065        mock.expect_execute_streaming_sql()
3066            .once()
3067            .in_sequence(&mut sequence)
3068            .returning(|_| Err(tonic::Status::unavailable("transient error")));
3069
3070        // 2. Fallback explicit BeginTransaction is executed exactly once and fails
3071        mock.expect_begin_transaction()
3072            .once()
3073            .in_sequence(&mut sequence)
3074            .returning(|_| Err(tonic::Status::unavailable("transient error")));
3075
3076        mock.expect_create_session().returning(|_| {
3077            Ok(Response::new(mock_v1::Session {
3078                name: "session".to_string(),
3079                multiplexed: true,
3080                ..Default::default()
3081            }))
3082        });
3083
3084        let (db_client, _server) = setup_db_client(mock).await;
3085
3086        let transaction = db_client
3087            .read_only_transaction()
3088            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
3089            .with_begin_retry_policy(NeverRetry)
3090            .build()
3091            .await?;
3092
3093        let stmt = Statement::builder("SELECT 1").build();
3094        let res = transaction.execute_query(stmt).await;
3095
3096        assert!(
3097            res.is_err(),
3098            "should fail immediately during fallback without retrying the fallback RPC"
3099        );
3100        let err = res.unwrap_err();
3101        assert_eq!(err.status().expect("status").code, GaxCode::Unavailable);
3102
3103        Ok(())
3104    }
3105
3106    #[tokio_test_no_panics]
3107    async fn read_only_transaction_begin_with_attempt_timeout() -> anyhow::Result<()> {
3108        let mut mock = MockSpanner::new();
3109        let mut sequence = mockall::Sequence::new();
3110
3111        mock.expect_begin_transaction()
3112            .once()
3113            .in_sequence(&mut sequence)
3114            .withf(|req| {
3115                let timeout_header = req.metadata().get("grpc-timeout");
3116                assert!(
3117                    timeout_header.is_some(),
3118                    "grpc-timeout header should be present"
3119                );
3120                let val = timeout_header.unwrap().to_str().unwrap();
3121                assert!(
3122                    val.contains("5000") || val.contains("5"),
3123                    "timeout header value '{}' should represent 5 seconds",
3124                    val
3125                );
3126                true
3127            })
3128            .returning(|_| {
3129                Ok(Response::new(mock_v1::Transaction {
3130                    id: vec![42],
3131                    ..Default::default()
3132                }))
3133            });
3134
3135        mock.expect_create_session().returning(|_| {
3136            Ok(Response::new(mock_v1::Session {
3137                name: "session".to_string(),
3138                multiplexed: true,
3139                ..Default::default()
3140            }))
3141        });
3142
3143        let (db_client, _server) = setup_db_client(mock).await;
3144
3145        let _transaction = db_client
3146            .read_only_transaction()
3147            .with_begin_transaction_option(BeginTransactionOption::ExplicitBegin)
3148            .with_begin_attempt_timeout(std::time::Duration::from_secs(5))
3149            .build()
3150            .await?;
3151
3152        Ok(())
3153    }
3154
3155    #[tokio_test_no_panics]
3156    async fn read_only_transaction_builder_sets_gax_options() -> anyhow::Result<()> {
3157        let mut mock = MockSpanner::new();
3158        mock.expect_create_session().returning(|_| {
3159            Ok(Response::new(mock_v1::Session {
3160                name: "session".to_string(),
3161                multiplexed: true,
3162                ..Default::default()
3163            }))
3164        });
3165        let (db_client, _server) = setup_db_client(mock).await;
3166
3167        let builder = db_client
3168            .read_only_transaction()
3169            .with_begin_attempt_timeout(Duration::from_secs(5))
3170            .with_begin_retry_policy(NeverRetry)
3171            .with_begin_backoff_policy(ExponentialBackoff::default());
3172
3173        let gax = builder
3174            .begin_gax_options
3175            .as_ref()
3176            .expect("begin_gax_options missing");
3177        assert_eq!(*gax.attempt_timeout(), Some(Duration::from_secs(5)));
3178        assert!(gax.retry_policy().is_some());
3179        assert!(gax.backoff_policy().is_some());
3180
3181        Ok(())
3182    }
3183
3184    #[tokio_test_no_panics]
3185    async fn read_only_transaction_lazy_begin_fallback_uses_statement_options_when_unconfigured()
3186    -> anyhow::Result<()> {
3187        let mut mock = MockSpanner::new();
3188        let mut sequence = mockall::Sequence::new();
3189
3190        // 1. First query execution fails with Unavailable (transient error)
3191        mock.expect_execute_streaming_sql()
3192            .once()
3193            .in_sequence(&mut sequence)
3194            .returning(|_| Err(tonic::Status::unavailable("transient error")));
3195
3196        // 2. Fallback explicit BeginTransaction is executed. Since the transaction itself has no
3197        // custom options, it must inherit the statement options, which set attempt_timeout to 5 seconds.
3198        mock.expect_begin_transaction()
3199            .once()
3200            .in_sequence(&mut sequence)
3201            .withf(|req| {
3202                let timeout_header = req.metadata().get("grpc-timeout");
3203                assert!(
3204                    timeout_header.is_some(),
3205                    "grpc-timeout header should be present"
3206                );
3207                let val = timeout_header.unwrap().to_str().unwrap();
3208                assert!(
3209                    val.contains("5000") || val.contains("5"),
3210                    "timeout header value '{}' should represent 5 seconds",
3211                    val
3212                );
3213                true
3214            })
3215            .returning(|_| {
3216                Ok(Response::new(mock_v1::Transaction {
3217                    id: vec![42],
3218                    ..Default::default()
3219                }))
3220            });
3221
3222        // 3. Query is retried with the successfully obtained transaction ID, succeeding this time
3223        mock.expect_execute_streaming_sql()
3224            .once()
3225            .in_sequence(&mut sequence)
3226            .withf(|req| {
3227                matches!(
3228                    req.get_ref()
3229                        .transaction
3230                        .as_ref()
3231                        .and_then(|t| t.selector.as_ref()),
3232                    Some(mock_v1::transaction_selector::Selector::Id(id)) if id == &vec![42]
3233                )
3234            })
3235            .returning(|_| {
3236                let mut result_set_partial = setup_select1();
3237                result_set_partial
3238                    .metadata
3239                    .as_mut()
3240                    .expect("metadata should be present")
3241                    .transaction = Some(mock_v1::Transaction {
3242                    id: vec![42],
3243                    ..Default::default()
3244                });
3245                Ok(gaxi::grpc::tonic::Response::from(adapt([Ok(
3246                    result_set_partial,
3247                )])))
3248            });
3249
3250        mock.expect_create_session().returning(|_| {
3251            Ok(Response::new(mock_v1::Session {
3252                name: "session".to_string(),
3253                multiplexed: true,
3254                ..Default::default()
3255            }))
3256        });
3257
3258        let (db_client, _server) = setup_db_client(mock).await;
3259
3260        let transaction = db_client
3261            .read_only_transaction()
3262            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
3263            .build()
3264            .await?;
3265
3266        let mut stmt_opts = crate::RequestOptions::default();
3267        stmt_opts.set_attempt_timeout(Duration::from_secs(5));
3268        let stmt = Statement::builder("SELECT 1")
3269            .build()
3270            .with_gax_options(stmt_opts);
3271
3272        let mut rs = transaction.execute_query(stmt).await?;
3273        let row = rs.next().await.expect("has row")?;
3274        assert_eq!(row.raw_values(), [Value(string_val("1"))]);
3275
3276        Ok(())
3277    }
3278
3279    #[tokio_test_no_panics]
3280    async fn read_only_transaction_lazy_begin_fallback_merges_custom_options() -> anyhow::Result<()>
3281    {
3282        let mut mock = MockSpanner::new();
3283        let mut sequence = mockall::Sequence::new();
3284
3285        // 1. First query execution fails with Unavailable (transient error)
3286        mock.expect_execute_streaming_sql()
3287            .once()
3288            .in_sequence(&mut sequence)
3289            .returning(|_| Err(tonic::Status::unavailable("transient error")));
3290
3291        // 2. Fallback explicit BeginTransaction must have BOTH:
3292        // - attempt_timeout of 5 seconds (inherited from statement's options)
3293        // - retry_policy of NeverRetry (inherited from transaction's begin options)
3294        // If it did not merge correctly, the timeout header would be missing, or it would retry.
3295        mock.expect_begin_transaction()
3296            .once()
3297            .in_sequence(&mut sequence)
3298            .withf(|req| {
3299                let timeout_header = req.metadata().get("grpc-timeout");
3300                assert!(
3301                    timeout_header.is_some(),
3302                    "grpc-timeout header should be present"
3303                );
3304                let val = timeout_header.unwrap().to_str().unwrap();
3305                assert!(
3306                    val.contains("5000") || val.contains("5"),
3307                    "timeout header value '{}' should represent 5 seconds",
3308                    val
3309                );
3310                true
3311            })
3312            .returning(|_| Err(tonic::Status::unavailable("transient error")));
3313
3314        mock.expect_create_session().returning(|_| {
3315            Ok(Response::new(mock_v1::Session {
3316                name: "session".to_string(),
3317                multiplexed: true,
3318                ..Default::default()
3319            }))
3320        });
3321
3322        let (db_client, _server) = setup_db_client(mock).await;
3323
3324        let transaction = db_client
3325            .read_only_transaction()
3326            .with_begin_transaction_option(BeginTransactionOption::InlineBegin)
3327            .with_begin_retry_policy(NeverRetry)
3328            .build()
3329            .await?;
3330
3331        let mut stmt_opts = crate::RequestOptions::default();
3332        stmt_opts.set_attempt_timeout(Duration::from_secs(5));
3333        let stmt = Statement::builder("SELECT 1")
3334            .build()
3335            .with_gax_options(stmt_opts);
3336
3337        let res = transaction.execute_query(stmt).await;
3338
3339        assert!(
3340            res.is_err(),
3341            "should fail immediately because of NeverRetry"
3342        );
3343        let err = res.unwrap_err();
3344        assert_eq!(err.status().expect("status").code, GaxCode::Unavailable);
3345
3346        Ok(())
3347    }
3348
3349    #[test]
3350    fn test_merge_request_options() {
3351        // Case 1: Destination has values, source is empty (Destination preserved)
3352        let mut dest = crate::RequestOptions::default();
3353        dest.set_attempt_timeout(Duration::from_secs(2));
3354        dest.set_retry_policy(NeverRetry);
3355
3356        // Source is None (Destination preserved)
3357        let merged = merge_request_options(dest, None);
3358
3359        assert_eq!(*merged.attempt_timeout(), Some(Duration::from_secs(2)));
3360        assert!(merged.retry_policy().is_some());
3361
3362        // Case 2: Source has overriding values, destination is empty (Source overrides)
3363        let dest = crate::RequestOptions::default();
3364
3365        let mut source = crate::RequestOptions::default();
3366        source.set_attempt_timeout(Duration::from_secs(5));
3367        source.set_retry_policy(NeverRetry);
3368
3369        let merged = merge_request_options(dest, Some(&source));
3370
3371        assert_eq!(*merged.attempt_timeout(), Some(Duration::from_secs(5)));
3372        assert!(merged.retry_policy().is_some());
3373
3374        // Case 3: Both have distinct custom headers (Headers must merge/combine)
3375        let mut dest = crate::RequestOptions::default();
3376        let mut dest_headers = HeaderMap::new();
3377        dest_headers.insert(
3378            HeaderName::from_static("x-goog-spanner-route-to-leader"),
3379            HeaderValue::from_static("true"),
3380        );
3381        dest = dest.insert_extension(dest_headers);
3382
3383        let mut source = crate::RequestOptions::default();
3384        let mut src_headers = HeaderMap::new();
3385        src_headers.insert(
3386            HeaderName::from_static("x-custom-header"),
3387            HeaderValue::from_static("custom-value"),
3388        );
3389        source = source.insert_extension(src_headers);
3390
3391        let merged = merge_request_options(dest, Some(&source));
3392        let merged_headers = merged
3393            .get_extension::<HeaderMap>()
3394            .expect("HeaderMap missing");
3395
3396        assert_eq!(
3397            merged_headers
3398                .get("x-goog-spanner-route-to-leader")
3399                .unwrap()
3400                .to_str()
3401                .unwrap(),
3402            "true"
3403        );
3404        assert_eq!(
3405            merged_headers
3406                .get("x-custom-header")
3407                .unwrap()
3408                .to_str()
3409                .unwrap(),
3410            "custom-value"
3411        );
3412    }
3413
3414    #[test]
3415    fn test_transaction_selector_check_failed_propagates_original_error() {
3416        let options = TransactionOptions {
3417            mode: Some(Mode::ReadWrite(Default::default())),
3418            ..Default::default()
3419        };
3420        let selector = ReadContextTransactionSelector::Lazy(Arc::new(StdMutex::new(
3421            TransactionState::Starting(options, Arc::new(Notify::new())),
3422        )));
3423
3424        let initial_err = crate::error::internal_error("initial statement error");
3425        selector.set_failed(&initial_err);
3426
3427        assert!(selector.is_first_statement_failed());
3428
3429        let res = selector.check_failed();
3430        assert!(res.is_err());
3431        let err = res.unwrap_err();
3432        assert!(
3433            err.to_string()
3434                .contains("Aborted due to failed initial statement")
3435        );
3436    }
3437
3438    #[tokio_test_no_panics]
3439    async fn test_subsequent_query_failure_does_not_lock_mutex() -> anyhow::Result<()> {
3440        let mut mock = create_session_mock();
3441
3442        // The query fails with a transient error
3443        mock.expect_execute_streaming_sql()
3444            .once()
3445            .returning(|_| Err(tonic::Status::new(tonic::Code::Internal, "query failed")));
3446
3447        let (db_client, _server) = setup_db_client(mock).await;
3448
3449        // Construct a lazy read-only transaction context
3450        let mut transaction_selector = crate::model::TransactionSelector::default();
3451        transaction_selector.selector = Some(crate::model::transaction_selector::Selector::Id(
3452            bytes::Bytes::copy_from_slice(&[1, 2, 3]),
3453        ));
3454
3455        let state = Arc::new(StdMutex::new(TransactionState::Started(
3456            transaction_selector.clone(),
3457            None,
3458        )));
3459        let selector = ReadContextTransactionSelector::Lazy(state.clone());
3460
3461        let context = ReadContext {
3462            session_name: "projects/p/instances/i/databases/d/sessions/123".to_string(),
3463            client: db_client,
3464            transaction_selector: selector,
3465            precommit_token_tracker: crate::read_only_transaction::PrecommitTokenTracker::new(),
3466            transaction_tag: None,
3467            channel_hint: 0,
3468            begin_transaction_request_options: None,
3469        };
3470
3471        // Poison the mutex
3472        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3473            let _guard = state.lock().unwrap();
3474            panic!("poisoning the mutex intentionally");
3475        }));
3476
3477        // Execute a subsequent query (request uses Selector::Id, meaning it's a subsequent query)
3478        let statement = Statement::builder("SELECT 1").build();
3479        let request = statement
3480            .into_request()
3481            .set_session(context.session_name.clone())
3482            .set_transaction(transaction_selector);
3483
3484        let gax_options = google_cloud_gax::options::RequestOptions::default();
3485
3486        async fn run_macro(
3487            context: &ReadContext,
3488            mut request: crate::model::ExecuteSqlRequest,
3489            gax_options: google_cloud_gax::options::RequestOptions,
3490        ) -> crate::Result<ResultSet> {
3491            execute_stream_with_retry!(
3492                context,
3493                request,
3494                gax_options,
3495                execute_streaming_sql,
3496                StreamOperation::Query
3497            )
3498        }
3499
3500        let res = run_macro(&context, request, gax_options).await;
3501
3502        // Assert that we get the query failed error rather than a panic from the poisoned mutex
3503        assert!(res.is_err());
3504        let err = res.unwrap_err();
3505        assert_eq!(err.status().map(|s| s.code), Some(GaxCode::Internal));
3506        assert_eq!(
3507            err.status().map(|s| s.message.as_str()),
3508            Some("query failed")
3509        );
3510
3511        Ok(())
3512    }
3513}