oracle_nosql_rust_sdk/
multi_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, FieldValue, OpCode};
13use crate::writer::Writer;
14use std::result::Result;
15use std::time::Duration;
16
17/// A range of values to be used in a [`MultiDeleteRequest`] operation.
18///
19/// `FieldRange` is used as the least significant component in a partially
20/// specified key value in order to create a value range for an operation that
21/// returns multiple rows or keys. The data types supported by `FieldRange` are
22/// limited to the atomic types which are valid for primary keys.
23///
24/// The least significant component of a key is the first component of the key
25/// that is not fully specified. For example, if the primary key for a table is
26/// defined as the tuple:
27///
28///   <a, b, c>
29///
30/// A `FieldRange` can be specified for:
31///
32///   "a" if the primary key supplied is empty.
33///   "b" if the primary key supplied to the operation has a concrete value for "a" but not for "b" or "c".
34///
35/// The `field_path` specified must name a field in a table's primary key.
36/// The `start` and `end` values used must be of the same type and that type must
37/// match the type of the field specified.
38///
39/// Validation of this struct is performed when it is used in an operation.
40/// Validation includes verifying that the field is in the required key and,
41/// in the case of a composite key, that the field is in the proper order
42/// relative to the key used in the operation.
43#[derive(Default, Debug)]
44pub struct FieldRange {
45    // field_path specifies the path to the field used in the range.
46    pub field_path: String,
47
48    // start specifies the start value of the range.
49    pub start: Option<FieldValue>,
50
51    // start_inclusive specifies whether start value is included in the range,
52    // i.e., start value is less than or equal to the first FieldValue in the range.
53    //
54    // This value is valid only if the start value is specified.
55    pub start_inclusive: bool,
56
57    // end specifies the end value of the range.
58    pub end: Option<FieldValue>,
59
60    // end_inclusive specifies whether end value is included in the range,
61    // i.e., end value is greater than or equal to the last FieldValue in the range.
62    //
63    // This value is valid only if the end value is specified.
64    pub end_inclusive: bool,
65}
66
67/// Struct used for deleting a range of rows from a NoSQL table.
68#[derive(Default, Debug)]
69pub struct MultiDeleteRequest {
70    pub(crate) table_name: String,
71    pub(crate) compartment_id: String,
72    pub(crate) key: FieldValue,
73    pub(crate) continuation_key: Option<Vec<u8>>,
74    pub(crate) field_range: Option<FieldRange>,
75    pub(crate) max_write_kb: i32,
76    pub(crate) timeout: Option<Duration>,
77    // Durability is currently only used in On-Prem installations.
78    // Added in SDK Version 1.3.0
79    // TODO durability: Option<Something>
80
81    // namespace is used on-premises only. It defines a namespace to use
82    // for the request. It is optional.
83    // If a namespace is specified in the table name for the request
84    // (using the namespace:tablename format), that value will override this
85    // setting.
86    // This is only available with on-premises installations using NoSQL
87    // Server versions 23.3 and above.
88    // TODO: pub namespace: String,
89
90    // TODO: limiters, retry stats, etc
91}
92
93/// Struct representing the result of a [`MultiDeleteRequest`] operation.
94#[derive(Default, Debug)]
95pub struct MultiDeleteResult {
96    pub(crate) num_deleted: i32,
97    pub(crate) continuation_key: Option<Vec<u8>>,
98    pub(crate) consumed: Option<Capacity>,
99}
100
101impl MultiDeleteResult {
102    /// Get the number of records deleted by the operation.
103    pub fn num_deleted(&self) -> i32 {
104        self.num_deleted
105    }
106    /// Get a continuation key that can be used in a subsequent MultiDelete operation.
107    /// This typically will be set when a `max_write_kb` is specified and there are more
108    /// records to delete.
109    pub fn continuation_key(&self) -> Option<Vec<u8>> {
110        if let Some(ck) = &self.continuation_key {
111            return Some(ck.clone());
112        }
113        None
114    }
115    /// Get the consumed capacity (read/write units) of the operation. This is only valid in the NoSQL Cloud Service.
116    pub fn consumed(&self) -> Option<&Capacity> {
117        if let Some(c) = &self.consumed {
118            return Some(c);
119        }
120        None
121    }
122}
123
124impl MultiDeleteRequest {
125    /// Create a new `MultiDeleteRequest`.
126    ///
127    /// `table_name` specifies the name of table for the request.
128    /// It is required and must be non-empty.
129    ///
130    /// `partial_key` specifies the partial key used for the request.
131    /// It is required and must be non-nil.
132    pub fn new(table_name: &str, partial_key: &FieldValue) -> MultiDeleteRequest {
133        MultiDeleteRequest {
134            table_name: table_name.to_string(),
135            key: partial_key.clone_internal(),
136            ..Default::default()
137        }
138    }
139
140    /// Specify the timeout value for the request.
141    ///
142    /// This is optional.
143    /// If set, it must be greater than or equal to 1 millisecond, otherwise an
144    /// IllegalArgument error will be returned.
145    /// If not set, the default timeout value configured for the [`Handle`](crate::HandleBuilder::timeout()) is used.
146    pub fn timeout(mut self, t: &Duration) -> MultiDeleteRequest {
147        self.timeout = Some(t.clone());
148        self
149    }
150
151    /// Cloud Service only: set the name or id of a compartment to be used for this operation.
152    ///
153    /// If the associated handle authenticated as an Instance Principal, this value must be an OCID.
154    /// In all other cases, the value may be specified as either a name (or path for nested compartments) or as an OCID.
155    ///
156    /// If no compartment is given, the default compartment id for the handle is used. If that value was
157    /// not specified, the root compartment of the tenancy will be used.
158    pub fn compartment_id(mut self, compartment_id: &str) -> MultiDeleteRequest {
159        self.compartment_id = compartment_id.to_string();
160        self
161    }
162
163    /// Specifiy the [`FieldRange`] to be used for the operation.
164    ///
165    /// It is optional, but required to delete a specific range of rows.
166    pub fn field_range(mut self, field_range: FieldRange) -> MultiDeleteRequest {
167        self.field_range = Some(field_range);
168        self
169    }
170
171    /// Specify the continuation key to use to continue the operation.
172    ///
173    /// This is typically populated from a previous [`MultiDeleteResult::continuation_key()`].
174    pub fn continuation_key(mut self, key: Vec<u8>) -> MultiDeleteRequest {
175        self.continuation_key = Some(key);
176        self
177    }
178
179    /// Specify the limit on the total KB write during this operation.
180    ///
181    /// This is optional and has no effect for on-premise.
182    /// When used for the cloud service, if this value is not set, or set to 0, there
183    /// is no application-defined limit.
184    ///
185    ///  This value can only reduce the system defined limit. An attempt to increase the
186    /// limit beyond the system defined limit will cause an IllegalArgument error.
187    pub fn max_write_kb(mut self, max_write_kb: i32) -> MultiDeleteRequest {
188        self.max_write_kb = max_write_kb;
189        self
190    }
191
192    pub async fn execute(&self, h: &Handle) -> Result<MultiDeleteResult, NoSQLError> {
193        // TODO: validate: size > 0, etc
194        let mut w: Writer = Writer::new();
195        w.write_i16(h.inner.serial_version);
196        let timeout = h.get_timeout(&self.timeout);
197        self.serialize_internal(&mut w, &timeout);
198        // TODO: namespace in http header?
199        let mut opts = SendOptions {
200            timeout: timeout,
201            retryable: false,
202            compartment_id: self.compartment_id.clone(),
203            ..Default::default()
204        };
205        let mut r = h.send_and_receive(w, &mut opts).await?;
206        let resp = MultiDeleteRequest::nson_deserialize(&mut r)?;
207        Ok(resp)
208    }
209
210    fn serialize_internal(&self, w: &mut Writer, timeout: &Duration) {
211        let mut ns = NsonSerializer::start_request(w);
212        ns.start_header();
213        ns.write_header(OpCode::MultiDelete, timeout, &self.table_name);
214        ns.end_header();
215
216        ns.start_payload();
217        //ns.write_i32_field(DURABILITY, 0); // TODO
218
219        ns.write_i32_field(MAX_WRITE_KB, self.max_write_kb);
220
221        ns.write_field(KEY, &self.key);
222
223        if let Some(ckey) = &self.continuation_key {
224            ns.write_binary_field(CONTINUATION_KEY, ckey);
225        }
226
227        if let Some(range) = &self.field_range {
228            ns.start_map(RANGE);
229            ns.write_string_field(RANGE_PATH, &range.field_path);
230            if let Some(start) = &range.start {
231                ns.write_field(VALUE, start);
232                ns.write_bool_field(INCLUSIVE, range.start_inclusive);
233            }
234            if let Some(end) = &range.end {
235                ns.write_field(VALUE, end);
236                ns.write_bool_field(INCLUSIVE, range.end_inclusive);
237            }
238            ns.end_map(RANGE);
239        }
240
241        ns.end_payload();
242        ns.end_request();
243    }
244
245    pub(crate) fn nson_deserialize(r: &mut Reader) -> Result<MultiDeleteResult, NoSQLError> {
246        let mut walker = MapWalker::new(r)?;
247        let mut res: MultiDeleteResult = Default::default();
248        while walker.has_next() {
249            walker.next()?;
250            let name = walker.current_name();
251            match name.as_str() {
252                ERROR_CODE => {
253                    walker.handle_error_code()?;
254                }
255                CONSUMED => {
256                    res.consumed = Some(walker.read_nson_consumed_capacity()?);
257                    //println!(" consumed={:?}", res.consumed);
258                }
259                NUM_DELETIONS => {
260                    res.num_deleted = walker.read_nson_i32()?;
261                    //println!(" num_deleted={:?}", res.num_deleted);
262                }
263                CONTINUATION_KEY => {
264                    res.continuation_key = Some(walker.read_nson_binary()?);
265                    //println!(" continuation_key={:?}", res.continuation_key);
266                }
267                _ => {
268                    //println!("   multi_delete_result: skipping field '{}'", name);
269                    walker.skip_nson_field()?;
270                }
271            }
272        }
273        Ok(res)
274    }
275}
276
277impl NsonRequest for MultiDeleteRequest {
278    fn serialize(&self, w: &mut Writer, timeout: &Duration) {
279        self.serialize_internal(w, timeout);
280    }
281}