/home/gh-runner/action-runner3/_work/feoxdb/feoxdb/src/core/store/operations.rs
Line | Count | Source |
1 | | use bytes::Bytes; |
2 | | use std::sync::atomic::Ordering; |
3 | | use std::sync::Arc; |
4 | | use std::time::{SystemTime, UNIX_EPOCH}; |
5 | | |
6 | | use crate::constants::*; |
7 | | use crate::core::record::Record; |
8 | | use crate::error::{FeoxError, Result}; |
9 | | |
10 | | use super::FeoxStore; |
11 | | |
12 | | impl FeoxStore { |
13 | | /// Insert or update a key-value pair. |
14 | | /// |
15 | | /// If the key already exists with a TTL, the TTL is removed (key becomes permanent). |
16 | | /// To preserve or set TTL, use `insert_with_ttl()` instead. |
17 | | /// |
18 | | /// # Arguments |
19 | | /// |
20 | | /// * `key` - The key to insert |
21 | | /// * `value` - The value to store |
22 | | /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time. |
23 | | /// |
24 | | /// # Returns |
25 | | /// |
26 | | /// Returns `Ok(true)` if a new key was inserted, `Ok(false)` if an existing key was updated. |
27 | | /// |
28 | | /// # Errors |
29 | | /// |
30 | | /// * `InvalidKey` - Key is empty or too large |
31 | | /// * `InvalidValue` - Value is too large |
32 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
33 | | /// * `OutOfMemory` - Memory limit exceeded |
34 | | /// |
35 | | /// # Example |
36 | | /// |
37 | | /// ```rust |
38 | | /// # use feoxdb::FeoxStore; |
39 | | /// # fn main() -> feoxdb::Result<()> { |
40 | | /// # let store = FeoxStore::new(None)?; |
41 | | /// store.insert(b"user:123", b"{\"name\":\"Mehran\"}")?; |
42 | | /// # Ok(()) |
43 | | /// # } |
44 | | /// ``` |
45 | | /// |
46 | | /// # Performance |
47 | | /// |
48 | | /// * Memory mode: ~600ns |
49 | | /// * Persistent mode: ~800ns (buffered write) |
50 | 2.07k | pub fn insert(&self, key: &[u8], value: &[u8]) -> Result<bool> { |
51 | 2.07k | self.insert_with_timestamp(key, value, None) |
52 | 2.07k | } |
53 | | |
54 | | /// Insert or update a key-value pair with explicit timestamp. |
55 | | /// |
56 | | /// This is the advanced version that allows manual timestamp control for |
57 | | /// conflict resolution. Most users should use `insert()` instead. |
58 | | /// |
59 | | /// # Arguments |
60 | | /// |
61 | | /// * `key` - The key to insert |
62 | | /// * `value` - The value to store |
63 | | /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time. |
64 | | /// |
65 | | /// # Errors |
66 | | /// |
67 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
68 | 2.07k | pub fn insert_with_timestamp( |
69 | 2.07k | &self, |
70 | 2.07k | key: &[u8], |
71 | 2.07k | value: &[u8], |
72 | 2.07k | timestamp: Option<u64>, |
73 | 2.07k | ) -> Result<bool> { |
74 | 2.07k | self.insert_with_timestamp_and_ttl_internal(key, value, timestamp, 0) |
75 | 2.07k | } |
76 | | |
77 | | /// Insert or update a key-value pair using zero-copy Bytes. |
78 | | /// |
79 | | /// This method avoids copying the value data by directly using the Bytes type, |
80 | | /// which provides reference-counted zero-copy semantics. Useful when inserting |
81 | | /// data that was already read from network or disk as Bytes. |
82 | | /// |
83 | | /// If the key already exists with a TTL, the TTL is removed (key becomes permanent). |
84 | | /// To preserve or set TTL, use `insert_bytes_with_ttl()` instead. |
85 | | /// |
86 | | /// # Arguments |
87 | | /// |
88 | | /// * `key` - The key to insert |
89 | | /// * `value` - The value to store as Bytes |
90 | | /// |
91 | | /// # Returns |
92 | | /// |
93 | | /// Returns `Ok(true)` if a new key was inserted, `Ok(false)` if an existing key was updated. |
94 | | /// |
95 | | /// # Errors |
96 | | /// |
97 | | /// * `InvalidKey` - Key is empty or too large |
98 | | /// * `InvalidValue` - Value is too large |
99 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
100 | | /// * `OutOfMemory` - Memory limit exceeded |
101 | | /// |
102 | | /// # Example |
103 | | /// |
104 | | /// ```rust |
105 | | /// # use feoxdb::FeoxStore; |
106 | | /// # use bytes::Bytes; |
107 | | /// # fn main() -> feoxdb::Result<()> { |
108 | | /// # let store = FeoxStore::new(None)?; |
109 | | /// let data = Bytes::from_static(b"{\"name\":\"Mehran\"}"); |
110 | | /// store.insert_bytes(b"user:123", data)?; |
111 | | /// # Ok(()) |
112 | | /// # } |
113 | | /// ``` |
114 | | /// |
115 | | /// # Performance |
116 | | /// |
117 | | /// * Memory mode: ~600ns (avoids value copy) |
118 | | /// * Persistent mode: ~800ns (buffered write, avoids value copy) |
119 | 7 | pub fn insert_bytes(&self, key: &[u8], value: Bytes) -> Result<bool> { |
120 | 7 | self.insert_bytes_with_timestamp(key, value, None) |
121 | 7 | } |
122 | | |
123 | | /// Insert or update a key-value pair using zero-copy Bytes with explicit timestamp. |
124 | | /// |
125 | | /// This is the advanced version that allows manual timestamp control for |
126 | | /// conflict resolution. Most users should use `insert_bytes()` instead. |
127 | | /// |
128 | | /// # Arguments |
129 | | /// |
130 | | /// * `key` - The key to insert |
131 | | /// * `value` - The value to store as Bytes |
132 | | /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time. |
133 | | /// |
134 | | /// # Errors |
135 | | /// |
136 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
137 | 10 | pub fn insert_bytes_with_timestamp( |
138 | 10 | &self, |
139 | 10 | key: &[u8], |
140 | 10 | value: Bytes, |
141 | 10 | timestamp: Option<u64>, |
142 | 10 | ) -> Result<bool> { |
143 | 10 | self.insert_bytes_with_timestamp_and_ttl_internal(key, value, timestamp, 0) |
144 | 10 | } |
145 | | |
146 | 2.08k | pub(super) fn insert_with_timestamp_and_ttl_internal( |
147 | 2.08k | &self, |
148 | 2.08k | key: &[u8], |
149 | 2.08k | value: &[u8], |
150 | 2.08k | timestamp: Option<u64>, |
151 | 2.08k | ttl_expiry: u64, |
152 | 2.08k | ) -> Result<bool> { |
153 | 2.08k | let start = std::time::Instant::now(); |
154 | 2.08k | let timestamp = match timestamp { |
155 | 2.07k | Some(0) | None => self.get_timestamp(), |
156 | 13 | Some(ts) => ts, |
157 | | }; |
158 | 2.08k | self.validate_key_value(key, value)?3 ; |
159 | | |
160 | | // Check for existing record |
161 | 2.08k | let is_update = self.hash_table.contains(key); |
162 | 2.08k | let existing_record = self.hash_table.read(key, |_, v| v500 .clone500 ()); |
163 | 2.08k | if let Some(existing_record500 ) = existing_record { |
164 | 500 | let existing_ts = existing_record.timestamp; |
165 | 500 | let existing_clone = existing_record; |
166 | | |
167 | 500 | if timestamp < existing_ts { |
168 | 1 | return Err(FeoxError::OlderTimestamp); |
169 | 499 | } |
170 | | |
171 | | // Update existing record |
172 | 499 | return self.update_record_with_ttl(&existing_clone, value, timestamp, ttl_expiry); |
173 | 1.58k | } |
174 | | |
175 | 1.58k | let record_size = self.calculate_record_size(key.len(), value.len()); |
176 | 1.58k | if !self.check_memory_limit(record_size) { |
177 | 1 | return Err(FeoxError::OutOfMemory); |
178 | 1.58k | } |
179 | | |
180 | | // Create new record with TTL if specified and TTL is enabled |
181 | 1.58k | let record = if ttl_expiry > 0 && self.enable_ttl6 { |
182 | 6 | self.stats.keys_with_ttl.fetch_add(1, Ordering::Relaxed); |
183 | 6 | Arc::new(Record::new_with_timestamp_ttl( |
184 | 6 | key.to_vec(), |
185 | 6 | value.to_vec(), |
186 | 6 | timestamp, |
187 | 6 | ttl_expiry, |
188 | | )) |
189 | | } else { |
190 | 1.57k | Arc::new(Record::new(key.to_vec(), value.to_vec(), timestamp)) |
191 | | }; |
192 | | |
193 | 1.58k | let key_vec = record.key.clone(); |
194 | | |
195 | | // Insert into hash table |
196 | 1.58k | self.hash_table.upsert(key_vec.clone(), Arc::clone(&record)); |
197 | | |
198 | | // Insert into lock-free skip list for ordered access |
199 | 1.58k | self.tree.insert(key_vec, Arc::clone(&record)); |
200 | | |
201 | | // Update statistics |
202 | 1.58k | self.stats.record_count.fetch_add(1, Ordering::AcqRel); |
203 | 1.58k | self.stats |
204 | 1.58k | .memory_usage |
205 | 1.58k | .fetch_add(record_size, Ordering::AcqRel); |
206 | 1.58k | self.stats |
207 | 1.58k | .record_insert(start.elapsed().as_nanos() as u64, is_update); |
208 | | |
209 | | // Only do persistence if not in memory-only mode |
210 | 1.58k | if !self.memory_only { |
211 | | // Queue for persistence if write buffer exists |
212 | 303 | if let Some(ref wb) = self.write_buffer { |
213 | 303 | if let Err(_e0 ) = wb.add_write(Operation::Insert, record, 0) { |
214 | 0 | // Don't fail the insert - data is still in memory |
215 | 0 | // Return code already indicates success since data is in memory |
216 | 303 | } |
217 | 0 | } |
218 | 1.27k | } |
219 | | |
220 | 1.58k | Ok(!is_update) |
221 | 2.08k | } |
222 | | |
223 | | /// Internal method to insert a Bytes value with timestamp and TTL (zero-copy) |
224 | 15 | pub(super) fn insert_bytes_with_timestamp_and_ttl_internal( |
225 | 15 | &self, |
226 | 15 | key: &[u8], |
227 | 15 | value: Bytes, |
228 | 15 | timestamp: Option<u64>, |
229 | 15 | ttl_seconds: u64, |
230 | 15 | ) -> Result<bool> { |
231 | 15 | let start = std::time::Instant::now(); |
232 | | // Get timestamp before any operations |
233 | 15 | let timestamp = match timestamp { |
234 | 9 | Some(0) | None => self.get_timestamp(), |
235 | 6 | Some(ts) => ts, |
236 | | }; |
237 | | |
238 | 15 | self.validate_key(key)?0 ; |
239 | 15 | let value_len = value.len(); |
240 | 15 | if value_len == 0 || value_len > MAX_VALUE_SIZE14 { |
241 | 1 | return Err(FeoxError::InvalidValueSize); |
242 | 14 | } |
243 | | |
244 | | // Check for existing record |
245 | 14 | let is_update = self.hash_table.contains(key); |
246 | 14 | let existing_record = self.hash_table.read(key, |_, v| v6 .clone6 ()); |
247 | 14 | if let Some(existing_record6 ) = existing_record { |
248 | 6 | let existing_ts = existing_record.timestamp; |
249 | | |
250 | 6 | if timestamp < existing_ts { |
251 | 2 | return Err(FeoxError::OlderTimestamp); |
252 | 4 | } |
253 | | |
254 | | // Calculate TTL expiry |
255 | 4 | let ttl_expiry = if ttl_seconds > 0 && self.enable_ttl1 { |
256 | 1 | timestamp + (ttl_seconds * 1_000_000_000) |
257 | | } else { |
258 | 3 | 0 |
259 | | }; |
260 | | |
261 | | // Update existing record using the Bytes version |
262 | 4 | return self.update_record_with_ttl_bytes( |
263 | 4 | &existing_record, |
264 | 4 | value, |
265 | 4 | timestamp, |
266 | 4 | ttl_expiry, |
267 | | ); |
268 | 8 | } |
269 | | |
270 | | // This point is only reached for new inserts (not updates) |
271 | 8 | let new_size = self.calculate_record_size(key.len(), value_len); |
272 | 8 | if !self.check_memory_limit(new_size) { |
273 | 0 | return Err(FeoxError::OutOfMemory); |
274 | 8 | } |
275 | | |
276 | | // Create new record with Bytes value |
277 | 8 | let record = if ttl_seconds > 0 && self.enable_ttl3 { |
278 | 3 | let ttl_expiry = timestamp + (ttl_seconds * 1_000_000_000); |
279 | 3 | self.stats.keys_with_ttl.fetch_add(1, Ordering::Relaxed); |
280 | 3 | Arc::new(Record::new_from_bytes_with_ttl( |
281 | 3 | key.to_vec(), |
282 | 3 | value, |
283 | 3 | timestamp, |
284 | 3 | ttl_expiry, |
285 | | )) |
286 | | } else { |
287 | 5 | Arc::new(Record::new_from_bytes(key.to_vec(), value, timestamp)) |
288 | | }; |
289 | | |
290 | 8 | let key_vec = record.key.clone(); |
291 | | |
292 | | // Insert into hash table |
293 | 8 | self.hash_table.upsert(key_vec.clone(), Arc::clone(&record)); |
294 | | |
295 | | // Insert into skip list for ordered access |
296 | 8 | self.tree.insert(key_vec, Arc::clone(&record)); |
297 | | |
298 | | // Update statistics |
299 | 8 | self.stats.record_count.fetch_add(1, Ordering::AcqRel); |
300 | 8 | self.stats |
301 | 8 | .memory_usage |
302 | 8 | .fetch_add(new_size, Ordering::AcqRel); |
303 | 8 | self.stats |
304 | 8 | .record_insert(start.elapsed().as_nanos() as u64, is_update); |
305 | | |
306 | | // Only do persistence if not in memory-only mode |
307 | 8 | if !self.memory_only { |
308 | | // Queue for persistence if write buffer exists |
309 | 0 | if let Some(ref wb) = self.write_buffer { |
310 | 0 | if let Err(_e) = wb.add_write(Operation::Insert, record, 0) { |
311 | 0 | // Don't fail the insert - data is still in memory |
312 | 0 | } |
313 | 0 | } |
314 | 8 | } |
315 | | |
316 | 8 | Ok(!is_update) |
317 | 15 | } |
318 | | |
319 | | /// Retrieve a value by key. |
320 | | /// |
321 | | /// # Arguments |
322 | | /// |
323 | | /// * `key` - The key to look up |
324 | | /// * `expected_size` - Optional expected value size for validation |
325 | | /// |
326 | | /// # Returns |
327 | | /// |
328 | | /// Returns the value as a `Vec<u8>` if found. |
329 | | /// |
330 | | /// # Errors |
331 | | /// |
332 | | /// * `KeyNotFound` - Key does not exist |
333 | | /// * `InvalidKey` - Key is invalid |
334 | | /// * `SizeMismatch` - Value size doesn't match expected size |
335 | | /// * `IoError` - Failed to read from disk (persistent mode) |
336 | | /// |
337 | | /// # Example |
338 | | /// |
339 | | /// ```rust |
340 | | /// # use feoxdb::FeoxStore; |
341 | | /// # fn main() -> feoxdb::Result<()> { |
342 | | /// # let store = FeoxStore::new(None)?; |
343 | | /// # store.insert(b"key", b"value")?; |
344 | | /// let value = store.get(b"key")?; |
345 | | /// assert_eq!(value, b"value"); |
346 | | /// # Ok(()) |
347 | | /// # } |
348 | | /// ``` |
349 | | /// |
350 | | /// # Performance |
351 | | /// |
352 | | /// * Memory mode: ~100ns |
353 | | /// * Persistent mode (cached): ~150ns |
354 | | /// * Persistent mode (disk read): ~500ns |
355 | 3.61k | pub fn get(&self, key: &[u8]) -> Result<Vec<u8>> { |
356 | 3.61k | let start = std::time::Instant::now(); |
357 | 3.61k | self.validate_key(key)?0 ; |
358 | | |
359 | 3.61k | let mut cache_hit = false; |
360 | 3.61k | if self.enable_caching { |
361 | 4 | if let Some(ref cache) = self.cache { |
362 | 4 | if let Some(value0 ) = cache.get(key) { |
363 | 0 | self.stats |
364 | 0 | .record_get(start.elapsed().as_nanos() as u64, true); |
365 | 0 | return Ok(value.to_vec()); |
366 | 4 | } |
367 | 0 | } |
368 | 3.60k | } |
369 | | |
370 | 3.61k | let record3.58k = self |
371 | 3.61k | .hash_table |
372 | 3.61k | .read(key, |_, v| v3.58k .clone3.58k ()) |
373 | 3.61k | .ok_or(FeoxError::KeyNotFound)?31 ; |
374 | | |
375 | | // Check TTL expiry if TTL is enabled |
376 | 3.58k | if self.enable_ttl { |
377 | 11 | let ttl_expiry = record.ttl_expiry.load(Ordering::Relaxed); |
378 | 11 | if ttl_expiry > 0 { |
379 | 8 | let now = SystemTime::now() |
380 | 8 | .duration_since(UNIX_EPOCH) |
381 | 8 | .unwrap_or_default() |
382 | 8 | .as_nanos() as u64; |
383 | 8 | if now > ttl_expiry { |
384 | 3 | self.stats.ttl_expired_lazy.fetch_add(1, Ordering::Relaxed); |
385 | 3 | return Err(FeoxError::KeyNotFound); |
386 | 5 | } |
387 | 3 | } |
388 | 3.56k | } |
389 | | |
390 | 3.57k | let value = if let Some(val3.57k ) = record.get_value() { |
391 | 3.57k | val.to_vec() |
392 | | } else { |
393 | 4 | cache_hit = false; // Reading from disk |
394 | 4 | self.load_value_from_disk(&record)?0 |
395 | | }; |
396 | | |
397 | 3.57k | if self.enable_caching { |
398 | 4 | if let Some(ref cache) = self.cache { |
399 | 4 | cache.insert(key.to_vec(), Bytes::from(value.clone())); |
400 | 4 | }0 |
401 | 3.57k | } |
402 | | |
403 | 3.57k | self.stats |
404 | 3.57k | .record_get(start.elapsed().as_nanos() as u64, cache_hit); |
405 | 3.57k | Ok(value) |
406 | 3.61k | } |
407 | | |
408 | | /// Get a value by key without copying (zero-copy). |
409 | | /// |
410 | | /// Returns `Bytes` which avoids the memory copy that `get()` performs |
411 | | /// when converting to `Vec<u8>`. |
412 | | /// |
413 | | /// # Arguments |
414 | | /// |
415 | | /// * `key` - The key to look up |
416 | | /// |
417 | | /// # Returns |
418 | | /// |
419 | | /// Returns the value as `Bytes` if found. |
420 | | /// |
421 | | /// # Example |
422 | | /// |
423 | | /// ```rust |
424 | | /// # use feoxdb::FeoxStore; |
425 | | /// # fn main() -> feoxdb::Result<()> { |
426 | | /// # let store = FeoxStore::new(None)?; |
427 | | /// # store.insert(b"key", b"value")?; |
428 | | /// let bytes = store.get_bytes(b"key")?; |
429 | | /// // Use bytes directly without copying |
430 | | /// assert_eq!(&bytes[..], b"value"); |
431 | | /// # Ok(()) |
432 | | /// # } |
433 | | /// ``` |
434 | | /// |
435 | | /// # Performance |
436 | | /// |
437 | | /// Significantly faster than `get()` for large values: |
438 | | /// * 100 bytes: ~15% faster |
439 | | /// * 1KB: ~50% faster |
440 | | /// * 10KB: ~90% faster |
441 | | /// * 100KB: ~95% faster |
442 | 10 | pub fn get_bytes(&self, key: &[u8]) -> Result<Bytes> { |
443 | 10 | let start = std::time::Instant::now(); |
444 | 10 | self.validate_key(key)?0 ; |
445 | | |
446 | 10 | if self.enable_caching { |
447 | 0 | if let Some(ref cache) = self.cache { |
448 | 0 | if let Some(value) = cache.get(key) { |
449 | 0 | self.stats |
450 | 0 | .record_get(start.elapsed().as_nanos() as u64, true); |
451 | 0 | return Ok(value); |
452 | 0 | } |
453 | 0 | } |
454 | 10 | } |
455 | | |
456 | 10 | let record9 = self |
457 | 10 | .hash_table |
458 | 10 | .read(key, |_, v| v9 .clone9 ()) |
459 | 10 | .ok_or(FeoxError::KeyNotFound)?1 ; |
460 | | |
461 | | // Check TTL expiry if TTL is enabled |
462 | 9 | if self.enable_ttl { |
463 | 1 | let ttl_expiry = record.ttl_expiry.load(Ordering::Relaxed); |
464 | 1 | if ttl_expiry > 0 { |
465 | 1 | let now = SystemTime::now() |
466 | 1 | .duration_since(UNIX_EPOCH) |
467 | 1 | .unwrap_or_default() |
468 | 1 | .as_nanos() as u64; |
469 | 1 | if now > ttl_expiry { |
470 | 0 | self.stats.ttl_expired_lazy.fetch_add(1, Ordering::Relaxed); |
471 | 0 | return Err(FeoxError::KeyNotFound); |
472 | 1 | } |
473 | 0 | } |
474 | 8 | } |
475 | | |
476 | 9 | let (value, cache_hit) = if let Some(val) = record.get_value() { |
477 | 9 | (val, true) |
478 | | } else { |
479 | 0 | (Bytes::from(self.load_value_from_disk(&record)?), false) |
480 | | }; |
481 | | |
482 | 9 | if self.enable_caching { |
483 | 0 | if let Some(ref cache) = self.cache { |
484 | 0 | cache.insert(key.to_vec(), value.clone()); |
485 | 0 | } |
486 | 9 | } |
487 | | |
488 | 9 | self.stats |
489 | 9 | .record_get(start.elapsed().as_nanos() as u64, cache_hit); |
490 | 9 | Ok(value) |
491 | 10 | } |
492 | | |
493 | | /// Delete a key-value pair. |
494 | | /// |
495 | | /// # Arguments |
496 | | /// |
497 | | /// * `key` - The key to delete |
498 | | /// * `timestamp` - Optional timestamp for conflict resolution |
499 | | /// |
500 | | /// # Returns |
501 | | /// |
502 | | /// Returns `Ok(())` if the key was deleted. |
503 | | /// |
504 | | /// # Errors |
505 | | /// |
506 | | /// * `KeyNotFound` - Key does not exist |
507 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
508 | | /// |
509 | | /// # Example |
510 | | /// |
511 | | /// ```rust |
512 | | /// # use feoxdb::FeoxStore; |
513 | | /// # fn main() -> feoxdb::Result<()> { |
514 | | /// # let store = FeoxStore::new(None)?; |
515 | | /// # store.insert(b"temp", b"data")?; |
516 | | /// store.delete(b"temp")?; |
517 | | /// # Ok(()) |
518 | | /// # } |
519 | | /// ``` |
520 | | /// |
521 | | /// # Performance |
522 | | /// |
523 | | /// * Memory mode: ~300ns |
524 | | /// * Persistent mode: ~400ns |
525 | 30 | pub fn delete(&self, key: &[u8]) -> Result<()> { |
526 | 30 | self.delete_with_timestamp(key, None) |
527 | 30 | } |
528 | | |
529 | | /// Delete a key-value pair with explicit timestamp. |
530 | | /// |
531 | | /// This is the advanced version that allows manual timestamp control. |
532 | | /// Most users should use `delete()` instead. |
533 | | /// |
534 | | /// # Arguments |
535 | | /// |
536 | | /// * `key` - The key to delete |
537 | | /// * `timestamp` - Optional timestamp. If `None`, uses current time. |
538 | | /// |
539 | | /// # Errors |
540 | | /// |
541 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
542 | 32 | pub fn delete_with_timestamp(&self, key: &[u8], timestamp: Option<u64>) -> Result<()> { |
543 | 32 | let start = std::time::Instant::now(); |
544 | 32 | let timestamp = match timestamp { |
545 | 30 | Some(0) | None => self.get_timestamp(), |
546 | 2 | Some(ts) => ts, |
547 | | }; |
548 | 32 | self.validate_key(key)?0 ; |
549 | | |
550 | | // Remove from hash table and get the record |
551 | 32 | let record_pair22 = self.hash_table.remove(key).ok_or(FeoxError::KeyNotFound)?10 ; |
552 | 22 | let record = record_pair.1; |
553 | | |
554 | 22 | if timestamp < record.timestamp { |
555 | | // Put it back if timestamp is older |
556 | 1 | self.hash_table.upsert(key.to_vec(), record); |
557 | 1 | return Err(FeoxError::OlderTimestamp); |
558 | 21 | } |
559 | | |
560 | 21 | let record_size = record.calculate_size(); |
561 | 21 | let old_value_len = record.value_len; |
562 | | |
563 | | // Mark record as deleted by setting refcount to 0 |
564 | 21 | record.refcount.store(0, Ordering::Release); |
565 | | |
566 | | // Remove from lock-free skip list |
567 | 21 | self.tree.remove(key); |
568 | | |
569 | | // Update statistics |
570 | 21 | self.stats.record_count.fetch_sub(1, Ordering::AcqRel); |
571 | 21 | self.stats |
572 | 21 | .memory_usage |
573 | 21 | .fetch_sub(record_size, Ordering::AcqRel); |
574 | | |
575 | | // Clear from cache |
576 | 21 | if self.enable_caching { |
577 | 2 | if let Some(ref cache) = self.cache { |
578 | 2 | cache.remove(key); |
579 | 2 | }0 |
580 | 19 | } |
581 | | |
582 | | // Queue deletion for persistence if write buffer exists and not memory-only |
583 | 21 | if !self.memory_only { |
584 | 2 | if let Some(ref wb) = self.write_buffer { |
585 | 2 | if let Err(_e0 ) = wb.add_write(Operation::Delete, record, old_value_len) { |
586 | 0 | // Silent failure - data operation succeeded in memory |
587 | 2 | } |
588 | 0 | } |
589 | 19 | } |
590 | | |
591 | 21 | self.stats.record_delete(start.elapsed().as_nanos() as u64); |
592 | 21 | Ok(()) |
593 | 32 | } |
594 | | |
595 | | /// Get the size of a value without loading it. |
596 | | /// |
597 | | /// Useful for checking value size before loading large values from disk. |
598 | | /// |
599 | | /// # Arguments |
600 | | /// |
601 | | /// * `key` - The key to check |
602 | | /// |
603 | | /// # Returns |
604 | | /// |
605 | | /// Returns the size in bytes of the value. |
606 | | /// |
607 | | /// # Errors |
608 | | /// |
609 | | /// * `KeyNotFound` - Key does not exist |
610 | | /// |
611 | | /// # Example |
612 | | /// |
613 | | /// ```rust |
614 | | /// # use feoxdb::FeoxStore; |
615 | | /// # fn main() -> feoxdb::Result<()> { |
616 | | /// # let store = FeoxStore::new(None)?; |
617 | | /// store.insert(b"large_file", &vec![0u8; 1_000_000])?; |
618 | | /// |
619 | | /// // Check size before loading |
620 | | /// let size = store.get_size(b"large_file")?; |
621 | | /// assert_eq!(size, 1_000_000); |
622 | | /// # Ok(()) |
623 | | /// # } |
624 | | /// ``` |
625 | 2 | pub fn get_size(&self, key: &[u8]) -> Result<usize> { |
626 | 2 | self.validate_key(key)?0 ; |
627 | | |
628 | 2 | let record1 = self |
629 | 2 | .hash_table |
630 | 2 | .read(key, |_, v| v1 .clone1 ()) |
631 | 2 | .ok_or(FeoxError::KeyNotFound)?1 ; |
632 | | |
633 | 1 | Ok(record.value_len) |
634 | 2 | } |
635 | | |
636 | | // Internal helper methods |
637 | | |
638 | 5.36k | pub(super) fn validate_key_value(&self, key: &[u8], value: &[u8]) -> Result<()> { |
639 | 5.36k | if key.is_empty() || key.len() > MAX_KEY_SIZE5.36k { |
640 | 2 | return Err(FeoxError::InvalidKeySize); |
641 | 5.36k | } |
642 | | |
643 | 5.36k | if value.is_empty() || value.len() > MAX_VALUE_SIZE5.36k { |
644 | 1 | return Err(FeoxError::InvalidValueSize); |
645 | 5.36k | } |
646 | | |
647 | 5.36k | Ok(()) |
648 | 5.36k | } |
649 | | |
650 | 4.79k | pub(super) fn validate_key(&self, key: &[u8]) -> Result<()> { |
651 | 4.79k | if key.is_empty() || key.len() > MAX_KEY_SIZE { |
652 | 0 | return Err(FeoxError::InvalidKeySize); |
653 | 4.79k | } |
654 | | |
655 | 4.79k | Ok(()) |
656 | 4.79k | } |
657 | | |
658 | 1.59k | pub(super) fn check_memory_limit(&self, size: usize) -> bool { |
659 | 1.59k | match self.max_memory { |
660 | 1.59k | Some(limit) => { |
661 | 1.59k | let current = self.stats.memory_usage.load(Ordering::Acquire); |
662 | 1.59k | current + size <= limit |
663 | | } |
664 | 0 | None => true, |
665 | | } |
666 | 1.59k | } |
667 | | |
668 | 4.60k | pub(super) fn calculate_record_size(&self, key_len: usize, value_len: usize) -> usize { |
669 | 4.60k | std::mem::size_of::<Record>() + key_len + value_len |
670 | 4.60k | } |
671 | | |
672 | 4.34k | pub(super) fn get_timestamp(&self) -> u64 { |
673 | 4.34k | self.get_timestamp_pub() |
674 | 4.34k | } |
675 | | } |