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}