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    /// The compartment may be specified as either a name (or path for nested compartments) or as an id (OCID).
103    /// A name (vs id) can only be used when authenticated using a specific user identity. It is not available if
104    /// the associated handle authenticated as an Instance Principal (which can be done when calling the service from
105    /// a compute instance in the Oracle Cloud Infrastructure: see [`HandleBuilder::cloud_auth_from_instance()`](crate::HandleBuilder::cloud_auth_from_instance()).)
106    ///
107    /// If no compartment is given, the root compartment of the tenancy will be used.
108    pub fn compartment_id(mut self, compartment_id: &str) -> Self {
109        self.compartment_id = compartment_id.to_string();
110        self
111    }
112
113    /// Specify the primary key to use to find the row (record) in the table, from a [`MapValue`].
114    ///
115    /// `key` must contain all fields required to construct the primary key for the table.
116    pub fn key(mut self, key: MapValue) -> GetRequest {
117        self.key = key;
118        self
119    }
120
121    /// Specify the primary key to use to find the row (record) in the table, from a native Rust struct.
122    ///
123    /// `row_key` must be an instance of a struct that implements the [`NoSQLRow`] trait, which is
124    /// done by adding the [`derive@NoSQLRow`] derive to the struct definition. `row_key` must contain
125    /// all fields required to construct the primary key for the table.
126    ///
127    /// See the [`GetRequest::execute_into()`] documentation below for an example of how to
128    /// add the `NoSQLRow` derive to a struct.
129    pub fn row_key(mut self, row_key: &dyn NoSQLRow) -> Result<GetRequest, NoSQLError> {
130        match row_key.to_map_value() {
131            Ok(value) => {
132                self = self.key(value);
133                return Ok(self);
134            }
135            Err(e) => {
136                // TODO: save error as source
137                return Err(NoSQLError::new(
138                    IllegalArgument,
139                    &format!("could not convert struct to MapValue: {}", e.to_string()),
140                ));
141            }
142        }
143    }
144
145    /// Specify the desired [`Consistency`] for the operation.
146    pub fn consistency(mut self, c: Consistency) -> GetRequest {
147        self.consistency = c;
148        self
149    }
150
151    /// Execute the request, returning a [`GetResult`].
152    ///
153    /// If the record exists in the table, [`GetResult::row`] will be `Some()`.
154    pub async fn execute(&self, h: &Handle) -> Result<GetResult, NoSQLError> {
155        let mut w: Writer = Writer::new();
156        w.write_i16(h.inner.serial_version);
157        let timeout = h.get_timeout(&self.timeout);
158        self.nson_serialize(&mut w, &timeout);
159        let mut opts = SendOptions {
160            timeout: timeout,
161            retryable: true,
162            compartment_id: self.compartment_id.clone(),
163            ..Default::default()
164        };
165        let mut r = h.send_and_receive(w, &mut opts).await?;
166        let resp = GetRequest::nson_deserialize(&mut r)?;
167        Ok(resp)
168    }
169
170    /// Execute the request, populating an existing Rust native struct.
171    ///
172    /// `row` must be an instance of a struct that implements the [`NoSQLRow`] trait, which is
173    /// done by adding the [`derive@NoSQLRow`] derive to the struct definition. `row` will have all
174    /// of its fields populated if this method returns `Ok()`:
175    /// ```no_run
176    /// use oracle_nosql_rust_sdk::GetRequest;
177    /// use oracle_nosql_rust_sdk::types::*;
178    /// # use oracle_nosql_rust_sdk::Handle;
179    /// # use oracle_nosql_rust_sdk::types::FieldValue;
180    /// # use std::error::Error;
181    /// # #[tokio::main]
182    /// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
183    /// # let handle = Handle::builder().build().await?;
184    /// // Assume a table was created with the following statement:
185    /// // "CREATE TABLE people (id long, name string, street string,
186    /// //                       city string, zip integer, primary_key(id))"
187    /// // A corresponding Rust struct could be:
188    /// #[derive(Default, Debug, NoSQLRow)]
189    /// struct Person {
190    ///     pub id: i64,
191    ///     pub name: String,
192    ///     pub street: String,
193    ///     pub city: String,
194    ///     pub zip: i32,
195    /// }
196    /// // To get a specific person from the table:
197    /// let mut person = Person {
198    ///     id: 123456,
199    ///     ..Default::default()
200    /// };
201    /// GetRequest::new("people")
202    ///     .row_key(&person)?
203    ///     .execute_into(&handle, &mut person).await?;
204    /// // person contains all fields
205    /// # Ok(())
206    /// # }
207    /// ```
208    ///
209    /// See the [`PutRequest::put()`](crate::PutRequest::put()) documentation for an additional example of how to
210    /// add the `NoSQLRow` derive to a struct.
211    ///
212    /// If it is desired to also get the row metadata (version, modification time, etc), use the the
213    /// [`GetRequest::execute()`] method instead, and then populate the native struct using
214    /// the [`NoSQLRow::from_map_value()`] method:
215    /// ```no_run
216    /// use oracle_nosql_rust_sdk::GetRequest;
217    /// use oracle_nosql_rust_sdk::types::*;
218    /// # use oracle_nosql_rust_sdk::Handle;
219    /// # use oracle_nosql_rust_sdk::types::FieldValue;
220    /// # use std::error::Error;
221    /// # #[tokio::main]
222    /// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
223    /// # let handle = Handle::builder().build().await?;
224    /// // Assume a table was created with the following statement:
225    /// // "CREATE TABLE people (id long, name string, street string,
226    /// //                       city string, zip integer, primary_key(id))"
227    /// // A corresponding Rust struct could be:
228    /// #[derive(Default, Debug, NoSQLRow)]
229    /// struct Person {
230    ///     pub id: i64,
231    ///     pub name: String,
232    ///     pub street: String,
233    ///     pub city: String,
234    ///     pub zip: i32,
235    /// }
236    /// // To get a specific person from the table:
237    /// let mut person = Person {
238    ///     id: 123456,
239    ///     ..Default::default()
240    /// };
241    /// let get_request = GetRequest::new("people").row_key(&person)?;
242    /// let resp = get_request.execute(&handle).await?;
243    /// if let Some(mv) = resp.row() {
244    ///     // resp metadata (modification time, version, etc.) valid
245    ///     let result = person.from_map_value(mv);
246    ///     if result.is_ok() {
247    ///         // person contains all fields
248    ///     } else {
249    ///         // There was some error deserializing the row MapValue to a Person struct
250    ///     }
251    /// }
252    /// # Ok(())
253    /// # }
254    /// ```
255    pub async fn execute_into(&self, h: &Handle, row: &mut dyn NoSQLRow) -> Result<(), NoSQLError> {
256        let resp = self.execute(h).await?;
257        if let Some(r) = resp.row() {
258            match row.from_map_value(r) {
259                Ok(_) => {
260                    return Ok(());
261                }
262                Err(e) => {
263                    // TODO: save error as source
264                    return Err(NoSQLError::new(
265                        IllegalArgument,
266                        &format!(
267                            "could not convert MapValue to native struct: {}",
268                            e.to_string()
269                        ),
270                    ));
271                }
272            }
273        }
274        Err(NoSQLError::new(ResourceNotFound, "NoSQL row not found"))
275    }
276
277    pub(crate) fn nson_serialize(&self, w: &mut Writer, timeout: &Duration) {
278        let mut ns = NsonSerializer::start_request(w);
279        ns.start_header();
280        ns.write_header(OpCode::Get, timeout, &self.table_name);
281        ns.end_header();
282
283        // payload
284        ns.start_payload();
285        ns.write_consistency(self.consistency);
286        ns.write_map_field(KEY, &self.key);
287        ns.end_payload();
288
289        ns.end_request();
290    }
291
292    pub(crate) fn nson_deserialize(r: &mut Reader) -> Result<GetResult, NoSQLError> {
293        let mut walker = MapWalker::new(r)?;
294        let mut res: GetResult = Default::default();
295        while walker.has_next() {
296            walker.next()?;
297            let name = walker.current_name();
298            match name.as_str() {
299                ERROR_CODE => {
300                    //println!("   get_result: ERROR_CODE");
301                    walker.handle_error_code()?;
302                }
303                CONSUMED => {
304                    //println!("   get_result: CONSUMED");
305                    res.consumed = Some(walker.read_nson_consumed_capacity()?);
306                    //println!(" consumed={:?}", res.consumed);
307                }
308                ROW => {
309                    //println!("   get_result: ROW");
310                    read_row(walker.r, &mut res)?;
311                    //for (f,v) in res.row.iter() {
312                    //println!("row: field={} value={:?}", f, v);
313                    //}
314                }
315                _ => {
316                    //println!("   get_result: skipping field '{}'", name);
317                    walker.skip_nson_field()?;
318                }
319            }
320        }
321        Ok(res)
322    }
323}
324
325fn read_row(r: &mut Reader, res: &mut GetResult) -> Result<(), NoSQLError> {
326    let mut walker = MapWalker::new(r)?;
327    while walker.has_next() {
328        walker.next()?;
329        let name = walker.current_name();
330        match name.as_str() {
331            MODIFIED => {
332                //println!("   read_row: MODIFIED");
333                res.modification_time = walker.read_nson_i64()?;
334            }
335            EXPIRATION => {
336                //println!("   read_row: EXPIRATION");
337                res.expiration_time = walker.read_nson_i64()?;
338            }
339            ROW_VERSION => {
340                //println!("   read_row: ROW_VERSION");
341                res.version = Some(walker.read_nson_binary()?);
342            }
343            VALUE => {
344                //println!("   read_row: VALUE");
345                res.row = Some(walker.read_nson_map()?);
346            }
347            _ => {
348                //println!("   read_row: skipping field '{}'", name);
349                walker.skip_nson_field()?;
350            }
351        }
352    }
353    Ok(())
354}
355
356impl NsonRequest for GetRequest {
357    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
358        self.nson_serialize(w, timeout);
359    }
360}