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
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 mut txn = unsafe { ffi_sdk::ditto_write_transaction(&*store.ditto).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(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,
}