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}