Skip to main content

google_cloud_spanner/
timestamp_bound.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::model::transaction_options::read_only::TimestampBound as ReadOnlyTimestampBound;
16use std::fmt::Debug;
17
18/// Use a timestamp bound to specify how to choose a timestamp at which a query should read data.
19///
20/// # Example
21/// ```
22/// # use google_cloud_spanner::client::Spanner;
23/// # use google_cloud_spanner::transaction::TimestampBound;
24/// # async fn test_doc() -> Result<(), google_cloud_spanner::Error> {
25/// let client = Spanner::builder().build().await.unwrap();
26/// let db = client.database_client("projects/p/instances/i/databases/d").build().await.unwrap();
27///
28/// let tx = db.single_use().set_timestamp_bound(TimestampBound::strong()).build();
29/// # Ok(())
30/// # }
31/// ```
32///
33/// See <https://cloud.google.com/spanner/docs/timestamp-bounds> for more information.
34#[derive(Clone, Debug)]
35pub struct TimestampBound(pub(crate) ReadOnlyTimestampBound);
36
37impl TimestampBound {
38    /// Returns a strong timestamp bound. Strong reads are guaranteed to see the
39    /// effects of all transactions that have committed before the start of the read.
40    ///
41    /// See [timestamp_bound_strong] for more information.
42    ///
43    /// [timestamp_bound_strong]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#strong
44    pub fn strong() -> Self {
45        Self(ReadOnlyTimestampBound::Strong(true))
46    }
47
48    /// Returns a timestamp bound for an exact timestamp. The data will be read as it was at the given timestamp.
49    ///
50    /// See [timestamp_bound_exact_staleness] for more information.
51    ///
52    /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness
53    pub fn read_timestamp<T>(timestamp: T) -> Self
54    where
55        T: TryInto<wkt::Timestamp>,
56        T::Error: Debug,
57    {
58        Self::try_read_timestamp(timestamp).expect("timestamp out of range")
59    }
60
61    /// Returns a timestamp bound for an exact timestamp, returning an error if the timestamp is out of range.
62    ///
63    /// See [timestamp_bound_exact_staleness] for more information.
64    ///
65    /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness
66    pub fn try_read_timestamp<T>(timestamp: T) -> Result<Self, T::Error>
67    where
68        T: TryInto<wkt::Timestamp>,
69    {
70        let timestamp = timestamp.try_into()?;
71        Ok(Self(ReadOnlyTimestampBound::ReadTimestamp(Box::new(
72            timestamp,
73        ))))
74    }
75
76    /// Returns a timestamp bound for a minimum read timestamp. The data will be read as it was at the
77    /// given timestamp or later.
78    ///
79    /// See [timestamp_bound_bounded_staleness] for more information.
80    ///
81    /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness
82    pub fn min_read_timestamp<T>(timestamp: T) -> Self
83    where
84        T: TryInto<wkt::Timestamp>,
85        T::Error: Debug,
86    {
87        Self::try_min_read_timestamp(timestamp).expect("timestamp out of range")
88    }
89
90    /// Returns a timestamp bound for a minimum read timestamp, returning an error if the timestamp is out of range.
91    ///
92    /// See [timestamp_bound_bounded_staleness] for more information.
93    ///
94    /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness
95    pub fn try_min_read_timestamp<T>(timestamp: T) -> Result<Self, T::Error>
96    where
97        T: TryInto<wkt::Timestamp>,
98    {
99        let timestamp = timestamp.try_into()?;
100        Ok(Self(ReadOnlyTimestampBound::MinReadTimestamp(Box::new(
101            timestamp,
102        ))))
103    }
104
105    /// Returns a timestamp bound for an exact staleness. The data will be read as it was at the given timestamp
106    /// calculated by the current server time minus the given duration.
107    ///
108    /// See [timestamp_bound_exact_staleness] for more information.
109    ///
110    /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness
111    pub fn exact_staleness<T>(duration: T) -> Self
112    where
113        T: TryInto<wkt::Duration>,
114        T::Error: Debug,
115    {
116        Self::try_exact_staleness(duration).expect("duration out of range")
117    }
118
119    /// Returns a timestamp bound for an exact staleness, returning an error if the duration is out of range.
120    ///
121    /// See [timestamp_bound_exact_staleness] for more information.
122    ///
123    /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness
124    pub fn try_exact_staleness<T>(duration: T) -> Result<Self, T::Error>
125    where
126        T: TryInto<wkt::Duration>,
127    {
128        let duration = duration.try_into()?;
129        Ok(Self(ReadOnlyTimestampBound::ExactStaleness(Box::new(
130            duration,
131        ))))
132    }
133
134    /// Returns a timestamp bound for a maximum staleness. The data will be read as it was at the
135    /// current server time minus the given duration or later.
136    ///
137    /// See [timestamp_bound_bounded_staleness] for more information.
138    ///
139    /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness
140    pub fn max_staleness<T>(duration: T) -> Self
141    where
142        T: TryInto<wkt::Duration>,
143        T::Error: Debug,
144    {
145        Self::try_max_staleness(duration).expect("duration out of range")
146    }
147
148    /// Returns a timestamp bound for a maximum staleness, returning an error if the duration is out of range.
149    ///
150    /// See [timestamp_bound_bounded_staleness] for more information.
151    ///
152    /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness
153    pub fn try_max_staleness<T>(duration: T) -> Result<Self, T::Error>
154    where
155        T: TryInto<wkt::Duration>,
156    {
157        let duration = duration.try_into()?;
158        Ok(Self(ReadOnlyTimestampBound::MaxStaleness(Box::new(
159            duration,
160        ))))
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use std::time::Duration;
168    use time::macros::datetime;
169
170    #[test]
171    fn test_auto_traits() {
172        static_assertions::assert_impl_all!(TimestampBound: Clone, Debug, Send, Sync);
173    }
174
175    #[test]
176    fn test_strong() {
177        let bound = TimestampBound::strong();
178        assert!(matches!(bound.0, ReadOnlyTimestampBound::Strong(true)));
179    }
180
181    #[test]
182    fn test_read_timestamp_methods() {
183        let ts = datetime!(2026-03-09 18:00:00 UTC);
184
185        // 1. OffsetDateTime
186        let read = TimestampBound::read_timestamp(ts);
187        assert!(matches!(
188            read.0,
189            ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
190        ));
191
192        let try_read = TimestampBound::try_read_timestamp(ts).expect("valid OffsetDateTime");
193        assert!(matches!(
194            try_read.0,
195            ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
196        ));
197
198        // 2. wkt::Timestamp
199        let wkt_ts = wkt::Timestamp::try_from(ts).expect("valid wkt timestamp");
200        let read = TimestampBound::read_timestamp(wkt_ts);
201        assert!(matches!(
202            read.0,
203            ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
204        ));
205
206        let try_read = TimestampBound::try_read_timestamp(wkt_ts).expect("valid wkt timestamp");
207        assert!(matches!(
208            try_read.0,
209            ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
210        ));
211
212        // 3. SystemTime
213        let system_time = std::time::SystemTime::from(ts);
214        let read = TimestampBound::read_timestamp(system_time);
215        assert!(matches!(
216            read.0,
217            ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
218        ));
219
220        let try_read = TimestampBound::try_read_timestamp(system_time).expect("valid SystemTime");
221        assert!(matches!(
222            try_read.0,
223            ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
224        ));
225    }
226
227    #[test]
228    fn test_min_read_timestamp_methods() {
229        let ts = datetime!(2026-03-09 18:00:00 UTC);
230
231        // 1. OffsetDateTime
232        let min_read = TimestampBound::min_read_timestamp(ts);
233        assert!(matches!(
234            min_read.0,
235            ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
236        ));
237
238        let try_min_read =
239            TimestampBound::try_min_read_timestamp(ts).expect("valid OffsetDateTime");
240        assert!(matches!(
241            try_min_read.0,
242            ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
243        ));
244
245        // 2. wkt::Timestamp
246        let wkt_ts = wkt::Timestamp::try_from(ts).expect("valid wkt timestamp");
247        let min_read = TimestampBound::min_read_timestamp(wkt_ts);
248        assert!(matches!(
249            min_read.0,
250            ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
251        ));
252
253        let try_min_read =
254            TimestampBound::try_min_read_timestamp(wkt_ts).expect("valid wkt timestamp");
255        assert!(matches!(
256            try_min_read.0,
257            ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
258        ));
259
260        // 3. SystemTime
261        let system_time = std::time::SystemTime::from(ts);
262        let min_read = TimestampBound::min_read_timestamp(system_time);
263        assert!(matches!(
264            min_read.0,
265            ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
266        ));
267
268        let try_min_read =
269            TimestampBound::try_min_read_timestamp(system_time).expect("valid SystemTime");
270        assert!(matches!(
271            try_min_read.0,
272            ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32
273        ));
274    }
275
276    #[test]
277    fn test_exact_staleness_methods() {
278        let d = Duration::from_secs(60);
279
280        // 1. std::time::Duration
281        let exact = TimestampBound::exact_staleness(d);
282        assert!(matches!(
283            exact.0,
284            ReadOnlyTimestampBound::ExactStaleness(ref t) if t.seconds() == 60 && t.nanos() == 0
285        ));
286
287        let try_exact = TimestampBound::try_exact_staleness(d).expect("valid std::time::Duration");
288        assert!(matches!(
289            try_exact.0,
290            ReadOnlyTimestampBound::ExactStaleness(ref t) if t.seconds() == 60 && t.nanos() == 0
291        ));
292
293        // 2. wkt::Duration
294        let wkt_d = wkt::Duration::try_from(d).expect("valid wkt duration");
295        let exact = TimestampBound::exact_staleness(wkt_d);
296        assert!(matches!(
297            exact.0,
298            ReadOnlyTimestampBound::ExactStaleness(ref t) if t.seconds() == 60 && t.nanos() == 0
299        ));
300
301        let try_exact = TimestampBound::try_exact_staleness(wkt_d).expect("valid wkt::Duration");
302        assert!(matches!(
303            try_exact.0,
304            ReadOnlyTimestampBound::ExactStaleness(ref t) if t.seconds() == 60 && t.nanos() == 0
305        ));
306    }
307
308    #[test]
309    fn test_max_staleness_methods() {
310        let d = Duration::from_secs(120);
311
312        // 1. std::time::Duration
313        let max = TimestampBound::max_staleness(d);
314        assert!(matches!(
315            max.0,
316            ReadOnlyTimestampBound::MaxStaleness(ref t) if t.seconds() == 120 && t.nanos() == 0
317        ));
318
319        let try_max = TimestampBound::try_max_staleness(d).expect("valid std::time::Duration");
320        assert!(matches!(
321            try_max.0,
322            ReadOnlyTimestampBound::MaxStaleness(ref t) if t.seconds() == 120 && t.nanos() == 0
323        ));
324
325        // 2. wkt::Duration
326        let wkt_d = wkt::Duration::try_from(d).expect("valid wkt duration");
327        let max = TimestampBound::max_staleness(wkt_d);
328        assert!(matches!(
329            max.0,
330            ReadOnlyTimestampBound::MaxStaleness(ref t) if t.seconds() == 120 && t.nanos() == 0
331        ));
332
333        let try_max = TimestampBound::try_max_staleness(wkt_d).expect("valid wkt::Duration");
334        assert!(matches!(
335            try_max.0,
336            ReadOnlyTimestampBound::MaxStaleness(ref t) if t.seconds() == 120 && t.nanos() == 0
337        ));
338    }
339}