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    /// If the associated handle authenticated as an Instance Principal, this value must be an OCID.
130    /// In all other cases, the value may be specified as either a name (or path for nested compartments) or as an OCID.
131    ///
132    /// If no compartment is given, the default compartment id for the handle is used. If that value was
133    /// not specified, the root compartment of the tenancy will be used.
134    pub fn compartment_id(mut self, compartment_id: &str) -> Self {
135        self.compartment_id = compartment_id.to_string();
136        self
137    }
138
139    /// Succeed only if the record already exists its version matches the given version.
140    pub fn if_version(mut self, version: &Version) -> DeleteRequest {
141        self.match_version = version.clone();
142        self
143    }
144
145    /// Return information about the existing row. Requesting this information incurs
146    /// additional cost and may affect operation latency.
147    pub fn return_row(mut self, val: bool) -> DeleteRequest {
148        self.return_row = val;
149        self
150    }
151
152    pub fn set_abort_on_fail(mut self, val: bool) -> DeleteRequest {
153        self.abort_on_fail = val;
154        self
155    }
156
157    pub async fn execute(&self, h: &Handle) -> Result<DeleteResult, NoSQLError> {
158        let mut w: Writer = Writer::new();
159        w.write_i16(h.inner.serial_version);
160        let timeout = h.get_timeout(&self.timeout);
161        self.serialize_internal(&mut w, false, false, &timeout);
162        let mut opts = SendOptions {
163            timeout: timeout,
164            retryable: true,
165            compartment_id: self.compartment_id.clone(),
166            ..Default::default()
167        };
168        let mut r = h.send_and_receive(w, &mut opts).await?;
169        let resp = DeleteRequest::nson_deserialize(&mut r)?;
170        Ok(resp)
171    }
172
173    // TODO: when is add_table_name ever true??
174    fn serialize_internal(
175        &self,
176        w: &mut Writer,
177        is_sub_request: bool,
178        add_table_name: bool,
179        timeout: &Duration,
180    ) {
181        let mut ns = NsonSerializer::start_request(w);
182        let mut opcode = OpCode::Delete;
183        if self.match_version.len() > 0 {
184            opcode = OpCode::DeleteIfVersion;
185        }
186
187        if is_sub_request {
188            if add_table_name {
189                ns.write_string_field(TABLE_NAME, &self.table_name);
190            }
191            ns.write_i32_field(OP_CODE, opcode as i32);
192            if self.abort_on_fail {
193                ns.write_bool_field(ABORT_ON_FAIL, true);
194            }
195        } else {
196            ns.start_header();
197            ns.write_header(opcode, timeout, &self.table_name);
198            ns.end_header();
199            ns.start_payload();
200            // TODO: ns.write_durability(self.durability);
201        }
202
203        ns.write_true_bool_field(RETURN_ROW, true);
204        // TODO identity cache size
205
206        if self.match_version.len() > 0 {
207            ns.write_binary_field(ROW_VERSION, &self.match_version);
208        }
209
210        ns.write_map_field(KEY, &self.key);
211
212        // TODO others
213
214        if is_sub_request == false {
215            ns.end_payload();
216            ns.end_request();
217        }
218    }
219
220    pub(crate) fn nson_deserialize(r: &mut Reader) -> Result<DeleteResult, NoSQLError> {
221        let mut walker = MapWalker::new(r)?;
222        let mut res: DeleteResult = Default::default();
223        while walker.has_next() {
224            walker.next()?;
225            let name = walker.current_name();
226            match name.as_str() {
227                ERROR_CODE => {
228                    //println!("   w: ERROR_CODE");
229                    walker.handle_error_code()?;
230                }
231                CONSUMED => {
232                    //println!("   w: CONSUMED");
233                    res.consumed = Some(walker.read_nson_consumed_capacity()?);
234                    //println!(" consumed={:?}", res.consumed);
235                }
236                SUCCESS => {
237                    res.success = walker.read_nson_boolean()?;
238                    //println!(" success={:?}", res.success);
239                }
240                RETURN_INFO => {
241                    //println!("   w: RETURN_INFO");
242                    read_return_info(walker.r, &mut res)?;
243                }
244                _ => {
245                    //println!("   delete_result: skipping field '{}'", name);
246                    walker.skip_nson_field()?;
247                }
248            }
249        }
250        Ok(res)
251    }
252}
253
254// TODO: make this common to all write results
255fn read_return_info(r: &mut Reader, res: &mut DeleteResult) -> Result<(), NoSQLError> {
256    let mut walker = MapWalker::new(r)?;
257    while walker.has_next() {
258        walker.next()?;
259        let name = walker.current_name();
260        match name.as_str() {
261            EXISTING_MOD_TIME => {
262                //println!("   read_ri: EXISTING_MOD_TIME");
263                res.existing_modification_time = walker.read_nson_i64()?;
264            }
265            //EXISTING_EXPIRATION => {
266            //println!("   read_ri: EXISTING_EXPIRATION");
267            //res.existing_expiration_time = walker.read_nson_i64()?;
268            //},
269            EXISTING_VERSION => {
270                //println!("   read_ri: EXISTING_VERSION");
271                res.existing_version = Some(walker.read_nson_binary()?);
272            }
273            EXISTING_VALUE => {
274                //println!("   read_ri: EXISTING_VALUE");
275                res.existing_value = Some(walker.read_nson_map()?);
276            }
277            _ => {
278                //println!( "   delete_result read_return_info: skipping field '{}'", name);
279                walker.skip_nson_field()?;
280            }
281        }
282    }
283    Ok(())
284}
285
286impl NsonRequest for DeleteRequest {
287    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
288        self.serialize_internal(w, false, false, timeout);
289    }
290}
291
292impl NsonSubRequest for DeleteRequest {
293    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
294        self.serialize_internal(w, true, false, timeout);
295    }
296}