Skip to main content

feoxdb/core/store/
json_patch.rs

1use crate::error::{FeoxError, Result};
2use std::sync::Arc;
3
4use super::FeoxStore;
5
6impl FeoxStore {
7    /// Apply a JSON patch to a value.
8    ///
9    /// Uses RFC 6902 JSON Patch format to modify specific fields in a JSON document.
10    /// Both the existing value and the patch must be valid JSON.
11    ///
12    /// # Arguments
13    ///
14    /// * `key` - The key containing the JSON document to patch
15    /// * `patch` - JSON Patch operations in RFC 6902 format
16    /// * `timestamp` - Optional timestamp for conflict resolution
17    ///
18    /// # Returns
19    ///
20    /// Returns `Ok(())` if the update was applied.
21    ///
22    /// # Errors
23    ///
24    /// * `KeyNotFound` - Key does not exist
25    /// * `OlderTimestamp` - Timestamp is not newer than existing record
26    /// * `JsonPatchError` - Invalid JSON document or patch format
27    ///
28    /// # Example
29    ///
30    /// ```no_run
31    /// # use feoxdb::FeoxStore;
32    /// # fn main() -> feoxdb::Result<()> {
33    /// # let store = FeoxStore::new(None)?;
34    /// // Insert initial JSON value
35    /// let initial = br#"{"name":"Alice","age":30}"#;
36    /// store.insert(b"user:1", initial)?;
37    ///
38    /// // Apply JSON patch to update age
39    /// let patch = br#"[{"op":"replace","path":"/age","value":31}]"#;
40    /// store.json_patch(b"user:1", patch)?;
41    ///
42    /// // Value now has age updated to 31
43    /// let updated = store.get(b"user:1")?;
44    /// assert_eq!(updated.len(), initial.len()); // Same length, just age changed
45    /// # Ok(())
46    /// # }
47    /// ```
48    pub fn json_patch(&self, key: &[u8], patch: &[u8]) -> Result<()> {
49        self.json_patch_with_timestamp(key, patch, None)
50    }
51
52    /// Apply JSON patch with explicit timestamp.
53    ///
54    /// This is the advanced version that allows manual timestamp control.
55    /// Most users should use `json_patch()` instead.
56    ///
57    /// # Arguments
58    ///
59    /// * `key` - The key whose value to patch
60    /// * `patch` - JSON Patch array (RFC 6902)
61    /// * `timestamp` - Optional timestamp. If `None`, uses current time.
62    ///
63    /// # Errors
64    ///
65    /// * `OlderTimestamp` - Timestamp is not newer than existing record
66    pub fn json_patch_with_timestamp(
67        &self,
68        key: &[u8],
69        patch: &[u8],
70        timestamp: Option<u64>,
71    ) -> Result<()> {
72        self.validate_key(key)?;
73        let timestamp = self.resolve_timestamp(key, timestamp);
74        let timestamp_value = timestamp.0;
75
76        let start = std::time::Instant::now();
77        let mut observed = None;
78        loop {
79            let record = self
80                .hash_table
81                .read(key, |_, record| record.clone())
82                .ok_or(FeoxError::KeyNotFound)?;
83            let observed = observed.get_or_insert_with(|| Arc::clone(&record));
84            if !Arc::ptr_eq(observed, &record) && timestamp_value <= observed.retirement_timestamp()
85            {
86                return Err(FeoxError::OlderTimestamp);
87            }
88
89            if timestamp_value <= record.timestamp {
90                return Err(FeoxError::OlderTimestamp);
91            }
92
93            let (current_value, _, source) = self.resolve_value(key, record)?;
94            if !Arc::ptr_eq(observed, &source) && timestamp_value <= observed.retirement_timestamp()
95            {
96                return Err(FeoxError::OlderTimestamp);
97            }
98            let new_value = crate::utils::json_patch::apply_json_patch(&current_value, patch)?;
99            self.validate_key_value(key, &new_value)?;
100            crate::test_hooks::pause_at(crate::test_hooks::AFTER_JSON_PATCH_READ);
101
102            if self.replace_record_if_current(key, &source, &new_value, timestamp, 0, start)? {
103                return Ok(());
104            }
105        }
106    }
107}