oracle_nosql_rust_sdk/
get_request.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
//
// Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
//
// Licensed under the Universal Permissive License v 1.0 as shown at
//  https://oss.oracle.com/licenses/upl/
//
use crate::error::NoSQLError;
use crate::error::NoSQLErrorCode::{IllegalArgument, ResourceNotFound};
use crate::handle::Handle;
use crate::handle::SendOptions;
use crate::nson::*;
use crate::reader::Reader;
use crate::types::{Capacity, Consistency, MapValue, NoSQLRow, OpCode};
use crate::writer::Writer;
use crate::Version;
use std::result::Result;
use std::time::Duration;

/// Struct used for getting a single row of data from a NoSQL table.
#[derive(Default, Debug)]
pub struct GetRequest {
    pub(crate) table_name: String,
    pub(crate) compartment_id: String,
    pub(crate) timeout: Option<Duration>,
    pub(crate) key: MapValue,
    pub(crate) consistency: Consistency,
    // TODO: limiters, retry stats, etc
}

/// Struct representing the result of a [`GetRequest`] operation.
///
/// This struct is returned from a [`GetRequest::execute()`] call.
#[derive(Default, Debug)]
pub struct GetResult {
    pub(crate) row: Option<MapValue>,
    pub(crate) consumed: Option<Capacity>,
    pub(crate) modification_time: i64, // TODO: Time
    pub(crate) expiration_time: i64,   // TODO: Time
    pub(crate) version: Option<Version>,
    // TODO: stats, rldelay, etc...
}

impl GetResult {
    /// Get the returned row. If the row does not exist in the table, this value will be `None`.
    pub fn row(&self) -> Option<&MapValue> {
        if let Some(r) = &self.row {
            return Some(r);
        }
        None
    }
    /// Get the consumed capacity (read/write units) of the operation. This is only valid in the NoSQL Cloud Service.
    pub fn consumed(&self) -> Option<&Capacity> {
        if let Some(c) = &self.consumed {
            return Some(c);
        }
        None
    }
    /// Get the last modification time of the row. This is only valid if the operation succeeded.
    /// Its value is the number of milliseconds since the epoch (Jan 1 1970).
    pub fn modification_time(&self) -> i64 {
        self.modification_time
    }
    /// Get the expiration time of the row. This is only valid if the operation succeeded.
    /// Its value is the number of milliseconds since the epoch (Jan 1 1970).
    pub fn expiration_time(&self) -> i64 {
        self.expiration_time
    }
    /// Get the version of the row. This is only valid if the operation succeeded.
    pub fn version(&self) -> Option<&Version> {
        if let Some(v) = &self.version {
            return Some(v);
        }
        None
    }
    // TODO: stats, rldelay, etc...
}

impl GetRequest {
    /// Create a new `GetRequest`.
    ///
    /// `table_name` is required and must be non-empty.
    pub fn new(table_name: &str) -> GetRequest {
        GetRequest {
            table_name: table_name.to_string(),
            ..Default::default()
        }
    }

    /// Specify the timeout value for the request.
    ///
    /// This is optional.
    /// If set, it must be greater than or equal to 1 millisecond, otherwise an
    /// IllegalArgument error will be returned.
    /// If not set, the default timeout value configured for the [`Handle`](crate::HandleBuilder::timeout()) is used.
    pub fn timeout(mut self, t: &Duration) -> Self {
        self.timeout = Some(t.clone());
        self
    }

    /// Cloud Service only: set the name or id of a compartment to be used for this operation.
    ///
    /// The compartment may be specified as either a name (or path for nested compartments) or as an id (OCID).
    /// A name (vs id) can only be used when authenticated using a specific user identity. It is not available if
    /// the associated handle authenticated as an Instance Principal (which can be done when calling the service from
    /// a compute instance in the Oracle Cloud Infrastructure: see [`HandleBuilder::cloud_auth_from_instance()`](crate::HandleBuilder::cloud_auth_from_instance()).)
    ///
    /// If no compartment is given, the root compartment of the tenancy will be used.
    pub fn compartment_id(mut self, compartment_id: &str) -> Self {
        self.compartment_id = compartment_id.to_string();
        self
    }

