nitrite 0.4.0

An embedded NoSQL document database for Rust with collections, repositories, indexing, and ACID transactions
Documentation
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
use super::IndexDescriptor;
use crate::{
    collection::{FindPlan, NitriteId},
    errors::{ErrorKind, NitriteError, NitriteResult},
    FieldValues,
};
use std::ops::Deref;
use std::sync::Arc;

/// Provider trait for concrete index implementations.
///
/// NitriteIndexProvider defines the core contract for index storage and retrieval.
/// Each index type (simple B-Tree, compound nested, spatial) implements this trait
/// to manage the mapping between field values and document identifiers.
///
/// # Responsibilities
/// - Store and retrieve mappings from indexed field values to NitriteIds
/// - Enforce uniqueness constraints if applicable
/// - Support write/remove operations for document changes
/// - Execute filter-based queries to find matching documents
/// - Lifecycle management for index resources
pub trait NitriteIndexProvider: Send + Sync {
    /// Retrieves the metadata descriptor for this index.
    ///
    /// # Returns
    /// IndexDescriptor containing index type, field names, and collection info.
    ///
    /// # Errors
    /// Returns IndexingError if descriptor metadata is inaccessible.
    fn index_descriptor(&self) -> NitriteResult<IndexDescriptor>;

    /// Records a field value mapping in the index.
    ///
    /// # Arguments
    /// * `field_values` - The indexed field value(s) and associated NitriteId
    ///
    /// # Returns
    /// Ok(()) on success, Error on failure.
    ///
    /// # Behavior
    /// Stores the mapping from field_values to its NitriteId in the index.
    /// For unique indexes, enforces that duplicate field values are rejected.
    ///
    /// # Errors
    /// Returns IndexingError if write fails or unique constraint is violated.
    fn write(&self, field_values: &FieldValues) -> NitriteResult<()>;

    /// Removes a field value mapping from the index.
    ///
    /// # Arguments
    /// * `field_values` - The indexed field value(s) and NitriteId to remove
    ///
    /// # Returns
    /// Ok(()) on success, Error on failure.
    ///
    /// # Errors
    /// Returns IndexingError if removal fails.
    fn remove(&self, field_values: &FieldValues) -> NitriteResult<()>;

    /// Drops the entire index, releasing all associated resources.
    ///
    /// # Returns
    /// Ok(()) if successful, Error on failure.
    ///
    /// # Behavior
    /// Removes all data associated with this index. After this call,
    /// the index is no longer usable.
    ///
    /// # Errors
    /// Returns IndexingError if cleanup fails.
    fn drop_index(&self) -> NitriteResult<()>;

    /// Finds all NitriteIds matching criteria specified in the find plan.
    ///
    /// # Arguments
    /// * `find_plan` - Query plan with filters to evaluate
    ///
    /// # Returns
    /// Vector of NitriteIds matching all filter criteria, empty if none match.
    ///
    /// # Behavior
    /// Evaluates filters against indexed data and returns matching document IDs.
    /// Results are deduplicated and consistently ordered.
    ///
    /// # Errors
    /// Returns IndexingError if query execution fails.
    fn find_nitrite_ids(&self, find_plan: &FindPlan) -> NitriteResult<Vec<NitriteId>>;

    /// Returns whether this index enforces uniqueness on indexed field values.
    ///
    /// # Returns
    /// `true` for unique indexes, `false` for non-unique indexes.
    ///
    /// # Behavior
    /// Used to determine if duplicate field values should be rejected.
    /// Unique indexes will return error from write() if value exists.
    fn is_unique(&self) -> bool;

