oracle_nosql_rust_sdk/
delete_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::handle::Handle;
9use crate::handle::SendOptions;
10use crate::nson::*;
11use crate::reader::Reader;
12use crate::types::{Capacity, MapValue, OpCode};
13use crate::writer::Writer;
14use crate::Version;
15use std::result::Result;
16use std::time::Duration;
17
18/// Struct used for deleting a single row from a table in the NoSQL Database.
19///
20/// This request can be used to perform unconditional and conditional deletes:
21///
22/// - Delete any existing row. This is the default.
23/// - Succeed only if the row exists and its Version matches a specific Version. Use
24///   [`if_version()`](DeleteRequest::if_version()) for this case.
25///
26/// Information about the existing row can be returned from a delete operation using
27/// [`return_row(true)`](DeleteRequest::return_row()). Requesting this information incurs
28/// additional cost and may affect operation latency.
29#[derive(Default, Debug)]
30pub struct DeleteRequest {
31    pub(crate) key: MapValue,
32    pub(crate) table_name: String,
33    pub(crate) timeout: Option<Duration>,
34    pub(crate) compartment_id: String,
35    pub(crate) abort_on_fail: bool,
36    pub(crate) return_row: bool,
37    // TODO: durability
38    match_version: Version,
39}
40
41/// Struct representing the result of a [`DeleteRequest`] execution.
42///
43/// This struct is returned from a [`DeleteRequest::execute()`] call.
44#[derive(Default, Debug)]
45pub struct DeleteResult {
46    pub(crate) success: bool,
47    pub(crate) consumed: Option<Capacity>,
48    pub(crate) existing_modification_time: i64,
49    pub(crate) existing_value: Option<MapValue>,
50    pub(crate) existing_version: Option<Version>,
51    // TODO: stats, etc... (base)
52}
53
54impl DeleteResult {
55    /// Get the result of the operation: `true` if the row was deleted from the table.
56    pub fn success(&self) -> bool {
57        self.success
58    }
59    /// Get the consumed capacity (read/write units) of the operation. This is only valid in the NoSQL Cloud Service.
60    pub fn consumed(&self) -> Option<&Capacity> {
61        if let Some(c) = &self.consumed {
62            return Some(c);
63        }
64        None
65    }
66
67    /// Get the modification time of the deleted row if the delete operation succeeded, or the modification time of the
68    /// current row if the operation failed due to a `if_version()` mismatch.
69    ///
70    /// In either case, this is only valid if [`return_row(true)`] was called on
71    /// the [`DeleteRequest`] and a previous row existed.
72    /// Its value is the number of milliseconds since the epoch (Jan 1 1970).
73    // TODO: make this a Time field
74    pub fn existing_modification_time(&self) -> i64 {
75        self.existing_modification_time
76    }
77    /// Get the value of the deleted row if the delete operation succeeded, or the value of the
78    /// current row if the operation failed due to a `if_version()` mismatch.
79    ///
80    /// In either case, this is only valid if [`return_row(true)`] was called on
81    /// the [`DeleteRequest`] and a previous row existed.
82    pub fn existing_value(&self) -> Option<&MapValue> {
83        if let Some(v) = &self.existing_value {
84            return Some(v);
85        }
86        None
87    }
88    /// Get the Version of the deleted row if the delete operation succeeded, or the Version of the
89    /// current row if the operation failed due to a `if_version()` mismatch.
90    ///
91    /// In either case, this is only valid if [`return_row(true)`] was called on
92    /// called on the [`DeleteRequest`] and a previous row existed.
93    pub fn existing_version(&self) -> Option<&Version> {
94        if let Some(v) = &self.existing_version {
95            return Some(v);
96        }
97        None
98    }
99    // TODO: stats, etc... (base)
100}
101
102impl DeleteRequest {
103    /// Create a new `DeleteRequest`.
104    ///
105    /// `table_name` and `key` are required and must be non-empty.
106    ///
107    /// `key` must contain all fields required to construct the primary key for the table.
108    pub fn new(table_name: &str, key: MapValue) -> DeleteRequest {
109        DeleteRequest {
110            table_name: table_name.to_string(),
111            key: key,
112            ..Default::default()
113        }
114    }
115
116    /// Specify the timeout value for the request.
117    ///
118    /// This is optional.
119    /// If set, it must be greater than or equal to 1 millisecond, otherwise an
120    /// IllegalArgument error will be returned.
121    /// If not set, the default timeout value configured for the [`Handle`](crate::HandleBuilder::timeout()) is used.
122    pub fn timeout(mut self, t: &Duration) -> Self {
123        self.timeout = Some(t.clone());
124        self
125    }
126
127    /// Cloud Service only: set the name or id of a compartment to be used for this operation.
128    ///
129    /// The compartment may be specified as either a name (or path for nested compartments) or as an id (OCID).
130    /// A name (vs id) can only be used when authenticated using a specific user identity. It is not available if
131    /// the associated handle authenticated as an Instance Principal (which can be done when calling the service from
132    /// a compute instance in the Oracle Cloud Infrastructure: see [`HandleBuilder::cloud_auth_from_instance()`](crate::HandleBuilder::cloud_auth_from_instance()).)
133    ///
134    /// If no compartment is given, the root compartment of the tenancy will be used.
135    pub fn compartment_id(mut self, compartment_id: &str) -> Self {
136        self.compartment_id = compartment_id.to_string();
137        self
138    }
139
140    /// Succeed only if the record already exists its version matches the given version.
141    pub fn if_version(mut self, version: &Version) -> DeleteRequest {
142        self.match_version = version.clone();
143        self
144    }
145
146    /// Return information about the existing row. Requesting this information incurs
147    /// additional cost and may affect operation latency.
148    pub fn return_row(mut self, val: bool) -> DeleteRequest {
149        self.return_row = val;
150        self
151    }
152
153    pub fn set_abort_on_fail(mut self, val: bool) -> DeleteRequest {
154        self.abort_on_fail = val;
155        self
156    }
157
158    pub async fn execute(&self, h: &Handle) -> Result<DeleteResult, NoSQLError> {
159        let mut w: Writer = Writer::new();
160        w.write_i16(h.inner.serial_version);
161        let timeout = h.get_timeout(&self.timeout);
162        self.serialize_internal(&mut w, false, false, &timeout);
163        let mut opts = SendOptions {
164            timeout: timeout,
165            retryable: true,
166            compartment_id: self.compartment_id.clone(),
167            ..Default::default()
168        };
169        let mut r = h.send_and_receive(w, &mut opts).await?;
170        let resp = DeleteRequest::nson_deserialize(&mut r)?;
171        Ok(resp)
172    }
173
174    // TODO: when is add_table_name ever true??
175    fn serialize_internal(
176        &self,
177        w: &mut Writer,
178        is_sub_request: bool,
179        add_table_name: bool,
180        timeout: &Duration,
181    ) {
182        let mut ns = NsonSerializer::start_request(w);
183        let mut opcode = OpCode::Delete;
184        if self.match_version.len() > 0 {
185            opcode = OpCode::DeleteIfVersion;
186        }
187
188        if is_sub_request {
189            if add_table_name {
190                ns.write_string_field(TABLE_NAME, &self.table_name);
191            }
192            ns.write_i32_field(OP_CODE, opcode as i32);
193            if self.abort_on_fail {
194                ns.write_bool_field(ABORT_ON_FAIL, true);
195            }
196        } else {
197            ns.start_header();
198            ns.write_header(opcode, timeout, &self.table_name);
199            ns.end_header();
200            ns.start_payload();
201            // TODO: ns.write_durability(self.durability);
202        }
203
204        ns.write_true_bool_field(RETURN_ROW, true);
205        // TODO identity cache size
206
207        if self.match_version.len() > 0 {
208            ns.write_binary_field(ROW_VERSION, &self.match_version);
209        }
210
211        ns.write_map_field(KEY, &self.key);
212
213        // TODO others
214
215        if is_sub_request == false {
216            ns.end_payload();
217            ns.end_request();
218        }
219    }
220
221    pub(crate) fn nson_deserialize(r: &mut Reader) -> Result<DeleteResult, NoSQLError> {
222        let mut walker = MapWalker::new(r)?;
223        let mut res: DeleteResult = Default::default();
224        while walker.has_next() {
225            walker.next()?;
226            let name = walker.current_name();
227            match name.as_str() {
228                ERROR_CODE => {
229                    //println!("   w: ERROR_CODE");
230                    walker.handle_error_code()?;
231                }
232                CONSUMED => {
233                    //println!("   w: CONSUMED");
234                    res.consumed = Some(walker.read_nson_consumed_capacity()?);
235                    //println!(" consumed={:?}", res.consumed);
236                }
237                SUCCESS => {
238                    res.success = walker.read_nson_boolean()?;
239                    //println!(" success={:?}", res.success);
240                }
241                RETURN_INFO => {
242                    //println!("   w: RETURN_INFO");
243                    read_return_info(walker.r, &mut res)?;
244                }
245                _ => {
246                    //println!("   delete_result: skipping field '{}'", name);
247                    walker.skip_nson_field()?;
248                }
249            }
250        }
251        Ok(res)
252    }
253}
254
255// TODO: make this common to all write results
256fn read_return_info(r: &mut Reader, res: &mut DeleteResult) -> Result<(), NoSQLError> {
257    let mut walker = MapWalker::new(r)?;
258    while walker.has_next() {
259        walker.next()?;
260        let name = walker.current_name();
261        match name.as_str() {
262            EXISTING_MOD_TIME => {
263                //println!("   read_ri: EXISTING_MOD_TIME");
264                res.existing_modification_time = walker.read_nson_i64()?;
265            }
266            //EXISTING_EXPIRATION => {
267            //println!("   read_ri: EXISTING_EXPIRATION");
268            //res.existing_expiration_time = walker.read_nson_i64()?;
269            //},
270            EXISTING_VERSION => {
271                //println!("   read_ri: EXISTING_VERSION");
272                res.existing_version = Some(walker.read_nson_binary()?);
273            }
274            EXISTING_VALUE => {
275                //println!("   read_ri: EXISTING_VALUE");
276                res.existing_value = Some(walker.read_nson_map()?);
277            }
278            _ => {
279                //println!( "   delete_result read_return_info: skipping field '{}'", name);
280                walker.skip_nson_field()?;
281            }
282        }
283    }
284    Ok(())
285}
286
287impl NsonRequest for DeleteRequest {
288    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
289        self.serialize_internal(w, false, false, timeout);
290    }
291}
292
293impl NsonSubRequest for DeleteRequest {
294    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
295        self.serialize_internal(w, true, false, timeout);
296    }
297}