Skip to main content

google_cloud_spanner/
read.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::key::KeySet;
16use crate::model::DirectedReadOptions;
17use crate::model::read_request::{LockHint, OrderBy};
18use crate::model::request_options::Priority;
19use google_cloud_gax::backoff_policy::BackoffPolicyArg;
20use google_cloud_gax::options::RequestOptions as GaxRequestOptions;
21use google_cloud_gax::retry_policy::RetryPolicyArg;
22use std::time::Duration;
23
24/// Represents an incomplete read operation that requires specifying keys.
25///
26/// # Example
27/// ```
28/// # use google_cloud_spanner::read::ReadRequest;
29/// # use google_cloud_spanner::key::KeySet;
30/// # use google_cloud_spanner::key;
31/// // Read all rows from a table using its primary key.
32/// let read_all = ReadRequest::builder("Users", vec!["Id", "Name"])
33///     .with_keys(KeySet::all())
34///     .build();
35///
36/// // Read specific rows using an index.
37/// let read_by_id = ReadRequest::builder("Users", vec!["Id", "Name"])
38///     .with_index("UsersByIndex", key![1_i64])
39///     .set_limit(10)
40///     .build();
41/// ```
42///
43/// Use `ReadRequest::builder` to define the table and columns to be read.
44/// Keys must be supplied using `with_keys` (for the primary key) or `with_index` (for an index)
45/// to obtain an executable `ReadRequest`.
46#[derive(Clone, Debug, PartialEq)]
47pub struct ReadRequestBuilder {
48    table: String,
49    columns: Vec<String>,
50}
51
52impl ReadRequestBuilder {
53    /// Supplies the `KeySet` targeting the table's primary key.
54    ///
55    /// # Example
56    /// ```
57    /// # use google_cloud_spanner::read::ReadRequest;
58    /// # use google_cloud_spanner::key::KeySet;
59    /// let request = ReadRequest::builder("Users", vec!["Id", "Name"]).with_keys(KeySet::all());
60    /// ```
61    ///
62    /// The `keys` parameter identifies the rows to be yielded by naming the primary keys
63    /// of the rows in the table. Rows are yielded in table primary key order.
64    pub fn with_keys(self, keys: impl Into<KeySet>) -> ConfiguredReadRequestBuilder {
65        ConfiguredReadRequestBuilder {
66            table: self.table,
67            index: None,
68            keys: keys.into(),
69            columns: self.columns,
70            limit: None,
71            request_options: None,
72            directed_read_options: None,
73            order_by: None,
74            lock_hint: None,
75            gax_options: GaxRequestOptions::default(),
76        }
77    }
78
79    /// Supplies an index name and its associated `KeySet`.
80    ///
81    /// # Example
82    /// ```
83    /// # use google_cloud_spanner::read::ReadRequest;
84    /// # use google_cloud_spanner::key::KeySet;
85    /// # use google_cloud_spanner::key;
86    /// let request = ReadRequest::builder("Users", vec!["Id", "Name"]).with_index("UsersByIndex", key![1_i64]);
87    /// ```
88    ///
89    /// The `keys` parameter identifies the rows to be yielded by naming the index keys
90    /// in the provided `index`. Rows are yielded in index key order.
91    pub fn with_index(
92        self,
93        index: impl Into<String>,
94        keys: impl Into<KeySet>,
95    ) -> ConfiguredReadRequestBuilder {
96        ConfiguredReadRequestBuilder {
97            table: self.table,
98            index: Some(index.into()),
99            keys: keys.into(),
100            columns: self.columns,
101            limit: None,
102            request_options: None,
103            directed_read_options: None,
104            order_by: None,
105            lock_hint: None,
106            gax_options: GaxRequestOptions::default(),
107        }
108    }
109}
110
111/// A fully configured read request that is ready to be built.
112#[derive(Clone, Debug)]
113pub struct ConfiguredReadRequestBuilder {
114    table: String,
115    index: Option<String>,
116    keys: KeySet,
117    columns: Vec<String>,
118    limit: Option<i64>,
119    request_options: Option<crate::model::RequestOptions>,
120    directed_read_options: Option<DirectedReadOptions>,
121    order_by: Option<OrderBy>,
122    lock_hint: Option<LockHint>,
123    gax_options: GaxRequestOptions,
124}
125
126impl ConfiguredReadRequestBuilder {
127    /// Sets an optional limit on how many rows could be retrieved.
128    ///
129    /// # Example
130    /// ```
131    /// # use google_cloud_spanner::read::ReadRequest;
132    /// # use google_cloud_spanner::key::KeySet;
133    /// let request = ReadRequest::builder("Users", vec!["Id"])
134    ///     .with_keys(KeySet::all())
135    ///     .set_limit(10)
136    ///     .build();
137    /// ```
138    ///
139    /// If fewer rows are found, only the matching rows will be returned.
140    pub fn set_limit(mut self, limit: i64) -> Self {
141        self.limit = Some(limit);
142        self
143    }
144
145    /// Sets the request tag to use for this read.
146    ///
147    /// # Example
148    /// ```
149    /// # use google_cloud_spanner::read::ReadRequest;
150    /// # use google_cloud_spanner::key::KeySet;
151    /// let request = ReadRequest::builder("Users", vec!["Id"])
152    ///     .with_keys(KeySet::all())
153    ///     .set_request_tag("my-tag")
154    ///     .build();
155    /// ```
156    ///
157    /// See also: [Troubleshooting with tags](https://docs.cloud.google.com/spanner/docs/introspection/troubleshooting-with-tags)
158    pub fn set_request_tag(mut self, tag: impl Into<String>) -> Self {
159        self.request_options
160            .get_or_insert_with(crate::model::RequestOptions::default)
161            .request_tag = tag.into();
162        self
163    }
164
165    /// Sets the RPC priority to use for this read request.
166    ///
167    /// # Example
168    /// ```
169    /// # use google_cloud_spanner::read::ReadRequest;
170    /// # use google_cloud_spanner::key::KeySet;
171    /// # use google_cloud_spanner::model::request_options::Priority;
172    /// let request = ReadRequest::builder("Users", vec!["Id"])
173    ///     .with_keys(KeySet::all())
174    ///     .set_priority(Priority::Low)
175    ///     .build();
176    /// ```
177    pub fn set_priority(mut self, priority: Priority) -> Self {
178        self.request_options
179            .get_or_insert_with(crate::model::RequestOptions::default)
180            .priority = priority;
181        self
182    }
183
184    /// Sets the directed read options for this request.
185    ///
186    /// ```
187    /// # use google_cloud_spanner::read::ReadRequest;
188    /// # use google_cloud_spanner::key::KeySet;
189    /// # use google_cloud_spanner::model::DirectedReadOptions;
190    /// let dro = DirectedReadOptions::default();
191    /// let req = ReadRequest::builder("MyTable", vec!["col1"])
192    ///     .with_keys(KeySet::all())
193    ///     .set_directed_read_options(dro)
194    ///     .build();
195    /// ```
196    ///
197    /// DirectedReadOptions can only be specified for a read-only transaction,
198    /// otherwise Spanner returns an INVALID_ARGUMENT error.
199    pub fn set_directed_read_options(mut self, options: DirectedReadOptions) -> Self {
200        self.directed_read_options = Some(options);
201        self
202    }
203
204    /// Sets the order in which rows are returned.
205    ///
206    /// # Example
207    /// ```
208    /// # use google_cloud_spanner::read::ReadRequest;
209    /// # use google_cloud_spanner::key::KeySet;
210    /// # use google_cloud_spanner::model::read_request::OrderBy;
211    /// let request = ReadRequest::builder("Users", vec!["Id"])
212    ///     .with_keys(KeySet::all())
213    ///     .set_order_by(OrderBy::NoOrder);
214    /// ```
215    ///
216    /// By default, Spanner returns result rows in primary key order (or index key
217    /// order if reading via an index) except for `PartitionRead` requests.
218    ///
219    /// For applications that don't require rows to be returned in primary key
220    /// (`ORDER_BY_PRIMARY_KEY`) order, setting `ORDER_BY_NO_ORDER` option allows
221    /// Spanner to optimize row retrieval, resulting in lower latencies in certain
222    /// cases (for example, bulk point lookups).
223    pub fn set_order_by(mut self, order_by: OrderBy) -> Self {
224        self.order_by = Some(order_by);
225        self
226    }
227
228    /// Sets the lock hint for this read.
229    ///
230    /// # Example
231    /// ```
232    /// # use google_cloud_spanner::read::ReadRequest;
233    /// # use google_cloud_spanner::key::KeySet;
234    /// # use google_cloud_spanner::model::read_request::LockHint;
235    /// let request = ReadRequest::builder("Users", vec!["Id"])
236    ///     .with_keys(KeySet::all())
237    ///     .set_lock_hint(LockHint::Exclusive);
238    /// ```
239    ///
240    /// Lock hints can only be used with read-write transactions.
241    ///
242    /// By default, Spanner acquires shared read locks, which allows other reads to
243    /// still access the data until your transaction is ready to commit.
244    ///
245    /// Requesting exclusive locks (`LOCK_HINT_EXCLUSIVE`) is beneficial if you observe
246    /// high write contention. It prevents deadlocks by avoiding the situation where
247    /// multiple transactions initially acquire shared locks and then both try to upgrade
248    /// to exclusive locks at the same time.
249    ///
250    /// Request exclusive locks judiciously because they block others from reading that
251    /// data for the entire transaction, rather than just when the writes are being performed.
252    pub fn set_lock_hint(mut self, lock_hint: LockHint) -> Self {
253        self.lock_hint = Some(lock_hint);
254        self
255    }
256
257    /// Sets the per-attempt timeout for this read request.
258    pub fn with_attempt_timeout(mut self, timeout: Duration) -> Self {
259        self.gax_options.set_attempt_timeout(timeout);
260        self
261    }
262
263    /// Sets the retry policy for this read request.
264    pub fn with_retry_policy(mut self, policy: impl Into<RetryPolicyArg>) -> Self {
265        self.gax_options.set_retry_policy(policy);
266        self
267    }
268
269    /// Sets the backoff policy for this read request.
270    pub fn with_backoff_policy(mut self, policy: impl Into<BackoffPolicyArg>) -> Self {
271        self.gax_options.set_backoff_policy(policy);
272        self
273    }
274
275    /// Builds the configured `ReadRequest`.
276    pub fn build(self) -> ReadRequest {
277        ReadRequest {
278            table: self.table,
279            index: self.index,
280            keys: self.keys,
281            columns: self.columns,
282            limit: self.limit,
283            request_options: self.request_options,
284            directed_read_options: self.directed_read_options,
285            order_by: self.order_by,
286            lock_hint: self.lock_hint,
287            gax_options: self.gax_options,
288        }
289    }
290}
291
292/// Represents a configured read request ready for execution.
293///
294/// Contains the table, optional index, keys, and columns.
295/// Allows configuring optional parameters on the read operation, such as a limit.
296#[derive(Clone, Debug)]
297pub struct ReadRequest {
298    pub(crate) table: String,
299    pub(crate) index: Option<String>,
300    pub(crate) keys: KeySet,
301    pub(crate) columns: Vec<String>,
302    pub(crate) limit: Option<i64>,
303    pub(crate) request_options: Option<crate::model::RequestOptions>,
304    pub(crate) directed_read_options: Option<DirectedReadOptions>,
305    pub(crate) order_by: Option<OrderBy>,
306    pub(crate) lock_hint: Option<LockHint>,
307    pub(crate) gax_options: GaxRequestOptions,
308}
309
310impl ReadRequest {
311    /// Creates a new read operation builder for a specific table.
312    ///
313    /// # Example
314    /// ```
315    /// # use google_cloud_spanner::read::ReadRequest;
316    /// let builder = ReadRequest::builder("Users", vec!["Id", "Name"]);
317    /// ```
318    ///
319    /// The table name and columns to retrieve are required to initiate a read.
320    pub fn builder(
321        table: impl Into<String>,
322        columns: impl IntoIterator<Item = impl Into<String>>,
323    ) -> ReadRequestBuilder {
324        ReadRequestBuilder {
325            table: table.into(),
326            columns: columns.into_iter().map(|s| s.into()).collect(),
327        }
328    }
329
330    pub(crate) fn into_request(self) -> crate::model::ReadRequest {
331        crate::model::ReadRequest::default()
332            .set_table(self.table)
333            .set_columns(self.columns)
334            .set_key_set(self.keys.into_proto())
335            .set_index(self.index.unwrap_or_default())
336            .set_limit(self.limit.unwrap_or_default())
337            .set_or_clear_request_options(self.request_options)
338            .set_or_clear_directed_read_options(self.directed_read_options)
339            .set_order_by(self.order_by.unwrap_or_default())
340            .set_lock_hint(self.lock_hint.unwrap_or_default())
341    }
342
343    pub(crate) fn into_partition_read_request(self) -> crate::model::PartitionReadRequest {
344        crate::model::PartitionReadRequest::default()
345            .set_table(self.table)
346            .set_columns(self.columns)
347            .set_key_set(self.keys.into_proto())
348            .set_index(self.index.unwrap_or_default())
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn auto_traits() {
358        static_assertions::assert_impl_all!(ReadRequestBuilder: Send, Sync, Clone, std::fmt::Debug);
359        static_assertions::assert_impl_all!(ConfiguredReadRequestBuilder: Send, Sync, Clone, std::fmt::Debug);
360        static_assertions::assert_impl_all!(ReadRequest: Send, Sync, Clone, std::fmt::Debug);
361    }
362
363    #[test]
364    fn read_with_keys() {
365        let keys = KeySet::all();
366        let req = ReadRequest::builder("MyTable", vec!["col1", "col2"])
367            .with_keys(keys.clone())
368            .build();
369        assert_eq!(req.table, "MyTable");
370        assert_eq!(req.index, None);
371        assert_eq!(req.keys, keys);
372        assert_eq!(req.columns, vec!["col1", "col2"]);
373        assert_eq!(req.limit, None);
374    }
375
376    #[test]
377    fn read_with_index() {
378        let keys = KeySet::all();
379        let req = ReadRequest::builder("MyTable", vec!["col1", "col2"])
380            .with_index("MyIndex", keys.clone())
381            .build();
382        assert_eq!(req.table, "MyTable");
383        assert_eq!(req.index, Some("MyIndex".to_string()));
384        assert_eq!(req.keys, keys);
385        assert_eq!(req.columns, vec!["col1", "col2"]);
386        assert_eq!(req.limit, None);
387    }
388
389    #[test]
390    fn with_limit() {
391        let req = ReadRequest::builder("MyTable", vec!["col1"])
392            .with_keys(KeySet::all())
393            .set_limit(42)
394            .build();
395        assert_eq!(req.limit, Some(42));
396    }
397
398    #[test]
399    fn with_request_tag() {
400        let req = ReadRequest::builder("MyTable", vec!["col1"])
401            .with_keys(KeySet::all())
402            .set_request_tag("tag1")
403            .build();
404        assert_eq!(
405            req.request_options
406                .expect("request options missing")
407                .request_tag,
408            "tag1"
409        );
410    }
411
412    #[test]
413    fn with_priority() {
414        let req = ReadRequest::builder("MyTable", vec!["col1"])
415            .with_keys(KeySet::all())
416            .set_priority(Priority::High)
417            .build();
418        assert_eq!(
419            req.request_options
420                .expect("request options missing")
421                .priority,
422            Priority::High
423        );
424    }
425
426    #[test]
427    fn with_directed_read_options() {
428        let dro = DirectedReadOptions::default();
429        let req = ReadRequest::builder("MyTable", vec!["col1"])
430            .with_keys(KeySet::all())
431            .set_directed_read_options(dro.clone())
432            .build();
433        assert_eq!(req.directed_read_options, Some(dro));
434    }
435
436    #[test]
437    fn with_order_by() {
438        let req = ReadRequest::builder("MyTable", vec!["col1"])
439            .with_keys(KeySet::all())
440            .set_order_by(OrderBy::PrimaryKey)
441            .build();
442        assert_eq!(req.order_by, Some(OrderBy::PrimaryKey));
443    }
444
445    #[test]
446    fn with_lock_hint() {
447        let req = ReadRequest::builder("MyTable", vec!["col1"])
448            .with_keys(KeySet::all())
449            .set_lock_hint(LockHint::Exclusive)
450            .build();
451        assert_eq!(req.lock_hint, Some(LockHint::Exclusive));
452    }
453
454    #[test]
455    fn with_gax_options() -> anyhow::Result<()> {
456        use google_cloud_gax::exponential_backoff::ExponentialBackoff;
457        use google_cloud_gax::retry_policy::NeverRetry;
458        use std::time::Duration;
459
460        let req = ReadRequest::builder("MyTable", vec!["col1"])
461            .with_keys(KeySet::all())
462            .with_attempt_timeout(Duration::from_secs(10))
463            .with_retry_policy(NeverRetry)
464            .with_backoff_policy(ExponentialBackoff::default())
465            .build();
466
467        assert_eq!(
468            req.gax_options.attempt_timeout(),
469            &Some(Duration::from_secs(10))
470        );
471        assert!(req.gax_options.retry_policy().is_some());
472        assert!(req.gax_options.backoff_policy().is_some());
473
474        Ok(())
475    }
476}