    /// Adds a NitriteId to the given list if uniqueness constraints allow it.
    ///
    /// # Arguments
    /// * `nitrite_ids` - Mutable vector to add the ID to
    /// * `field_values` - The field values context for uniqueness checking
    ///
    /// # Returns
    /// The modified nitrite_ids vector with the new ID added, or error.
    ///
    /// # Behavior
    /// For unique indexes with existing values, returns IndexingError.
    /// For non-unique or non-conflicting unique indexes, appends ID using
    /// mem::take for efficiency (moves vector ownership).
    ///
    /// # Errors
    /// Returns IndexingError if unique constraint is violated.
    fn add_nitrite_ids(
        &self,
        nitrite_ids: &mut Vec<NitriteId>,
        field_values: &FieldValues,
    ) -> NitriteResult<Vec<NitriteId>> {
        if self.is_unique() && nitrite_ids.len() == 1 {
            // if key is already exists for unique type, throw error
            log::error!("Unique constraint violated for {:?}", field_values);
            return Err(NitriteError::new(
                &format!("Unique constraint violated for {:?}", field_values),
                ErrorKind::IndexingError,
            ));
        }

        // index always are in ascending format
        nitrite_ids.push(*field_values.nitrite_id());
        Ok(std::mem::take(nitrite_ids))
    }

    /// Removes a NitriteId from the given list if it matches field values.
    ///
    /// # Arguments
    /// * `nitrite_ids` - Mutable vector to remove ID from
    /// * `field_values` - The field values and NitriteId to match and remove
    ///
    /// # Returns
    /// The modified nitrite_ids vector with matching ID removed, or error.
    ///
    /// # Behavior
    /// Filters out the matching NitriteId using retain. If entry doesn't exist,
    /// silently succeeds. Uses mem::take for efficient vector ownership transfer.
    ///
    /// # Errors
    /// Returns IndexingError only if an unexpected error occurs during filtering.
    fn remove_nitrite_ids(
        &self,
        nitrite_ids: &mut Vec<NitriteId>,
        field_values: &FieldValues,
    ) -> NitriteResult<Vec<NitriteId>> {
        if !nitrite_ids.is_empty() {
            nitrite_ids.retain(|x| x != field_values.nitrite_id());
        }
        Ok(std::mem::take(nitrite_ids))
    }
}

/// Wrapper for concrete index implementations with thread-safe reference counting.
///
/// NitriteIndex provides a unified interface for low-level index operations by
/// wrapping concrete index implementations. It enables different index types
/// (simple B-Tree, compound nested, spatial) to be used interchangeably through
/// the NitriteIndexProvider trait.
///
/// # Characteristics
/// - **Type-agnostic**: Handles any index type implementing NitriteIndexProvider
/// - **Thread-safe**: Safely shared across threads via Arc
/// - **Cloneable**: Cheap cloning via shared Arc reference
/// - **Direct mapping**: Maps field values directly to NitriteIds
///
/// # Relationship to NitriteIndexer
///
/// NitriteIndex provides lower-level storage operations (write, remove, find),
/// while NitriteIndexer wraps indexer plugins that handle higher-level concerns
/// like validation and lifecycle management.
///
/// # Deref Behavior
///
/// NitriteIndex implements Deref to transparently access NitriteIndexProvider
/// methods through the wrapper, allowing seamless method calls on the underlying
/// implementation.
#[derive(Clone)]
pub struct NitriteIndex {
    inner: Arc<dyn NitriteIndexProvider>,
}

impl NitriteIndex {
    /// Creates a new NitriteIndex wrapping a concrete implementation.
    ///
    /// # Arguments
    /// * `inner` - A concrete type implementing NitriteIndexProvider
    ///
    /// # Returns
    /// A new NitriteIndex wrapping the provided implementation in an Arc
    /// for thread-safe reference counting.
    ///
    /// # Behavior
    /// Wraps the implementation in Arc<dyn NitriteIndexProvider> to enable
    /// polymorphic index usage. The implementation remains accessible through
    /// Deref without explicit unwrapping.
    pub fn new<T: NitriteIndexProvider + 'static>(inner: T) -> Self {
        NitriteIndex { inner: Arc::new(inner) }
    }
}

impl Deref for NitriteIndex {
    type Target = Arc<dyn NitriteIndexProvider>;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::common::{Convertible, Fields, UNIQUE_INDEX};
    use crate::errors::ErrorKind;

    struct MockNitriteIndex;

