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}