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}