    impl NitriteIndexProvider for MockNitriteIndex {
        fn index_descriptor(&self) -> NitriteResult<IndexDescriptor> {
            Ok(IndexDescriptor::new(
                UNIQUE_INDEX,
                Fields::with_names(vec!["test_field"])?,
                "test",
            ))
        }

        fn write(&self, _field_values: &FieldValues) -> NitriteResult<()> {
            Ok(())
        }

        fn remove(&self, _field_values: &FieldValues) -> NitriteResult<()> {
            Ok(())
        }

        fn drop_index(&self) -> NitriteResult<()> {
            Ok(())
        }

        fn find_nitrite_ids(&self, _find_plan: &FindPlan) -> NitriteResult<Vec<NitriteId>> {
            Ok(vec![NitriteId::new()])
        }

        fn is_unique(&self) -> bool {
            true
        }
    }

    #[test]
    fn test_index_descriptor() {
        let index = NitriteIndex::new(MockNitriteIndex);
        assert!(index.index_descriptor().is_ok());
    }

    #[test]
    fn test_write() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        assert!(index.write(&field_values).is_ok());
    }

    #[test]
    fn test_remove() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        assert!(index.remove(&field_values).is_ok());
    }

    #[test]
    fn test_drop_index() {
        let index = NitriteIndex::new(MockNitriteIndex);
        assert!(index.drop_index().is_ok());
    }

    #[test]
    fn test_find_nitrite_ids() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let find_plan = FindPlan::new();
        assert!(index.find_nitrite_ids(&find_plan).is_ok());
    }

    #[test]
    fn test_is_unique() {
        let index = NitriteIndex::new(MockNitriteIndex);
        assert!(index.is_unique());
    }

    #[test]
    fn test_add_nitrite_ids_unique_violation() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids = vec![NitriteId::new()];
        let result = index.add_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().kind(), &ErrorKind::IndexingError);
    }

    #[test]
    fn test_add_nitrite_ids() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids = vec![];
        let result = index.add_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 1);
    }

    #[test]
    fn test_remove_nitrite_ids() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids = vec![*field_values.nitrite_id()];
        let result = index.remove_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }

    #[test]
    fn test_remove_nitrite_ids_not_found() {
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids = vec![NitriteId::new()];
        let result = index.remove_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 1);
    }

    // Performance optimization tests
    #[test]
    fn test_add_nitrite_ids_uses_mem_take() {
        // Test that add_nitrite_ids uses mem::take instead of clone
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids = vec![];
        let result = index.add_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 1);
        // Original should be empty due to mem::take
        assert!(nitrite_ids.is_empty());
    }

    #[test]
    fn test_remove_nitrite_ids_uses_mem_take() {
        // Test that remove_nitrite_ids uses mem::take instead of clone
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids = vec![*field_values.nitrite_id()];
        let result = index.remove_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
        // Original should be empty due to mem::take
        assert!(nitrite_ids.is_empty());
    }

    #[test]
    fn test_add_nitrite_ids_multiple_entries() {
        // Test that add_nitrite_ids efficiently handles multiple IDs
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        let mut nitrite_ids: Vec<NitriteId> = (0..10)
            .map(|_| NitriteId::new())
            .collect();
        
        let original_len = nitrite_ids.len();
        let result = index.add_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), original_len + 1);
    }

    #[test]
    fn test_remove_nitrite_ids_batch_retention() {
        // Test that remove_nitrite_ids efficiently filters multiple entries
        let index = NitriteIndex::new(MockNitriteIndex);
        let field_values = FieldValues::new(
            vec![(String::from("test_field"), 1.to_value().unwrap())],
            NitriteId::new(),
            Fields::with_names(vec!["test_field"]).unwrap(),
        );
        
        let mut nitrite_ids = vec![
            *field_values.nitrite_id(),
            NitriteId::new(),
            NitriteId::new(),
        ];
        
        let result = index.remove_nitrite_ids(&mut nitrite_ids, &field_values);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().len(), 2);
    }
}