    /// Specify the primary key to use to find the row (record) in the table, from a [`MapValue`].
    ///
    /// `key` must contain all fields required to construct the primary key for the table.
    pub fn key(mut self, key: MapValue) -> GetRequest {
        self.key = key;
        self
    }

    /// Specify the primary key to use to find the row (record) in the table, from a native Rust struct.
    ///
    /// `row_key` must be an instance of a struct that implements the [`NoSQLRow`] trait, which is
    /// done by adding the [`derive@NoSQLRow`] derive to the struct definition. `row_key` must contain
    /// all fields required to construct the primary key for the table.
    ///
    /// See the [`GetRequest::execute_into()`] documentation below for an example of how to
    /// add the `NoSQLRow` derive to a struct.
    pub fn row_key(mut self, row_key: &dyn NoSQLRow) -> Result<GetRequest, NoSQLError> {
        match row_key.to_map_value() {
            Ok(value) => {
                self = self.key(value);
                return Ok(self);
            }
            Err(e) => {
                // TODO: save error as source
                return Err(NoSQLError::new(
                    IllegalArgument,
                    &format!("could not convert struct to MapValue: {}", e.to_string()),
                ));
            }
        }
    }

    /// Specify the desired [`Consistency`] for the operation.
    pub fn consistency(mut self, c: Consistency) -> GetRequest {
        self.consistency = c;
        self
    }

    /// Execute the request, returning a [`GetResult`].
    ///
    /// If the record exists in the table, [`GetResult::row`] will be `Some()`.
    pub async fn execute(&self, h: &Handle) -> Result<GetResult, NoSQLError> {
        let mut w: Writer = Writer::new();
        w.write_i16(h.inner.serial_version);
        let timeout = h.get_timeout(&self.timeout);
        self.nson_serialize(&mut w, &timeout);
        let mut opts = SendOptions {
            timeout: timeout,
            retryable: true,
            compartment_id: self.compartment_id.clone(),
            ..Default::default()
        };
        let mut r = h.send_and_receive(w, &mut opts).await?;
        let resp = GetRequest::nson_deserialize(&mut r)?;
        Ok(resp)
    }

    /// Execute the request, populating an existing Rust native struct.
    ///
    /// `row` must be an instance of a struct that implements the [`NoSQLRow`] trait, which is
    /// done by adding the [`derive@NoSQLRow`] derive to the struct definition. `row` will have all
    /// of its fields populated if this method returns `Ok()`:
    /// ```no_run
    /// use oracle_nosql_rust_sdk::GetRequest;
    /// use oracle_nosql_rust_sdk::types::*;
    /// # use oracle_nosql_rust_sdk::Handle;
    /// # use oracle_nosql_rust_sdk::types::FieldValue;
    /// # use std::error::Error;
    /// # #[tokio::main]
    /// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// # let handle = Handle::builder().build().await?;
    /// // Assume a table was created with the following statement:
    /// // "CREATE TABLE people (id long, name string, street string,
    /// //                       city string, zip integer, primary_key(id))"
    /// // A corresponding Rust struct could be:
    /// #[derive(Default, Debug, NoSQLRow)]
    /// struct Person {
    ///     pub id: i64,
    ///     pub name: String,
    ///     pub street: String,
    ///     pub city: String,
    ///     pub zip: i32,
    /// }
    /// // To get a specific person from the table:
    /// let mut person = Person {
    ///     id: 123456,
    ///     ..Default::default()
    /// };
    /// GetRequest::new("people")
    ///     .row_key(&person)?
    ///     .execute_into(&handle, &mut person).await?;
    /// // person contains all fields
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// See the [`PutRequest::put()`](crate::PutRequest::put()) documentation for an additional example of how to
    /// add the `NoSQLRow` derive to a struct.
    ///
    /// If it is desired to also get the row metadata (version, modification time, etc), use the the
    /// [`GetRequest::execute()`] method instead, and then populate the native struct using
    /// the [`NoSQLRow::from_map_value()`] method:
    /// ```no_run
    /// use oracle_nosql_rust_sdk::GetRequest;
    /// use oracle_nosql_rust_sdk::types::*;
    /// # use oracle_nosql_rust_sdk::Handle;
    /// # use oracle_nosql_rust_sdk::types::FieldValue;
    /// # use std::error::Error;
    /// # #[tokio::main]
    /// # pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// # let handle = Handle::builder().build().await?;
    /// // Assume a table was created with the following statement:
    /// // "CREATE TABLE people (id long, name string, street string,
    /// //                       city string, zip integer, primary_key(id))"
    /// // A corresponding Rust struct could be:
    /// #[derive(Default, Debug, NoSQLRow)]
    /// struct Person {
    ///     pub id: i64,
    ///     pub name: String,
    ///     pub street: String,
    ///     pub city: String,
    ///     pub zip: i32,
    /// }
    /// // To get a specific person from the table:
    /// let mut person = Person {
    ///     id: 123456,
    ///     ..Default::default()
    /// };
    /// let get_request = GetRequest::new("people").row_key(&person)?;
    /// let resp = get_request.execute(&handle).await?;
    /// if let Some(mv) = resp.row() {
    ///     // resp metadata (modification time, version, etc.) valid
    ///     let result = person.from_map_value(mv);
    ///     if result.is_ok() {
    ///         // person contains all fields
    ///     } else {
    ///         // There was some error deserializing the row MapValue to a Person struct
    ///     }
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn execute_into(&self, h: &Handle, row: &mut dyn NoSQLRow) -> Result<(), NoSQLError> {
        let resp = self.execute(h).await?;
        if let Some(r) = resp.row() {
            match row.from_map_value(r) {
                Ok(_) => {
                    return Ok(());
                }
                Err(e) => {
                    // TODO: save error as source
                    return Err(NoSQLError::new(
                        IllegalArgument,
                        &format!(
                            "could not convert MapValue to native struct: {}",
                            e.to_string()
                        ),
                    ));
                }
            }
        }
        Err(NoSQLError::new(ResourceNotFound, "NoSQL row not found"))
    }

