1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
use serde::Serialize;
use serde_json::Value;
use tokio::sync::RwLockWriteGuard;
use crate::error::NanoDBError;
use super::tree::Tree;
/// A struct representing a write-guarded tree.
///
/// This struct contains a write lock guard and a tree. The write lock guard ensures that only one thread can modify the tree at a time.
///
/// # Fields
///
/// * `_guard` - The write lock guard. This is not directly used, but its existence ensures that the tree cannot be modified by other threads.
/// * `inner` - The tree that is being guarded.
#[derive(Debug)]
pub struct WriteGuardedTree<'a> {
_guard: RwLockWriteGuard<'a, Value>,
tree: Tree,
}
impl<'a> WriteGuardedTree<'a> {
/// Creates a new WriteGuardedTree instance.
///
/// # Arguments
///
/// * `guard` - The write lock guard. This is not directly used, but its existence ensures that the tree cannot be modified by other threads.
/// * `value` - The initial JSON value of the tree.
///
/// # Returns
///
/// * `WriteGuardedTree` - The new WriteGuardedTree instance.
pub(crate) fn new(guard: RwLockWriteGuard<'a, Value>, value: Value) -> Self {
let tree = Tree::new(value, vec![]);
WriteGuardedTree {
_guard: guard,
tree,
}
}
/// Releases the write lock guard of the TreeWriteGuarded instance.
///
/// This function consumes the TreeWriteGuarded instance and drops it, which releases the write lock guard.
pub fn release_lock(self) {
drop(self);
}
/// Retrieves the value associated with a given key in the JSON data of the TreeWriteGuarded instance.
///
/// # Arguments
///
/// * `key` - The key to retrieve the value for.
///
/// # Returns
///
/// * `Ok(&mut Self)` - The TreeWriteGuarded instance itself after the retrieval. This allows for method chaining.
/// * `Err(NanoDBError::InvalidJSONPath)` - If the path to the key in the JSON data is invalid.
pub fn get(&mut self, key: &str) -> Result<&mut Self, NanoDBError> {
self.tree = self.tree.get(key)?;
Ok(self)
}
/// Retrieves the value at a given index in the JSON array of the TreeWriteGuarded instance.
///
/// # Arguments
///
/// * `index` - The index to retrieve the value from.
///
/// # Returns
///
/// * `Ok(&mut Self)` - The TreeWriteGuarded instance itself after the retrieval. This allows for method chaining.
/// * `Err(NanoDBError::InvalidJSONPath)` - If the path to the index in the JSON data is invalid.
/// * `Err(NanoDBError::IndexOutOfBounds)` - If the index is out of bounds.
pub fn at(&mut self, index: usize) -> Result<&mut Self, NanoDBError> {
self.tree = self.tree.at(index)?;
Ok(self)
}
/// Inserts a key-value pair into the inner JSON object of the TreeWriteGuarded instance,
/// at the current path of the tree.
///
/// # Arguments
///
/// * `key` - The key to insert the value for.
/// * `value` - The value to insert. This value must implement the `Serialize` trait.
///
/// # Returns
///
/// * `Ok(&mut Self)` - The TreeWriteGuarded instance itself after the insertion. This allows for method chaining.
/// * `Err(NanoDBError::InvalidJSONPath)` - If the path to the key in the JSON data is invalid.
/// * `Err(NanoDBError::IndexOutOfBounds)` - If an array index in the path is out of bounds.
pub fn insert<T: Serialize>(&mut self, key: &str, value: T) -> Result<&mut Self, NanoDBError> {
self.tree = self.tree.clone().insert(key, value)?;
self.merge()?;
Ok(self)
}
/// Removes a key-value pair from the inner JSON object of the TreeWriteGuarded instance and then merges the result into the current JSON value of the write lock guard.
///
/// # Arguments
///
/// * `key` - The key to remove the value for.
///
/// # Returns
///
/// * `Ok(&mut Self)` - The TreeWriteGuarded instance itself after the removal and merge. This allows for method chaining.
/// * `Err(NanoDBError)` - If there was an error during the removal or the merge.
pub fn remove(&mut self, key: &str) -> Result<&mut Self, NanoDBError> {
self.tree = self.tree.clone().remove(key)?;
self.merge()?;
Ok(self)
}
/// Removes an element at a specific index from the array stored in the `Tree` instance of the `TreeWriteGuarded` and then merges the result into the current JSON value of the write lock guard.
///
/// # Arguments
///
/// * `index` - The index at which to remove the element.
///
/// # Returns
///
/// * `Ok(&mut Self)` - The `TreeWriteGuarded` instance itself after the removal and merge. This allows for method chaining.
/// * `Err(NanoDBError)` - If there was an error during the removal or the merge.
pub fn remove_at(&mut self, index: usize) -> Result<&mut Self, NanoDBError> {
self.tree = self.tree.clone().remove_at(index)?;
self.merge()?;
Ok(self)
}
/// Pushes a value to the tree if it's currently pointing to an array.
///
/// # Arguments
///
/// * `value` - A value of type T that implements the Serialize trait. This value will be serialized to JSON and pushed to the array.
///
/// # Returns
///
/// * `Ok(Tree)` - A new Tree object that represents the current state of the tree after the value has been pushed.
/// * `Err(NanoDBError::NotAnArray)` - If the inner value of the tree is not an array.
pub fn push<T: Serialize>(&mut self, value: T) -> Result<&mut Self, NanoDBError> {
self.tree = self.tree.clone().push(value)?;
self.merge()?;
Ok(self)
}
/// Applies a function to each element of the inner array of the tree.
///
/// # Arguments
///
/// * `f` - A mutable function that takes a mutable reference to a `serde_json::Value` and returns `()`.
///
/// # Returns
///
/// * `Ok(Tree)` - A new Tree object that represents the current state of the tree after the function has been applied to each element.
/// * `Err(NanoDBError::NotAnArray)` - If the inner value of the tree is not an array.
pub fn for_each<F>(&mut self, f: F) -> Result<&mut Self, NanoDBError>
where
F: FnMut(&mut serde_json::Value),
{
self.tree = self.tree.clone().for_each(f)?;
self.merge()?;
Ok(self)
}
/// Converts the inner JSON object of the TreeWriteGuarded instance into a specified type.
///
/// # Type Parameters
///
/// * `T` - The type to convert the JSON object into. This type must implement the `Deserialize` trait.
///
/// # Returns
///
/// * `Ok(T)` - The JSON object converted into the specified type.
/// * `Err(serde_json::Error)` - If there was an error during the conversion.
pub fn into<T: for<'de> serde::Deserialize<'de>>(&mut self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.tree.inner())
}
/// Merges the inner Tree (self.tree) instance into the the write lock guard.
///
/// # Returns
///
/// * `Ok(&mut Self)` - The TreeWriteGuarded instance itself after the merge. This allows for method chaining.
/// * `Err(NanoDBError)` - If there was an error during the merge.
pub fn merge(&mut self) -> Result<&mut Self, NanoDBError> {
let current = &mut *self._guard;
// Wrap it in a Tree so we can use the standard tree method to merge
let mut current_wrapped = Tree::new(current.clone(), vec![]);
current_wrapped.merge_from(self.tree.clone())?;
// Unwrap the value and assign it to the guard
*current = current_wrapped.inner();
Ok(self)
}
/// get snapshot of the tree
pub fn tree(&self) -> &Tree {
&self.tree
}
}
#[cfg(test)]
mod tests {
use crate::{
nanodb::NanoDB,
trees::tree::{PathStep, Tree},
};
use serde_json::{json, Value};
fn value() -> Value {
serde_json::from_str(
r#"{
"key1": "value1",
"key2": {
"inner_key1": "inner_value1",
"inner_key2": "inner_value2"
},
"key3": [1, 2, 3]
}"#,
)
.unwrap()
}
fn value_str() -> String {
r#"{
"key1": "value1",
"key2": {
"inner_key1": "inner_value1",
"inner_key2": "inner_value2"
},
"key3": [1, 2, 3]
}"#
.to_string()
}
#[tokio::test]
async fn test_write_guarded_new() {
let value = value();
let rwlock = tokio::sync::RwLock::new(value.clone());
let guard = rwlock.write().await;
let tree = Tree::new(value.clone(), vec![]);
let write_guarded = super::WriteGuardedTree::new(guard, value.clone());
assert_eq!(write_guarded.tree.inner(), tree.inner());
}
#[tokio::test]
async fn test_write_guarded_get() {
let db = NanoDB::new_from("/path/to/file.json", &value_str()).unwrap();
let mut write_guarded = db.update().await;
write_guarded.get("key2").unwrap();
let tree = Tree::new(
json!({
"inner_key1": "inner_value1",
"inner_key2": "inner_value2"
}),
vec![PathStep::Key("key2".to_string())],
);
assert_eq!(write_guarded.tree.inner(), tree.inner());
}
#[tokio::test]
async fn test_write_guarded_at() {
let db = NanoDB::new_from("/path/to/file.json", &value_str()).unwrap();
let mut write_guarded = db.update().await;
write_guarded.get("key3").unwrap().at(1).unwrap();
let tree = Tree::new(
json!(2),
vec![PathStep::Key("key3".to_string()), PathStep::Index(1)],
);
assert_eq!(write_guarded.tree.inner(), tree.inner());
}
#[tokio::test]
async fn test_write_guarded_insert() {
let db = NanoDB::new_from("/path/to/file.json", &value_str()).unwrap();
let mut write_guarded = db.update().await;
write_guarded
.get("key2")
.unwrap()
.insert("inner_key3", "inner_value3")
.unwrap();
let tree = Tree::new(
json!({
"inner_key1": "inner_value1",
"inner_key2": "inner_value2",
"inner_key3": "inner_value3"
}),
vec![PathStep::Key("key2".to_string())],
);
assert_eq!(write_guarded.tree.inner(), tree.inner());
}
#[tokio::test]
async fn test_write_guarded_remove() {
let db = NanoDB::new_from("/path/to/file.json", &value_str()).unwrap();
let mut write_guarded = db.update().await;
write_guarded
.get("key2")
.unwrap()
.remove("inner_key1")
.unwrap();
let tree = Tree::new(
json!({
"inner_key2": "inner_value2"
}),
vec![PathStep::Key("key2".to_string())],
);
assert_eq!(write_guarded.tree.inner(), tree.inner());
write_guarded.release_lock();
}
#[tokio::test]
async fn test_write_guarded_remove_at() {
let db = NanoDB::new_from("/path/to/file.json", &value_str()).unwrap();
let mut write_guarded = db.update().await;
write_guarded.get("key3").unwrap().remove_at(1).unwrap();
let tree = Tree::new(json!([1, 3]), vec![PathStep::Key("key3".to_string())]);
assert_eq!(write_guarded.tree.inner(), tree.inner());
write_guarded.release_lock();
}
}