oracle_nosql_rust_sdk/
get_request.rs

1//
2// Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
3//
4// Licensed under the Universal Permissive License v 1.0 as shown at
5//  https://oss.oracle.com/licenses/upl/
6//
7use crate::error::NoSQLError;
8use crate::error::NoSQLErrorCode::{IllegalArgument, ResourceNotFound};
9use crate::handle::Handle;
10use crate::handle::SendOptions;
11use crate::nson::*;
12use crate::reader::Reader;
13use crate::types::{Capacity, Consistency, MapValue, NoSQLRow, OpCode};
14use crate::writer::Writer;
15use crate::Version;
16use std::result::Result;
17use std::time::Duration;
18
19/// Struct used for getting a single row of data from a NoSQL table.
20#[derive(Default, Debug)]
21pub struct GetRequest {
22    pub(crate) table_name: String,
23    pub(crate) compartment_id: String,
24    pub(crate) timeout: Option<Duration>,
25    pub(crate) key: MapValue,
26    pub(crate) consistency: Consistency,
27    // TODO: limiters, retry stats, etc
28}
29
30/// Struct representing the result of a [`GetRequest`] operation.
31///
32/// This struct is returned from a [`GetRequest::execute()`] call.
33#[derive(Default, Debug)]
34pub struct GetResult {
35    pub(crate) row: Option<MapValue>,
36    pub(crate) consumed: Option<Capacity>,
37    pub(crate) modification_time: i64, // TODO: Time
38    pub(crate) expiration_time: i64,   // TODO: Time
39    pub(crate) version: Option<Version>,
40    // TODO: stats, rldelay, etc...
41}
42
43impl GetResult {
44    /// Get the returned row. If the row does not exist in the table, this value will be `None`.
45    pub fn row(&self) -> Option<&MapValue> {
46        if let Some(r) = &self.row {
47            return Some(r);
48        }
49        None
50    }
51    /// Get the consumed capacity (read/write units) of the operation. This is only valid in the NoSQL Cloud Service.
52    pub fn consumed(&self) -> Option<&Capacity> {
53        if let Some(c) = &self.consumed {
54            return Some(c);
55        }
56        None
57    }
58    /// Get the last modification time of the row. This is only valid if the operation succeeded.
59    /// Its value is the number of milliseconds since the epoch (Jan 1 1970).
60    pub fn modification_time(&self) -> i64 {
61        self.modification_time
62    }
63    /// Get the expiration time of the row. This is only valid if the operation succeeded.
64    /// Its value is the number of milliseconds since the epoch (Jan 1 1970).
65    pub fn expiration_time(&self) -> i64 {
66        self.expiration_time
67    }
68    /// Get the version of the row. This is only valid if the operation succeeded.
69    pub fn version(&self) -> Option<&Version> {
70        if let Some(v) = &self.version {
71            return Some(v);
72        }
73        None
74    }
75    // TODO: stats, rldelay, etc...
76}
77
78impl GetRequest {
79    /// Create a new `GetRequest`.
80    ///
81    /// `table_name` is required and must be non-empty.
82    pub fn new(table_name: &str) -> GetRequest {
83        GetRequest {
84            table_name: table_name.to_string(),
85            ..Default::default()
86        }
87    }
88
89    /// Specify the timeout value for the request.
90    ///
91    /// This is optional.
92    /// If set, it must be greater than or equal to 1 millisecond, otherwise an
93    /// IllegalArgument error will be returned.
94    /// If not set, the default timeout value configured for the [`Handle`](crate::HandleBuilder::timeout()) is used.
95    pub fn timeout(mut self, t: &Duration) -> Self {
96        self.timeout = Some(t.clone());
97        self
98    }
99
100    /// Cloud Service only: set the name or id of a compartment to be used for this operation.
101    ///
102    /// If the associated handle authenticated as an Instance Principal, this value must be an OCID.
103    /// In all other cases, the value may be specified as either a name (or path for nested compartments) or as an OCID.
104    ///
105    /// If no compartment is given, the default compartment id for the handle is used. If that value was
106    /// not specified, the root compartment of the tenancy will be used.
107    pub fn compartment_id(mut self, compartment_id: &str) -> Self {
108        self.compartment_id = compartment_id.to_string();
109        self
110    }
111
112    /// Specify the primary key to use to find the row (record) in the table, from a [`MapValue`].
113    ///
114    /// `key` must contain all fields required to construct the primary key for the table.
115    pub fn key(mut self, key: MapValue) -> GetRequest {
116        self.key = key;
117        self
118    }
119
120    /// Specify the primary key to use to find the row (record) in the table, from a native Rust struct.
121    ///
122    /// `row_key` must be an instance of a struct that implements the [`NoSQLRow`] trait, which is
123    /// done by adding the [`derive@NoSQLRow`] derive to the struct definition. `row_key` must contain
124    /// all fields required to construct the primary key for the table.
125    ///
126    /// See the [`GetRequest::execute_into()`] documentation below for an example of how to
127    /// add the `NoSQLRow` derive to a struct.
128    pub fn row_key(mut self, row_key: &dyn NoSQLRow) -> Result<GetRequest, NoSQLError> {
129        match row_key.to_map_value() {
130            Ok(value) => {
131                self = self.key(value);
132                return Ok(self);
133            }
134            Err(e) => {
135                // TODO: save error as source
136                return Err(NoSQLError::new(
137                    IllegalArgument,
138                    &format!("could not convert struct to MapValue: {}", e.to_string()),
139                ));
140            }
141        }
142    }
143
144    /// Specify the desired [`Consistency`] for the operation.
145    pub fn consistency(mut self, c: Consistency) -> GetRequest {
146        self.consistency = c;
147        self
148    }
149
150    /// Execute the request, returning a [`GetResult`].
151    ///
152    /// If the record exists in the table, [`GetResult::row`] will be `Some()`.
153    pub async fn execute(&self, h: &Handle) -> Result<GetResult, NoSQLError> {
154        let mut w: Writer = Writer::new();
155        w.write_i16(h.inner.serial_version);
156        let timeout = h.get_timeout(&self.timeout);
157        self.nson_serialize(&mut w, &timeout);
158        let mut opts = SendOptions {
159            timeout: timeout,
160            retryable: true,
161            compartment_id: self.compartment_id.clone(),
162            ..Default::default()
163        };
164        let mut r = h.send_and_receive(w, &mut opts).await?;
165        let resp = GetRequest::nson_deserialize(&mut r)?;
166        Ok(resp)
167    }
168
169    /// Execute the request, populating an existing Rust native struct.
170    ///
171    /// `row` must be an instance of a struct that implements the [`NoSQLRow`] trait, which is
172    /// done by adding the [`derive@NoSQLRow`] derive to the struct definition. `row` will have all
173    /// of its fields populated if this method returns `Ok()`:
174    /// ```no_run
175    /// use oracle_nosql_rust_sdk::GetRequest;
176    /// use oracle_nosql_rust_sdk::types::*;
177    /// # use oracle_nosql_rust_sdk::Handle;
178    /// # use oracle_nosql_rust_sdk::types::FieldValue;
179    /// # use std::error::Error;
180    /// # #[tokio::main]
181    /// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
182    /// # let handle = Handle::builder().build().await?;
183    /// // Assume a table was created with the following statement:
184    /// // "CREATE TABLE people (id long, name string, street string,
185    /// //                       city string, zip integer, primary_key(id))"
186    /// // A corresponding Rust struct could be:
187    /// #[derive(Default, Debug, NoSQLRow)]
188    /// struct Person {
189    ///     pub id: i64,
190    ///     pub name: String,
191    ///     pub street: String,
192    ///     pub city: String,
193    ///     pub zip: i32,
194    /// }
195    /// // To get a specific person from the table:
196    /// let mut person = Person {
197    ///     id: 123456,
198    ///     ..Default::default()
199    /// };
200    /// GetRequest::new("people")
201    ///     .row_key(&person)?
202    ///     .execute_into(&handle, &mut person).await?;
203    /// // person contains all fields
204    /// # Ok(())
205    /// # }
206    /// ```
207    ///
208    /// See the [`PutRequest::put()`](crate::PutRequest::put()) documentation for an additional example of how to
209    /// add the `NoSQLRow` derive to a struct.
210    ///
211    /// If it is desired to also get the row metadata (version, modification time, etc), use the the
212    /// [`GetRequest::execute()`] method instead, and then populate the native struct using
213    /// the [`NoSQLRow::from_map_value()`] method:
214    /// ```no_run
215    /// use oracle_nosql_rust_sdk::GetRequest;
216    /// use oracle_nosql_rust_sdk::types::*;
217    /// # use oracle_nosql_rust_sdk::Handle;
218    /// # use oracle_nosql_rust_sdk::types::FieldValue;
219    /// # use std::error::Error;
220    /// # #[tokio::main]
221    /// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
222    /// # let handle = Handle::builder().build().await?;
223    /// // Assume a table was created with the following statement:
224    /// // "CREATE TABLE people (id long, name string, street string,
225    /// //                       city string, zip integer, primary_key(id))"
226    /// // A corresponding Rust struct could be:
227    /// #[derive(Default, Debug, NoSQLRow)]
228    /// struct Person {
229    ///     pub id: i64,
230    ///     pub name: String,
231    ///     pub street: String,
232    ///     pub city: String,
233    ///     pub zip: i32,
234    /// }
235    /// // To get a specific person from the table:
236    /// let mut person = Person {
237    ///     id: 123456,
238    ///     ..Default::default()
239    /// };
240    /// let get_request = GetRequest::new("people").row_key(&person)?;
241    /// let resp = get_request.execute(&handle).await?;
242    /// if let Some(mv) = resp.row() {
243    ///     // resp metadata (modification time, version, etc.) valid
244    ///     let result = person.from_map_value(mv);
245    ///     if result.is_ok() {
246    ///         // person contains all fields
247    ///     } else {
248    ///         // There was some error deserializing the row MapValue to a Person struct
249    ///     }
250    /// }
251    /// # Ok(())
252    /// # }
253    /// ```
254    pub async fn execute_into(&self, h: &Handle, row: &mut dyn NoSQLRow) -> Result<(), NoSQLError> {
255        let resp = self.execute(h).await?;
256        if let Some(r) = resp.row() {
257            match row.from_map_value(r) {
258                Ok(_) => {
259                    return Ok(());
260                }
261                Err(e) => {
262                    // TODO: save error as source
263                    return Err(NoSQLError::new(
264                        IllegalArgument,
265                        &format!(
266                            "could not convert MapValue to native struct: {}",
267                            e.to_string()
268                        ),
269                    ));
270                }
271            }
272        }
273        Err(NoSQLError::new(ResourceNotFound, "NoSQL row not found"))
274    }
275
276    pub(crate) fn nson_serialize(&self, w: &mut Writer, timeout: &Duration) {
277        let mut ns = NsonSerializer::start_request(w);
278        ns.start_header();
279        ns.write_header(OpCode::Get, timeout, &self.table_name);
280        ns.end_header();
281
282        // payload
283        ns.start_payload();
284        ns.write_consistency(self.consistency);
285        ns.write_map_field(KEY, &self.key);
286        ns.end_payload();
287
288        ns.end_request();
289    }
290
291    pub(crate) fn nson_deserialize(r: &mut Reader) -> Result<GetResult, NoSQLError> {
292        let mut walker = MapWalker::new(r)?;
293        let mut res: GetResult = Default::default();
294        while walker.has_next() {
295            walker.next()?;
296            let name = walker.current_name();
297            match name.as_str() {
298                ERROR_CODE => {
299                    //println!("   get_result: ERROR_CODE");
300                    walker.handle_error_code()?;
301                }
302                CONSUMED => {
303                    //println!("   get_result: CONSUMED");
304                    res.consumed = Some(walker.read_nson_consumed_capacity()?);
305                    //println!(" consumed={:?}", res.consumed);
306                }
307                ROW => {
308                    //println!("   get_result: ROW");
309                    read_row(walker.r, &mut res)?;
310                    //for (f,v) in res.row.iter() {
311                    //println!("row: field={} value={:?}", f, v);
312                    //}
313                }
314                _ => {
315                    //println!("   get_result: skipping field '{}'", name);
316                    walker.skip_nson_field()?;
317                }
318            }
319        }
320        Ok(res)
321    }
322}
323
324fn read_row(r: &mut Reader, res: &mut GetResult) -> Result<(), NoSQLError> {
325    let mut walker = MapWalker::new(r)?;
326    while walker.has_next() {
327        walker.next()?;
328        let name = walker.current_name();
329        match name.as_str() {
330            MODIFIED => {
331                //println!("   read_row: MODIFIED");
332                res.modification_time = walker.read_nson_i64()?;
333            }
334            EXPIRATION => {
335                //println!("   read_row: EXPIRATION");
336                res.expiration_time = walker.read_nson_i64()?;
337            }
338            ROW_VERSION => {
339                //println!("   read_row: ROW_VERSION");
340                res.version = Some(walker.read_nson_binary()?);
341            }
342            VALUE => {
343                //println!("   read_row: VALUE");
344                res.row = Some(walker.read_nson_map()?);
345            }
346            _ => {
347                //println!("   read_row: skipping field '{}'", name);
348                walker.skip_nson_field()?;
349            }
350        }
351    }
352    Ok(())
353}
354
355impl NsonRequest for GetRequest {
356    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
357        self.nson_serialize(w, timeout);
358    }
359}