    pub(crate) fn nson_serialize(&self, w: &mut Writer, timeout: &Duration) {
        let mut ns = NsonSerializer::start_request(w);
        ns.start_header();
        ns.write_header(OpCode::Get, timeout, &self.table_name);
        ns.end_header();

        // payload
        ns.start_payload();
        ns.write_consistency(self.consistency);
        ns.write_map_field(KEY, &self.key);
        ns.end_payload();

        ns.end_request();
    }

    pub(crate) fn nson_deserialize(r: &mut Reader) -> Result<GetResult, NoSQLError> {
        let mut walker = MapWalker::new(r)?;
        let mut res: GetResult = Default::default();
        while walker.has_next() {
            walker.next()?;
            let name = walker.current_name();
            match name.as_str() {
                ERROR_CODE => {
                    //println!("   get_result: ERROR_CODE");
                    walker.handle_error_code()?;
                }
                CONSUMED => {
                    //println!("   get_result: CONSUMED");
                    res.consumed = Some(walker.read_nson_consumed_capacity()?);
                    //println!(" consumed={:?}", res.consumed);
                }
                ROW => {
                    //println!("   get_result: ROW");
                    read_row(walker.r, &mut res)?;
                    //for (f,v) in res.row.iter() {
                    //println!("row: field={} value={:?}", f, v);
                    //}
                }
                _ => {
                    //println!("   get_result: skipping field '{}'", name);
                    walker.skip_nson_field()?;
                }
            }
        }
        Ok(res)
    }
}

fn read_row(r: &mut Reader, res: &mut GetResult) -> Result<(), NoSQLError> {
    let mut walker = MapWalker::new(r)?;
    while walker.has_next() {
        walker.next()?;
        let name = walker.current_name();
        match name.as_str() {
            MODIFIED => {
                //println!("   read_row: MODIFIED");
                res.modification_time = walker.read_nson_i64()?;
            }
            EXPIRATION => {
                //println!("   read_row: EXPIRATION");
                res.expiration_time = walker.read_nson_i64()?;
            }
            ROW_VERSION => {
                //println!("   read_row: ROW_VERSION");
                res.version = Some(walker.read_nson_binary()?);
            }
            VALUE => {
                //println!("   read_row: VALUE");
                res.row = Some(walker.read_nson_map()?);
            }
            _ => {
                //println!("   read_row: skipping field '{}'", name);
                walker.skip_nson_field()?;
            }
        }
    }
    Ok(())
}

impl NsonRequest for GetRequest {
    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
        self.nson_serialize(w, timeout);
    }
}