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
use_prelude!();

use collection::ScopedCollection;

use super::collection::document_id::DocumentId;
mod collection;

/// Use lifetime parameters as type-level IDs (generative lifetimes),
/// so as to prevent the following anti-pattern from compiling:
///
/// ```no_run
/// use ::dittolive_ditto::prelude::*;
/// use ::std::path::Path;
/// use ::serde_json::json;
///
/// let dir1 = Path::new("...");
/// let mut ditto1 = Ditto::builder().build().unwrap();
///
/// ditto1.store().with_batched_write(|mut tx| {
///     // API requires that this closure return:
///     // - either `tx.commit_changes()`
///     // - or `tx.revert_changes()`
///     let mut coll = tx.collection("collection");
///     coll
///        .find_with_args("color == $args.color", json!({"color": "yellow"}))
///        .remove()
///        .unwrap();
///     tx.commit_changes()
/// });
/// ```
use marker::InvariantLifetime as TypeLevelId;

// Note, in practice `'batch` is `'txn`, but we hide this from
// the signatures to hide this implementation detail to the users.
pub struct ScopedStore<'batch> {
    // " 'batch == 'txn "
    txn: &'batch mut ffi_sdk::CWriteTransaction,

    // This could be 'store (which is ≥ 'txn), but for all effects and purposes
    // shrinking this to `'txn` is just as useful, and simplifies / reduces
    // the number of lifetime parameters.
    store: &'batch Store, // a Ref to the REAL store not the scoped store

    results: &'batch mut Vec<WriteTransactionResult>,

    // make sure the `'batch` lifetime is invariant so that it can be used as
    // a type-level unique id.
    _txn_id: TypeLevelId<'batch>,
}

pub(super) fn with_batched_write<F>(
    store: &Store, // Has lifetime of 'store
    f: F,
) -> Result<Vec<WriteTransactionResult>, super::DittoError>
where
    for<'batch> F: FnOnce(ScopedStore<'batch>) -> Action<'batch>,
{
    use super::*;

    let hint: Option<char_p::Ref<'_>> = None;
    let mut txn = unsafe { ffi_sdk::ditto_write_transaction(&*store.ditto, hint).ok()? };
    let mut results = vec![];

    let batch = ScopedStore {
        txn: &mut *txn,
        store, // The &Store contains an Arc to the root Ditto instance
        results: &mut results,
        _txn_id: TypeLevelId::default(),
    };
    match f(batch).0 {
        ActionKind::Commit => {
            let status = unsafe { ffi_sdk::ditto_write_transaction_commit(&*store.ditto, txn) };
            if status != 0 {
                return Err(DittoError::from_ffi(ErrorKind::Internal));
            }
        }
        ActionKind::Rollback => {
            unsafe { ffi_sdk::ditto_write_transaction_rollback(&*store.ditto, txn) };
        }
    }
    Ok(results)
}

pub struct Action<'batch>(ActionKind, TypeLevelId<'batch>);

enum ActionKind {
    Commit,
    Rollback,
}

impl<'batch> ScopedStore<'batch> {
    pub fn commit_changes(self: ScopedStore<'batch>) -> Action<'batch> {
        Action(ActionKind::Commit, TypeLevelId::default())
    }

    pub fn revert_changes(self: ScopedStore<'batch>) -> Action<'batch> {
        Action(ActionKind::Rollback, TypeLevelId::default())
    }

    pub fn collection<'coll>(
        self: &'coll mut ScopedStore<'batch>,
        collection_name: &'_ str,
    ) -> ScopedCollection<'coll, 'batch> {
        let c_name = char_p::new(collection_name);
        ScopedCollection {
            batch: self,
            collection_name: c_name,
        }
    }
}

pub struct WriteTransactionResult {
    pub collection_name: char_p::Box,

    pub doc_id: DocumentId,

    pub kind: DocChangeKind,
}

#[derive(Debug, PartialEq, Eq)]
pub enum DocChangeKind {
    Inserted,
    Removed,
    Evicted,
    Updated,
}