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
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)
}

/// Identifier for changes
pub struct Action<'batch>(ActionKind, TypeLevelId<'batch>);

enum ActionKind {
    Commit,
    Rollback,
}

impl<'batch> ScopedStore<'batch> {
    /// Commit changes done in this ScopedStore.
    pub fn commit_changes(self: ScopedStore<'batch>) -> Action<'batch> {
        Action(ActionKind::Commit, TypeLevelId::default())
    }

    /// Revert changes done in this ScopedStore.
    pub fn revert_changes(self: ScopedStore<'batch>) -> Action<'batch> {
        Action(ActionKind::Rollback, TypeLevelId::default())
    }

    /// Return an handle to change a single [`Collection`].
    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,
        }
    }
}

/// Provides information about the result of an operation on a document
/// that was part of a write transaction.
pub struct WriteTransactionResult {
    /// The name of the collection that the operation took place in.
    pub collection_name: char_p::Box,

    /// The Id of the document that the write transaction result applies to.
    pub doc_id: DocumentId,

    /// The type of write transaction result.
    pub kind: DocChangeKind,
}

#[derive(Debug, PartialEq, Eq)]

/// The types of write transaction result.
pub enum DocChangeKind {
    Inserted,
    Removed,
    Evicted,
    Updated,
}