Compare commits

...

7 commits

Author SHA1 Message Date
Emily Toop
bb250a5798 Address review comments @nalexander 2018-05-15 09:19:07 +01:00
Emily Toop
133cad35b6 Wrap caching FFI functions in Android Java library.
`CacheDirection` enum is used only on the Android side to provide a usable interface. FFI calls are more explicit.

Tests ensure that a cached query is faster than the uncached one.
2018-05-14 19:34:05 +01:00
Emily Toop
312d3bf4e0 Wrap caching FFI functions in Swift library.
`CacheDirection` enum is used only on the Swift side to provide a usable interface. FFI calls are more explicit.

Tests ensure that a cached query is faster than the uncached one.
2018-05-14 19:21:48 +01:00
Emily Toop
dddcd1c17f Expose cache over the FFI.
This exposes an FFI function for each direction of caching, `Forward`, `Reverse` and `Both`. This is to make is as clear as possible to consumers which direction they are caching their attributes in. The original implementation exposed the `CacheDirection` enum over FFI and it made mistakes very easy to make. This is more explicit and therefore less prone to error.
2018-05-14 19:21:48 +01:00
Emily Toop
ffff8df490 Implement InProgress transactions and InProgress and Entity builders on Android.
Rename some of the functions in TypedValue, TupleResult and QueryBuilder to make them more Javay and less Swifty
2018-05-14 19:13:55 +01:00
Emily Toop
3865803981 Implement InProgress transactions and InProgress and Entity builders on iOS 2018-05-14 19:05:36 +01:00
Emily Toop
d8bde6ed97 Expose InProgress, InProgressBuilder and EntityBuilder over the FFI.
There are two ways to create each builder, directly from a `Store` or from an `InProgress`. Creating from `Store` will perform two actions, creating a new `InProgress` and then returning a builder from that `InProgress`. In the case of `store_entity_builder_with_entid` and `store_entity_builder_from_tempid`, the function goes a step further and calls `describe` or `describe_tempid` from the created `InProgressBuilder` and returning the `EntityBuilder` that results. These two functions are replicated on `InProgress`. This has been done to reduce the overhead of objects being passed over the FFI boundary.

The decision to do this enables us to go from something like

```
in_progress  = store_begin_transaction(store);
builder = in_progress_builder(in_progress);
entity_builder = in_progress_builder_describe(builder, entid);
```
to
```
entity_builder = store_entity_builder_from_entid(store);
```

There is an `add_*` and `retract_*` function specified for each `TypedValue` type for both `InProgressBuilder` and `EntityBuilder`.

To enable `transact` on `EntityBuilder` and `InProgressBuilder`, a new `repr(C)` struct has been created that contains a pointer to an `InProgress` and a pointer to a `Result<TxReport>` to allow passing the tuple result returned from `transact` on those types over the FFI.

Commit is possible from both builders and `InProgress`.
2018-05-14 18:56:42 +01:00
25 changed files with 4226 additions and 116 deletions

View file

@ -87,11 +87,12 @@ use std::vec;
pub use mentat::{
Binding,
CacheDirection,
Entid,
FindSpec,
HasSchema,
InProgress,
KnownEntid,
Keyword,
Queryable,
QueryBuilder,
QueryInputs,
@ -126,6 +127,7 @@ pub use utils::strings::{
pub use utils::log;
// type aliases for iterator types.
pub type BindingIterator = vec::IntoIter<Binding>;
pub type BindingListIterator = std::slice::Chunks<'static, mentat::Binding>;
@ -221,6 +223,13 @@ impl<T, E> From<Result<T, E>> for ExternResult where E: std::error::Error {
}
}
#[repr(C)]
#[derive(Debug)]
pub struct InProgressTransactResult<'a, 'c> {
pub in_progress: *mut InProgress<'a, 'c>,
pub result: *mut ExternResult,
}
/// A store cannot be opened twice to the same location.
/// Once created, the reference to the store is held by the caller and not Rust,
/// therefore the caller is responsible for calling `destroy` to release the memory
@ -247,17 +256,737 @@ pub extern "C" fn store_open(uri: *const c_char) -> *mut Store {
// TODO: begin_read
// TODO: begin_transaction
/// Starts a new transaction to allow multiple transacts to be
/// performed together. This is more efficient than performing
/// a large set of individual commits.
///
/// Returns as [Result<TxReport>](mentat::TxReport) as an [ExternResult](ExternResult).
///
/// # Safety
///
/// Callers must ensure that the pointer to the [Store](mentat::Store) is not dangling.
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `tx_report_destroy` is provided for releasing the memory for this
/// pointer type.
///
/// TODO: Document the errors that can result from begin_transaction
#[no_mangle]
pub unsafe extern "C" fn store_begin_transaction(store: *mut Store) -> *mut ExternResult {
let store = &mut*store;
Box::into_raw(Box::new(store.begin_transaction().into()))
}
/// Performs a single transaction against the store.
/// Perform a single transact operation using the current in progress
/// transaction. Takes edn as a string to transact.
///
/// Returns as [Result<TxReport>](mentat::TxReport) as an [ExternResult](ExternResult).
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `destroy` is provided for releasing the memory for this
/// A destructor `tx_report_destroy` is provided for releasing the memory for this
/// pointer type.
///
// TODO: Document the errors that can result from transact
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn in_progress_transact<'m>(in_progress: *mut InProgress<'m, 'm>, transaction: *const c_char) -> *mut ExternResult {
let in_progress = &mut*in_progress;
let transaction = c_char_to_string(transaction);
Box::into_raw(Box::new(in_progress.transact(transaction).into()))
}
/// Commit all the transacts that have been performed using this
/// in progress transaction.
///
/// Returns as [Result<()>](std::result::Result) as an [ExternResult](ExternResult).
///
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn in_progress_commit<'m>(in_progress: *mut InProgress<'m, 'm>) -> *mut ExternResult {
let in_progress = Box::from_raw(in_progress);
Box::into_raw(Box::new(in_progress.commit().into()))
}
/// Rolls back all the transacts that have been performed using this
/// in progress transaction.
///
/// Returns as [Result<()>](std::result::Result) as an [ExternResult](ExternResult).
///
/// TODO: Document the errors that can result from rollback
#[no_mangle]
pub unsafe extern "C" fn in_progress_rollback<'m>(in_progress: *mut InProgress<'m, 'm>) -> *mut ExternResult {
let in_progress = Box::from_raw(in_progress);
Box::into_raw(Box::new(in_progress.rollback().into()))
}
/// Creates a builder using the in progress transaction to allow for programmatic
/// assertion of values.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `in_progress_builder_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder<'m>(in_progress: *mut InProgress<'m, 'm>) -> *mut InProgressBuilder {
let in_progress = Box::from_raw(in_progress);
Box::into_raw(Box::new(in_progress.builder()))
}
/// Creates a builder for an entity with `tempid` using the in progress transaction to
/// allow for programmatic assertion of values for that entity.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `entity_builder_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn in_progress_entity_builder_from_temp_id<'m>(in_progress: *mut InProgress<'m, 'm>, temp_id: *const c_char) -> *mut EntityBuilder<InProgressBuilder> {
let in_progress = Box::from_raw(in_progress);
let temp_id = c_char_to_string(temp_id);
Box::into_raw(Box::new(in_progress.builder().describe_tempid(&temp_id)))
}
/// Creates a builder for an entity with `entid` using the in progress transaction to
/// allow for programmatic assertion of values for that entity.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `entity_builder_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn in_progress_entity_builder_from_entid<'m>(in_progress: *mut InProgress<'m, 'm>, entid: c_longlong) -> *mut EntityBuilder<InProgressBuilder> {
let in_progress = Box::from_raw(in_progress);
Box::into_raw(Box::new(in_progress.builder().describe(&KnownEntid(entid))))
}
/// Starts a new transaction and creates a builder using the that transaction
/// to allow for programmatic assertion of values.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `in_progress_builder_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn store_in_progress_builder(store: *mut Store) -> *mut ExternResult {
let store = &mut*store;
let result = store.begin_transaction().and_then(|in_progress| {
Ok(in_progress.builder())
});
Box::into_raw(Box::new(result.into()))
}
/// Starts a new transaction and creates a builder for an entity with `tempid`
/// using the that transaction to allow for programmatic assertion of values for that entity.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `entity_builder_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn store_entity_builder_from_temp_id(store: *mut Store, temp_id: *const c_char) -> *mut ExternResult {
let store = &mut*store;
let temp_id = c_char_to_string(temp_id);
let result = store.begin_transaction().and_then(|in_progress| {
Ok(in_progress.builder().describe_tempid(&temp_id))
});
Box::into_raw(Box::new(result.into()))
}
/// Starts a new transaction and creates a builder for an entity with `entid`
/// using the that transaction to allow for programmatic assertion of values for that entity.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// A destructor `entity_builder_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn store_entity_builder_from_entid(store: *mut Store, entid: c_longlong) -> *mut ExternResult {
let store = &mut*store;
let result = store.begin_transaction().and_then(|in_progress| {
Ok(in_progress.builder().describe(&KnownEntid(entid)))
});
Box::into_raw(Box::new(result.into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/string`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_string<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = c_char_to_string(value).into();
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/long`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_long<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Long(value);
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If `value` is not present as an Entid in the store.
/// If the type of `kw` is not `:db.type/ref`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_ref<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Ref(value);
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If `value` is not present as an attribute in the store.
/// If the type of `kw` is not `:db.type/keyword`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_keyword<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = kw_from_string(c_char_to_string(value)).into();
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/boolean`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_boolean<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: bool) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/double`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_double<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: f64) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/instant`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_timestamp<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::instant(value);
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/uuid`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_add_uuid<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: *mut [u8; 16]) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value = &*value;
let value = Uuid::from_bytes(value).expect("valid uuid");
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.add_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/string`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_string<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = c_char_to_string(value).into();
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/long`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_long<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Long(value);
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/ref`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_ref<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Ref(value);
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/keyword`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_keyword<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = kw_from_string(c_char_to_string(value)).into();
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/boolean`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_boolean<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: bool) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/double`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_double<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: f64) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/instant`.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_timestamp<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::instant(value);
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/uuid`.
///
/// TODO don't panic if the UUID is not valid - return result instead.
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_retract_uuid<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, entid: c_longlong, kw: *const c_char, value: *mut [u8; 16]) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value = &*value;
let value = Uuid::from_bytes(value).expect("valid uuid");
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.retract_kw(KnownEntid(entid), &kw, value).into()))
}
/// Transacts and commits all the assertions and retractions that have been performed
/// using this builder.
///
/// This consumes the builder and the enclosed [InProgress](mentat::InProgress) transaction.
///
/// Returns as [Result<()>(std::result::Result) as an [ExternResult](ExternResult).
///
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_commit<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>) -> *mut ExternResult {
let builder = Box::from_raw(builder);
Box::into_raw(Box::new(builder.commit().into()))
}
/// Transacts all the assertions and retractions that have been performed
/// using this builder.
///
/// This consumes the builder and returns the enclosed [InProgress](mentat::InProgress) transaction
/// inside the [InProgressTransactResult](mentat::InProgressTransactResult) alongside the [TxReport](mentat::TxReport) generated
/// by the transact.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// The destructors `in_progress_destroy` and `tx_report_destroy` arew provided for
/// releasing the memory for these pointer types.
///
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_transact<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>) -> *mut InProgressTransactResult<'a, 'c> {
let builder = Box::from_raw(builder);
let (in_progress, tx_report) = builder.transact();
let result = InProgressTransactResult { in_progress: Box::into_raw(Box::new(in_progress)), result: Box::into_raw(Box::new(tx_report.into())) };
Box::into_raw(Box::new(result))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/string`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_string<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = c_char_to_string(value).into();
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/long`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_long<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Long(value);
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/ref`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_ref<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Ref(value);
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/keyword`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_keyword<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = kw_from_string(c_char_to_string(value)).into();
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/boolean`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_boolean<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: bool) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/double`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_double<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: f64) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/instant`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_timestamp<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::instant(value);
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to assert `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/uuid`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_add_uuid<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: *mut [u8; 16]) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value = &*value;
let value = Uuid::from_bytes(value).expect("valid uuid");
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.add_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/string`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_string<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = c_char_to_string(value).into();
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/long`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_long<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Long(value);
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/ref`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_ref<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::Ref(value);
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/keyword`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_keyword<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: *const c_char) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = kw_from_string(c_char_to_string(value)).into();
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/boolean`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_boolean<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: bool) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/double`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_double<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: f64) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/instant`.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_timestamp<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: c_longlong) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value: TypedValue = TypedValue::instant(value);
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Uses `builder` to retract `value` for `kw` on entity `entid`.
///
/// # Errors
///
/// If `entid` is not present in the store.
/// If `kw` is not a valid attribute in the store.
/// If the type of `kw` is not `:db.type/uuid`.
///
/// TODO don't panic if the UUID is not valid - return result instead.
#[no_mangle]
pub unsafe extern "C" fn entity_builder_retract_uuid<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>, kw: *const c_char, value: *mut [u8; 16]) -> *mut ExternResult {
let builder = &mut*builder;
let kw = kw_from_string(c_char_to_string(kw));
let value = &*value;
let value = Uuid::from_bytes(value).expect("valid uuid");
let value: TypedValue = value.into();
Box::into_raw(Box::new(builder.retract_kw(&kw, value).into()))
}
/// Transacts all the assertions and retractions that have been performed
/// using this builder.
///
/// This consumes the builder and returns the enclosed [InProgress](mentat::InProgress) transaction
/// inside the [InProgressTransactResult][::InProgressTransactResult] alongside the [TxReport](mentat::TxReport) generated
/// by the transact.
///
/// # Safety
///
/// Callers are responsible for managing the memory for the return value.
/// The destructors `in_progress_destroy` and `tx_report_destroy` are provided for
/// releasing the memory for these pointer types.
///
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn entity_builder_transact<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>) -> *mut InProgressTransactResult<'a, 'c> {
let builder = Box::from_raw(builder);
let (in_progress, tx_report) = builder.transact();
let result = InProgressTransactResult { in_progress: Box::into_raw(Box::new(in_progress)), result: Box::into_raw(Box::new(tx_report.into())) };
Box::into_raw(Box::new(result))
}
/// Transacts and commits all the assertions and retractions that have been performed
/// using this builder.
///
/// This consumes the builder and the enclosed [InProgress](mentat::InProgress) transaction.
///
/// Returns as [Result](std::result::Result) as an [ExternResult](::ExternResult).
///
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn entity_builder_commit<'a, 'c>(builder: *mut EntityBuilder<InProgressBuilder<'a, 'c>>) -> *mut ExternResult {
let builder = Box::from_raw(builder);
Box::into_raw(Box::new(builder.commit().into()))
}
/// Performs a single transaction against the store.
///
/// Returns as [TxReport](mentat::TxReport) as an [ExternResult](::ExternResult).
/// TODO: Document the errors that can result from transact
#[no_mangle]
pub unsafe extern "C" fn store_transact(store: *mut Store, transaction: *const c_char) -> *mut ExternResult {
let store = &mut*store;
@ -271,7 +1000,7 @@ pub unsafe extern "C" fn store_transact(store: *mut Store, transaction: *const c
Box::into_raw(Box::new(result.into()))
}
/// Fetches the `tx_id` for the given [TxReport](mentat::TxReport).
/// Fetches the `tx_id` for the given [TxReport](mentat::TxReport)`.
#[no_mangle]
pub unsafe extern "C" fn tx_report_get_entid(tx_report: *mut TxReport) -> c_longlong {
let tx_report = &*tx_report;
@ -298,9 +1027,40 @@ pub unsafe extern "C" fn tx_report_entity_for_temp_id(tx_report: *mut TxReport,
}
}
// TODO: cache
/// Adds an attribute to the cache.
/// `store_cache_attribute_forward` caches values for an attribute keyed by entity
/// (i.e. find values and entities that have this attribute, or find values of attribute for an entity)
#[no_mangle]
pub extern "C" fn store_cache_attribute_forward(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
let store = unsafe { &mut *store };
let kw = kw_from_string(c_char_to_string(attribute));
Box::into_raw(Box::new(store.cache(&kw, CacheDirection::Forward).into()))
}
// TODO: q_once
/// Adds an attribute to the cache.
/// `store_cache_attribute_reverse` caches entities for an attribute keyed by value.
/// (i.e. find entities that have a particular value for an attribute).
#[no_mangle]
pub extern "C" fn store_cache_attribute_reverse(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
let store = unsafe { &mut *store };
let kw = kw_from_string(c_char_to_string(attribute));
Box::into_raw(Box::new(store.cache(&kw, CacheDirection::Reverse).into()))
}
/// Adds an attribute to the cache.
/// `store_cache_attribute_bi_directional` caches entity in both available directions, forward and reverse.
///
/// `Forward` caches values for an attribute keyed by entity
/// (i.e. find values and entities that have this attribute, or find values of attribute for an entity)
///
/// `Reverse` caches entities for an attribute keyed by value.
/// (i.e. find entities that have a particular value for an attribute).
#[no_mangle]
pub extern "C" fn store_cache_attribute_bi_directional(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
let store = unsafe { &mut *store };
let kw = kw_from_string(c_char_to_string(attribute));
Box::into_raw(Box::new(store.cache(&kw, CacheDirection::Both).into()))
}
/// Creates a [QueryBuilder](mentat::QueryBuilder) from the given store to execute the provided query.
///
@ -501,7 +1261,7 @@ fn unwrap_conversion<T>(value: Option<T>, expected_type: ValueType) -> T {
///
/// If the [ValueType](mentat::ValueType) of the [Binding](mentat::Binding) is not [ValueType::Long](mentat::ValueType::Long).
#[no_mangle]
pub unsafe extern "C" fn typed_value_into_long(typed_value: *mut Binding) -> c_longlong {
pub unsafe extern "C" fn typed_value_into_long(typed_value: *mut Binding) -> c_longlong {
let typed_value = Box::from_raw(typed_value);
unwrap_conversion(typed_value.into_long(), ValueType::Long)
}
@ -510,10 +1270,11 @@ pub unsafe extern "C" fn typed_value_into_long(typed_value: *mut Binding) -> c_l
///
/// # Panics
///
/// If the [ValueType](mentat::ValueType) of the [Binding](mentat::Binding) is not [ValueType::Long](mentat::ValueType::Ref).
/// If the [ValueType](mentat::ValueType) of the [Binding](mentat::Binding) is not [ValueType::Ref](mentat::ValueType::Ref).
#[no_mangle]
pub unsafe extern "C" fn typed_value_into_entid(typed_value: *mut Binding) -> Entid {
pub unsafe extern "C" fn typed_value_into_entid(typed_value: *mut Binding) -> Entid {
let typed_value = Box::from_raw(typed_value);
println!("typed value as entid {:?}", typed_value);
unwrap_conversion(typed_value.into_entid(), ValueType::Ref)
}
@ -521,9 +1282,9 @@ pub unsafe extern "C" fn typed_value_into_entid(typed_value: *mut Binding) -> En
///
/// # Panics
///
/// If the [ValueType](mentat::ValueType) of the [Binding](mentat::Binding) is not [ValueType::Long](mentat::ValueType::Ref).
/// If the [ValueType](mentat::ValueType) of the [Binding](mentat::Binding) is not [ValueType::Ref](mentat::ValueType::Ref).
#[no_mangle]
pub unsafe extern "C" fn typed_value_into_kw(typed_value: *mut Binding) -> *const c_char {
pub unsafe extern "C" fn typed_value_into_kw(typed_value: *mut Binding) -> *const c_char {
let typed_value = Box::from_raw(typed_value);
unwrap_conversion(typed_value.into_kw_c_string(), ValueType::Keyword) as *const c_char
}
@ -588,7 +1349,7 @@ pub unsafe extern "C" fn typed_value_into_uuid(typed_value: *mut Binding) -> *m
/// Returns the [ValueType](mentat::ValueType) of this [Binding](mentat::Binding).
#[no_mangle]
pub unsafe extern "C" fn typed_value_value_type(typed_value: *mut Binding) -> ValueType {
pub unsafe extern "C" fn typed_value_value_type(typed_value: *mut Binding) -> ValueType {
let typed_value = &*typed_value;
typed_value.value_type().unwrap_or_else(|| panic!("Binding is not Scalar and has no ValueType"))
}
@ -602,12 +1363,12 @@ pub unsafe extern "C" fn typed_value_value_type(typed_value: *mut Binding) -> Va
/// A destructor `typed_value_result_set_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn row_at_index(rows: *mut RelResult<Binding>, index: c_int) -> *mut Vec<Binding> {
pub unsafe extern "C" fn row_at_index(rows: *mut RelResult<Binding>, index: c_int) -> *mut Vec<Binding> {
let result = &*rows;
result.row(index as usize).map_or_else(std::ptr::null_mut, |v| Box::into_raw(Box::new(v.to_vec())))
}
/// Consumes the `Vec<Vec<Binding>>` and returns an iterator over the values.
/// Consumes the `RelResult<Binding>` and returns an iterator over the values.
///
/// # Safety
///
@ -615,7 +1376,7 @@ pub unsafe extern "C" fn row_at_index(rows: *mut RelResult<Binding>, index: c_in
/// A destructor `typed_value_result_set_iter_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn typed_value_result_set_into_iter(rows: *mut RelResult<Binding>) -> *mut BindingListIterator {
pub unsafe extern "C" fn typed_value_result_set_into_iter(rows: *mut RelResult<Binding>) -> *mut BindingListIterator {
let result = &*rows;
let rows = result.rows();
Box::into_raw(Box::new(rows))
@ -630,7 +1391,7 @@ pub unsafe extern "C" fn typed_value_result_set_into_iter(rows: *mut RelResult<B
/// A destructor `typed_value_list_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn typed_value_result_set_iter_next(iter: *mut BindingListIterator) -> *mut Vec<Binding> {
pub unsafe extern "C" fn typed_value_result_set_iter_next(iter: *mut BindingListIterator) -> *mut Vec<Binding> {
let iter = &mut *iter;
iter.next().map_or(std::ptr::null_mut(), |v| Box::into_raw(Box::new(v.to_vec())))
}
@ -643,7 +1404,7 @@ pub unsafe extern "C" fn typed_value_result_set_iter_next(iter: *mut BindingList
/// A destructor `typed_value_list_iter_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn typed_value_list_into_iter(values: *mut Vec<Binding>) -> *mut BindingIterator {
pub unsafe extern "C" fn typed_value_list_into_iter(values: *mut Vec<Binding>) -> *mut BindingIterator {
let result = Box::from_raw(values);
Box::into_raw(Box::new(result.into_iter()))
}
@ -657,7 +1418,7 @@ pub unsafe extern "C" fn typed_value_list_into_iter(values: *mut Vec<Binding>) -
/// A destructor `typed_value_destroy` is provided for releasing the memory for this
/// pointer type.
#[no_mangle]
pub unsafe extern "C" fn typed_value_list_iter_next(iter: *mut BindingIterator) -> *mut Binding {
pub unsafe extern "C" fn typed_value_list_iter_next(iter: *mut BindingIterator) -> *mut Binding {
let iter = &mut *iter;
iter.next().map_or(std::ptr::null_mut(), |v| Box::into_raw(Box::new(v)))
}
@ -905,6 +1666,18 @@ pub unsafe extern "C" fn changelist_entry_at(tx_report: *mut TransactionChange,
tx_report.changes[index].clone()
}
/// Destructor for releasing the memory of [InProgressBuilder](mentat::InProgressBuilder).
#[no_mangle]
pub unsafe extern "C" fn in_progress_builder_destroy<'a, 'c>(obj: *mut InProgressBuilder<'a, 'c>) {
let _ = Box::from_raw(obj);
}
/// Destructor for releasing the memory of [EntityBuilder](mentat::EntityBuilder).
#[no_mangle]
pub unsafe extern "C" fn entity_builder_destroy<'a, 'c>(obj: *mut EntityBuilder<InProgressBuilder<'a, 'c>>) {
let _ = Box::from_raw(obj);
}
/// destroy function for releasing the memory for `repr(C)` structs.
#[no_mangle]
pub unsafe extern "C" fn destroy(obj: *mut c_void) {
@ -933,12 +1706,17 @@ define_destructor!(tx_report_destroy, TxReport);
/// Destructor for releasing the memory of [Binding](mentat::Binding).
define_destructor!(typed_value_destroy, Binding);
/// Destructor for releasing the memory of [Vec<Binding>][mentat::Binding].
define_destructor!(typed_value_list_destroy, Vec<Binding>);
/// Destructor for releasing the memory of [BindingIterator](BindingIterator) .
define_destructor!(typed_value_list_iter_destroy, BindingIterator);
/// Destructor for releasing the memory of [RelResult<Binding>](mentat::RelResult).
define_destructor!(typed_value_result_set_destroy, RelResult<Binding>);
/// Destructor for releasing the memory of [BindingListIterator](BindingListIterator) .
/// Destructor for releasing the memory of [BindingListIterator](::BindingListIterator).
define_destructor!(typed_value_result_set_iter_destroy, BindingListIterator);
/// Destructor for releasing the memory of [InProgress](mentat::InProgress).
define_destructor!(in_progress_destroy, InProgress);

View file

@ -29,7 +29,7 @@ pub mod strings {
}
pub fn kw_from_string(keyword_string: &'static str) -> Keyword {
// TODO: validate. The input might not be a keyword!
// TODO: validate. The input might not be a keyword!
let attr_name = keyword_string.trim_left_matches(":");
let parts: Vec<&str> = attr_name.split("/").collect();
Keyword::namespaced(parts[0], parts[1])

View file

@ -14,6 +14,7 @@ import android.content.Context;
import android.content.res.AssetManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -22,7 +23,6 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@ -40,6 +40,33 @@ import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
public class FFIIntegrationTest {
class DBSetupResult {
TxReport schemaReport;
TxReport dataReport;
public DBSetupResult(TxReport schemaReport, TxReport dataReport) {
this.schemaReport = schemaReport;
this.dataReport = dataReport;
}
}
class QueryTimer {
private long startTime = 0;
private long endTime = 0;
public void start() {
this.startTime = System.nanoTime();
}
public void end() {
this.endTime = System.nanoTime();
}
public long duration() {
return this.endTime - this.startTime;
}
}
Mentat mentat = null;
@Test
@ -95,7 +122,8 @@ public class FFIIntegrationTest {
return this.mentat;
}
public TxReport populateWithTypesSchema(Mentat mentat) {
public DBSetupResult populateWithTypesSchema(Mentat mentat) {
InProgress transaction = mentat.beginTransaction();
String schema = "[\n" +
" [:db/add \"b\" :db/ident :foo/boolean]\n" +
" [:db/add \"b\" :db/valueType :db.type/boolean]\n" +
@ -122,7 +150,7 @@ public class FFIIntegrationTest {
" [:db/add \"u\" :db/valueType :db.type/uuid]\n" +
" [:db/add \"u\" :db/cardinality :db.cardinality/one]\n" +
" ]";
TxReport report = mentat.transact(schema);
TxReport report = transaction.transact(schema);
Long stringEntid = report.getEntidForTempId("s");
String data = "[\n" +
@ -135,13 +163,16 @@ public class FFIIntegrationTest {
" [:db/add \"a\" :foo/uuid #uuid \"550e8400-e29b-41d4-a716-446655440000\"]\n" +
" [:db/add \"b\" :foo/boolean false]\n" +
" [:db/add \"b\" :foo/ref "+ stringEntid +"]\n" +
" [:db/add \"b\" :foo/keyword :foo/string]\n" +
" [:db/add \"b\" :foo/long 50]\n" +
" [:db/add \"b\" :foo/instant #inst \"2018-01-01T11:00:00.000Z\"]\n" +
" [:db/add \"b\" :foo/double 22.46]\n" +
" [:db/add \"b\" :foo/string \"Silence is worse; all truths that are kept silent become poisonous.\"]\n" +
" [:db/add \"b\" :foo/uuid #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]\n" +
" ]";
return mentat.transact(data);
TxReport dataReport = transaction.transact(data);
transaction.commit();
return new DBSetupResult(report, dataReport);
}
@Test
@ -168,7 +199,7 @@ public class FFIIntegrationTest {
Mentat mentat = openAndInitializeCitiesStore();
String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindString("?name", "Wallingford").runScalar(new ScalarResultHandler() {
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -187,7 +218,7 @@ public class FFIIntegrationTest {
Mentat mentat = openAndInitializeCitiesStore();
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
final Expectation expectation = new Expectation();
mentat.query(query).runColl(new CollResultHandler() {
mentat.query(query).run(new CollResultHandler() {
@Override
public void handleList(CollResult list) {
assertNotNull(list);
@ -208,7 +239,7 @@ public class FFIIntegrationTest {
Mentat mentat = openAndInitializeCitiesStore();
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
final Expectation expectation = new Expectation();
mentat.query(query).runColl(new CollResultHandler() {
mentat.query(query).run(new CollResultHandler() {
@Override
public void handleList(CollResult list) {
assertNotNull(list);
@ -234,7 +265,7 @@ public class FFIIntegrationTest {
" [?c :community/type :community.type/website]\n" +
" [(fulltext $ :community/category \"food\") [[?c ?cat]]]]";
final Expectation expectation = new Expectation();
mentat.query(query).runTuple(new TupleResultHandler() {
mentat.query(query).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
@ -333,11 +364,11 @@ public class FFIIntegrationTest {
@Test
public void bindingLongValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindLong("?long", 25).runScalar(new ScalarResultHandler() {
mentat.query(query).bind("?long", 25).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -354,12 +385,12 @@ public class FFIIntegrationTest {
@Test
public void bindingRefValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
long stringEntid = mentat.entIdForAttribute(":foo/string");
final Long bEntid = report.getEntidForTempId("b");
String query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?ref", stringEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?ref", stringEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -376,12 +407,12 @@ public class FFIIntegrationTest {
@Test
public void bindingRefKwValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
String refKeyword = ":foo/string";
final Long bEntid = report.getEntidForTempId("b");
String query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindKeywordReference("?ref", refKeyword).runScalar(new ScalarResultHandler() {
mentat.query(query).bindKeywordReference("?ref", refKeyword).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -398,11 +429,11 @@ public class FFIIntegrationTest {
@Test
public void bindingKwValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindKeyword("?kw", ":foo/string").runScalar(new ScalarResultHandler() {
mentat.query(query).bindKeyword("?kw", ":foo/string").run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -419,13 +450,13 @@ public class FFIIntegrationTest {
@Test
public void bindingDateValueSucceeds() throws InterruptedException, ParseException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
Date date = new Date(1523896758000L);
String query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindDate("?now", date).runTuple(new TupleResultHandler() {
mentat.query(query).bind("?now", date).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
@ -446,7 +477,7 @@ public class FFIIntegrationTest {
Mentat mentat = this.openAndInitializeCitiesStore();
String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindString("?name", "Wallingford").runScalar(new ScalarResultHandler() {
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -463,12 +494,12 @@ public class FFIIntegrationTest {
@Test
public void bindingUuidValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]";
UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
final Expectation expectation = new Expectation();
mentat.query(query).bindUUID("?uuid", uuid).runScalar(new ScalarResultHandler() {
mentat.query(query).bind("?uuid", uuid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -485,11 +516,11 @@ public class FFIIntegrationTest {
@Test
public void bindingBooleanValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindBoolean("?bool", true).runScalar(new ScalarResultHandler() {
mentat.query(query).bind("?bool", true).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -507,11 +538,11 @@ public class FFIIntegrationTest {
@Test
public void bindingDoubleValueSucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindDouble("?double", 11.23).runScalar(new ScalarResultHandler() {
mentat.query(query).bind("?double", 11.23).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -528,11 +559,11 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToLong() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -550,11 +581,11 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToRef() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :where [?e :foo/long 25]]";
final Expectation expectation = new Expectation();
mentat.query(query).runScalar(new ScalarResultHandler() {
mentat.query(query).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -572,11 +603,11 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToKeyword() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -594,11 +625,11 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToBoolean() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -616,11 +647,11 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToDouble() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -638,14 +669,14 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToDate() throws InterruptedException, ParseException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]";
final Expectation expectation = new Expectation();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.ENGLISH);
format.parse("2017-01-01T11:00:00+00:00");
final Calendar expectedDate = format.getCalendar();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -663,11 +694,11 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToString() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -685,12 +716,12 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToUUID() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]";
final UUID expectedUUID = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() {
mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() {
@Override
public void handleValue(TypedValue value) {
assertNotNull(value);
@ -708,7 +739,7 @@ public class FFIIntegrationTest {
@Test
public void valueForAttributeOfEntitySucceeds() throws InterruptedException {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat);
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
assertNotNull(value);
@ -722,4 +753,569 @@ public class FFIIntegrationTest {
long entid = mentat.entIdForAttribute(":foo/long");
assertEquals(65540, entid);
}
@Test
public void testInProgressTransact() {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
assertNotNull(report);
}
@Test
public void testInProgressRollback() {
Mentat mentat = new Mentat();
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
assertNotNull(report);
long aEntid = report.getEntidForTempId("a");
TypedValue preLongValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
assertEquals(25, preLongValue.asLong().longValue());
InProgress inProgress = mentat.beginTransaction();
report = inProgress.transact("[[:db/add "+ aEntid +" :foo/long 22]]");
assertNotNull(report);
inProgress.rollback();
TypedValue postLongValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
assertEquals(25, postLongValue.asLong().longValue());
}
@Test
public void testInProgressEntityBuilder() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
final long stringEntid = reports.schemaReport.getEntidForTempId("s");
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation1 = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(false, row.asBool(0));
assertEquals(new Date(1514804400000l), row.asDate(1));
assertEquals(UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644"), row.asUUID(2));
assertEquals(50, row.asLong(3).longValue());
assertEquals(new Double(22.46), row.asDouble(4));
assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5));
assertEquals(":foo/string", row.asKeyword(6));
assertEquals(stringEntid, row.asEntid(7).longValue());
expectation1.fulfill();
}
});
synchronized (expectation1) {
expectation1.wait(1000);
}
assertTrue(expectation1.isFulfilled);
InProgressBuilder builder = mentat.entityBuilder();
builder.add(bEntid, ":foo/boolean", true);
final Date newDate = new Date(1524743301000l);
builder.add(bEntid, ":foo/instant", newDate);
final UUID newUUID = UUID.randomUUID();
builder.add(bEntid, ":foo/uuid", newUUID);
builder.add(bEntid, ":foo/long", 75);
builder.add(bEntid, ":foo/double", 81.3);
builder.add(bEntid, ":foo/string", "Become who you are!");
builder.addKeyword(bEntid, ":foo/keyword", ":foo/long");
builder.addRef(bEntid, ":foo/ref", longEntid);
builder.commit();
final Expectation expectation2 = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(true, row.asBool(0));
System.out.println(row.asDate(1).getTime());
assertEquals(newDate, row.asDate(1));
assertEquals(newUUID, row.asUUID(2));
assertEquals(75, row.asLong(3).longValue());
assertEquals(new Double(81.3), row.asDouble(4));
assertEquals("Become who you are!", row.asString(5));
assertEquals(":foo/long", row.asKeyword(6));
assertEquals(longEntid, row.asEntid(7).longValue());
expectation2.fulfill();
}
});
synchronized (expectation2) {
expectation2.wait(1000);
}
assertTrue(expectation2.isFulfilled);
}
@Test
public void testEntityBuilderForEntid() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
final long stringEntid = reports.schemaReport.getEntidForTempId("s");
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation1 = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(false, row.asBool(0));
assertEquals(new Date(1514804400000l), row.asDate(1));
assertEquals(UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644"), row.asUUID(2));
assertEquals(50, row.asLong(3).longValue());
assertEquals(new Double(22.46), row.asDouble(4));
assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5));
assertEquals(":foo/string", row.asKeyword(6));
assertEquals(stringEntid, row.asEntid(7).longValue());
expectation1.fulfill();
}
});
synchronized (expectation1) {
expectation1.wait(1000);
}
assertTrue(expectation1.isFulfilled);
EntityBuilder builder = mentat.entityBuilder(bEntid);
builder.add(":foo/boolean", true);
final Date newDate = new Date(1524743301000l);
builder.add(":foo/instant", newDate);
final UUID newUUID = UUID.randomUUID();
builder.add(":foo/uuid", newUUID);
builder.add(":foo/long", 75);
builder.add(":foo/double", 81.3);
builder.add(":foo/string", "Become who you are!");
builder.addKeyword(":foo/keyword", ":foo/long");
builder.addRef(":foo/ref", longEntid);
builder.commit();
final Expectation expectation2 = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(true, row.asBool(0));
System.out.println(row.asDate(1).getTime());
assertEquals(newDate, row.asDate(1));
assertEquals(newUUID, row.asUUID(2));
assertEquals(75, row.asLong(3).longValue());
assertEquals(new Double(81.3), row.asDouble(4));
assertEquals("Become who you are!", row.asString(5));
assertEquals(":foo/long", row.asKeyword(6));
assertEquals(longEntid, row.asEntid(7).longValue());
expectation2.fulfill();
}
});
synchronized (expectation2) {
expectation2.wait(1000);
}
assertTrue(expectation2.isFulfilled);
}
@Test
public void testEntityBuilderForTempid() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
final long longEntid = reports.schemaReport.getEntidForTempId("l");
EntityBuilder builder = mentat.entityBuilder("c");
builder.add(":foo/boolean", true);
final Date newDate = new Date(1524743301000l);
builder.add(":foo/instant", newDate);
final UUID newUUID = UUID.randomUUID();
builder.add(":foo/uuid", newUUID);
builder.add(":foo/long", 75);
builder.add(":foo/double", 81.3);
builder.add(":foo/string", "Become who you are!");
builder.addKeyword(":foo/keyword", ":foo/long");
builder.addRef(":foo/ref", longEntid);
TxReport report = builder.commit();
long cEntid = report.getEntidForTempId("c");
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", cEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(true, row.asBool(0));
System.out.println(row.asDate(1).getTime());
assertEquals(newDate, row.asDate(1));
assertEquals(newUUID, row.asUUID(2));
assertEquals(75, row.asLong(3).longValue());
assertEquals(new Double(81.3), row.asDouble(4));
assertEquals("Become who you are!", row.asString(5));
assertEquals(":foo/long", row.asKeyword(6));
assertEquals(longEntid, row.asEntid(7).longValue());
expectation.fulfill();
}
});
synchronized (expectation) {
expectation.wait(1000);
}
assertTrue(expectation.isFulfilled);
}
@Test
public void testInProgressBuilderTransact() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long aEntid = reports.dataReport.getEntidForTempId("a");
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
InProgressBuilder builder = mentat.entityBuilder();
builder.add(bEntid, ":foo/boolean", true);
final Date newDate = new Date(1524743301000l);
builder.add(bEntid, ":foo/instant", newDate);
final UUID newUUID = UUID.randomUUID();
builder.add(bEntid, ":foo/uuid", newUUID);
builder.add(bEntid, ":foo/long", 75);
builder.add(bEntid, ":foo/double", 81.3);
builder.add(bEntid, ":foo/string", "Become who you are!");
builder.addKeyword(bEntid, ":foo/keyword", ":foo/long");
builder.addRef(bEntid, ":foo/ref", longEntid);
InProgressTransactionResult result = builder.transact();
assertNotNull(result);
assertNotNull(result.getInProgress());
assertNotNull(result.getReport());
result.getInProgress().transact("[[:db/add "+ aEntid +" :foo/long 22]]");
result.getInProgress().commit();
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(true, row.asBool(0));
System.out.println(row.asDate(1).getTime());
assertEquals(newDate, row.asDate(1));
assertEquals(newUUID, row.asUUID(2));
assertEquals(75, row.asLong(3).longValue());
assertEquals(new Double(81.3), row.asDouble(4));
assertEquals("Become who you are!", row.asString(5));
assertEquals(":foo/long", row.asKeyword(6));
assertEquals(longEntid, row.asEntid(7).longValue());
expectation.fulfill();
}
});
synchronized (expectation) {
expectation.wait(1000);
}
assertTrue(expectation.isFulfilled);
TypedValue longValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
assertEquals(22, longValue.asLong().longValue());
}
@Test
public void testEntityBuilderTransact() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long aEntid = reports.dataReport.getEntidForTempId("a");
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
EntityBuilder builder = mentat.entityBuilder(bEntid);
builder.add(":foo/boolean", true);
final Date newDate = new Date(1524743301000l);
builder.add(":foo/instant", newDate);
final UUID newUUID = UUID.randomUUID();
builder.add(":foo/uuid", newUUID);
builder.add(":foo/long", 75);
builder.add(":foo/double", 81.3);
builder.add(":foo/string", "Become who you are!");
builder.addKeyword(":foo/keyword", ":foo/long");
builder.addRef(":foo/ref", longEntid);
InProgressTransactionResult result = builder.transact();
assertNotNull(result);
assertNotNull(result.getInProgress());
assertNotNull(result.getReport());
result.getInProgress().transact("[[:db/add "+ aEntid +" :foo/long 22]]");
result.getInProgress().commit();
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(true, row.asBool(0));
System.out.println(row.asDate(1).getTime());
assertEquals(newDate, row.asDate(1));
assertEquals(newUUID, row.asUUID(2));
assertEquals(75, row.asLong(3).longValue());
assertEquals(new Double(81.3), row.asDouble(4));
assertEquals("Become who you are!", row.asString(5));
assertEquals(":foo/long", row.asKeyword(6));
assertEquals(longEntid, row.asEntid(7).longValue());
expectation.fulfill();
}
});
synchronized (expectation) {
expectation.wait(1000);
}
assertTrue(expectation.isFulfilled);
TypedValue longValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
assertEquals(22, longValue.asLong().longValue());
}
@Test
public void testEntityBuilderRetract() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
final long stringEntid = reports.schemaReport.getEntidForTempId("s");
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation1 = new Expectation();
final Date previousDate = new Date(1514804400000l);
final UUID previousUuid = UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644");
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(false, row.asBool(0));
assertEquals(previousDate, row.asDate(1));
assertEquals(previousUuid, row.asUUID(2));
assertEquals(50, row.asLong(3).longValue());
assertEquals(new Double(22.46), row.asDouble(4));
assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5));
assertEquals(":foo/string", row.asKeyword(6));
assertEquals(stringEntid, row.asEntid(7).longValue());
expectation1.fulfill();
}
});
synchronized (expectation1) {
expectation1.wait(1000);
}
assertTrue(expectation1.isFulfilled);
EntityBuilder builder = mentat.entityBuilder(bEntid);
builder.retract(":foo/boolean", false);
builder.retract(":foo/instant", previousDate);
builder.retract(":foo/uuid", previousUuid);
builder.retract(":foo/long", 50);
builder.retract(":foo/double", 22.46);
builder.retract(":foo/string", "Silence is worse; all truths that are kept silent become poisonous.");
builder.retractKeyword(":foo/keyword", ":foo/string");
builder.retractRef(":foo/ref", stringEntid);
builder.commit();
final Expectation expectation2 = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNull(row);
expectation2.fulfill();
}
});
synchronized (expectation2) {
expectation2.wait(1000);
}
assertTrue(expectation2.isFulfilled);
}
@Test
public void testInProgressBuilderRetract() throws InterruptedException {
Mentat mentat = new Mentat();
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
final long stringEntid = reports.schemaReport.getEntidForTempId("s");
// test that the values are as expected
String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" +
" :in ?e\n" +
" :where [?e :foo/boolean ?b]\n" +
" [?e :foo/instant ?i]\n" +
" [?e :foo/uuid ?u]\n" +
" [?e :foo/long ?l]\n" +
" [?e :foo/double ?d]\n" +
" [?e :foo/string ?s]\n" +
" [?e :foo/keyword ?k]\n" +
" [?e :foo/ref ?r]]";
final Expectation expectation1 = new Expectation();
final Date previousDate = new Date(1514804400000l);
final UUID previousUuid = UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644");
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNotNull(row);
assertEquals(false, row.asBool(0));
assertEquals(previousDate, row.asDate(1));
assertEquals(previousUuid, row.asUUID(2));
assertEquals(50, row.asLong(3).longValue());
assertEquals(new Double(22.46), row.asDouble(4));
assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5));
assertEquals(":foo/string", row.asKeyword(6));
assertEquals(stringEntid, row.asEntid(7).longValue());
expectation1.fulfill();
}
});
synchronized (expectation1) {
expectation1.wait(1000);
}
assertTrue(expectation1.isFulfilled);
InProgressBuilder builder = mentat.entityBuilder();
builder.retract(bEntid, ":foo/boolean", false);
builder.retract(bEntid, ":foo/instant", previousDate);
builder.retract(bEntid, ":foo/uuid", previousUuid);
builder.retract(bEntid, ":foo/long", 50);
builder.retract(bEntid, ":foo/double", 22.46);
builder.retract(bEntid, ":foo/string", "Silence is worse; all truths that are kept silent become poisonous.");
builder.retractKeyword(bEntid, ":foo/keyword", ":foo/string");
builder.retractRef(bEntid, ":foo/ref", stringEntid);
builder.commit();
final Expectation expectation2 = new Expectation();
mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() {
@Override
public void handleRow(TupleResult row) {
assertNull(row);
expectation2.fulfill();
}
});
synchronized (expectation2) {
expectation2.wait(1000);
}
assertTrue(expectation2.isFulfilled);
}
@Test
public void testCaching() throws InterruptedException {
String query = "[:find ?district :where\n" +
" [?neighborhood :neighborhood/name \"Beacon Hill\"]\n" +
" [?neighborhood :neighborhood/district ?d]\n" +
" [?d :district/name ?district]]";
Mentat mentat = openAndInitializeCitiesStore();
final Expectation expectation1 = new Expectation();
final QueryTimer uncachedTimer = new QueryTimer();
uncachedTimer.start();
mentat.query(query).run(new RelResultHandler() {
@Override
public void handleRows(RelResult rows) {
uncachedTimer.end();
assertNotNull(rows);
expectation1.fulfill();
}
});
synchronized (expectation1) {
expectation1.wait(1000);
}
assertTrue(expectation1.isFulfilled);
mentat.cache(":neighborhood/name", CacheDirection.REVERSE);
mentat.cache(":neighborhood/district", CacheDirection.FORWARD);
final Expectation expectation2 = new Expectation();
final QueryTimer cachedTimer = new QueryTimer();
cachedTimer.start();
mentat.query(query).run(new RelResultHandler() {
@Override
public void handleRows(RelResult rows) {
cachedTimer.end();
assertNotNull(rows);
expectation2.fulfill();
}
});
synchronized (expectation2) {
expectation2.wait(1000);
}
assertTrue(expectation2.isFulfilled);
long timingDifference = uncachedTimer.duration() - cachedTimer.duration();
Log.d("testCaching", "Cached query is "+ timingDifference +" nanoseconds faster than the uncached query");
assert cachedTimer.duration() < uncachedTimer.duration();
}
}

View file

@ -0,0 +1,15 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* Copyright 2018 Mozilla
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
package com.mozilla.mentat;
public enum CacheDirection {
FORWARD, REVERSE, BOTH
}

View file

@ -0,0 +1,357 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* Copyright 2018 Mozilla
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
package com.mozilla.mentat;
import android.util.Log;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.UUID;
/**
* This class wraps a raw pointer that points to a Rust `EntityBuilder<InProgressBuilder>` object.
*
* {@link EntityBuilder} provides a programmatic interface to performing assertions on a specific entity.
* It provides functions for adding and retracting values for attributes for an entity within
* an in progress transaction.
* <p/>
* The `transact` function will transact the assertions that have been added to the {@link EntityBuilder}
* and pass back the {@link TxReport} that was generated by this transact and the {@link InProgress} that was
* used to perform the transact. This enables you to perform further transacts on the same {@link InProgress}
* before committing.
* <p/>
* <pre>{@code
* long aEntid = txReport.getEntidForTempId("a");
* long bEntid = txReport.getEntidForTempId("b");
* EntityBuilder builder = mentat.getEntityBuilderForEntid(bEntid);
* builder.add(":foo/boolean", true);
* builder.add(":foo/instant", newDate);
* InProgress inProgress = builder.transact().getInProgress();
* inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
* inProgress.commit();
* }</pre>
* <p/>
* The `commit` function will transact and commit the assertions that have been added to the {@link EntityBuilder}.
* It will consume the {@link InProgress} used to perform the transact. It returns the {@link TxReport} generated by
* the transact. After calling `commit`, a new transaction must be started by calling <pre>Mentat.beginTransaction()</pre>
* in order to perform further actions.
* <p/>
* <pre>{@code
* long aEntid = txReport.getEntidForTempId("a");
* EntityBuilder builder = mentat.getEntityBuilderForEntid(bEntid);
* builder.add(":foo/boolean", true);
* builder.add(":foo/instant", newDate);
* builder.commit();
* }</pre>
*/
public class EntityBuilder extends RustObject {
public EntityBuilder(Pointer pointer) {
this.rawPointer = pointer;
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_long(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void addRef(String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_ref(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void addKeyword(String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_keyword(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(String keyword, boolean value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_boolean(this.rawPointer, keyword, value ? 1 : 0);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(String keyword, double value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_double(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(String keyword, Date value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_timestamp(this.rawPointer, keyword, value.getTime() * 1_000);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_string(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(String keyword, UUID value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_add_uuid(this.rawPointer, keyword, getPointerForUUID(value));
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_long(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retractRef(String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_ref(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retractKeyword(String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_keyword(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(String keyword, boolean value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_boolean(this.rawPointer, keyword, value ? 1 : 0);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(String keyword, double value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_double(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(String keyword, Date value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_timestamp(this.rawPointer, keyword, value.getTime() * 1_000);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_string(this.rawPointer, keyword, value);
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(String keyword, UUID value) {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_retract_uuid(this.rawPointer, keyword, this.getPointerForUUID(value));
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
}
}
/**
* Transacts the added assertions. This consumes the pointer associated with this {@link EntityBuilder}
* such that no further assertions can be added after the `transact` has completed. To perform
* further assertions, use the {@link InProgress} inside the {@link InProgressTransactionResult}
* returned from this function.
* <p/>
* This does not commit the transaction. In order to do so, `commit` can be called on the
* {@link InProgress} inside the {@link InProgressTransactionResult} returned from this function.
*
* TODO throw exception if error occurs
*
* @return A {@link InProgressTransactionResult} containing the current {@link InProgress} and
* the {@link TxReport} generated by the transact.
*/
public InProgressTransactionResult transact() {
this.validate();
InProgressTransactionResult result = JNA.INSTANCE.entity_builder_transact(this.rawPointer);
this.rawPointer = null;
return result;
}
/**
* Transacts the added assertions and commits. This consumes the pointer associated with this
* {@link EntityBuilder} and the associated {@link InProgress} such that no further assertions
* can be added after the `commit` has completed.
* <p/>
* To perform further assertions, a new `{@link InProgress} or {@link EntityBuilder} should be
* created.
*
* TODO throw exception if error occurs
*
* @return
*/
public TxReport commit() {
this.validate();
RustResult result = JNA.INSTANCE.entity_builder_commit(this.rawPointer);
this.rawPointer = null;
if (result.isFailure()) {
Log.e("EntityBuilder", result.err);
return null;
}
return new TxReport(result.ok);
}
@Override
public void close() throws IOException {
if (this.rawPointer != null) {
JNA.INSTANCE.entity_builder_destroy(this.rawPointer);
}
}
}

View file

@ -0,0 +1,195 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* Copyright 2018 Mozilla
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
package com.mozilla.mentat;
import android.util.Log;
import com.sun.jna.Pointer;
import java.io.IOException;
/**
* This class wraps a raw pointer that points to a Rust {@link InProgress} object.
* </p>
* {@link InProgress} allows for multiple transacts to be performed in a single transaction.
* Each transact performed results in a {@link TxReport} that can be used to gather information
* to be used in subsequent transacts.
* </p>
* Committing an {@link InProgress} commits all the transacts that have been performed using
* that {@link InProgress}.
* </p>
* Rolling back and {@link InProgress} rolls back all the transacts that have been performed
* using that {@link InProgress}.
* </p>
* <pre>{@code
* do {
* let inProgress = try mentat.beginTransaction()
* let txReport = try inProgress.transact(transaction: "[[:db/add "a" :foo/long 22]]")
* let aEntid = txReport.entid(forTempId: "a")
* let report = try inProgress.transact(transaction: "[[:db/add "b" :foo/ref \(aEntid)] [:db/add "b" :foo/boolean true]]")
* try inProgress.commit()
* } catch {
* ...
* }
* }</pre>
* </p>
* {@link InProgress} also provides a number of functions to generating an builder to assert datoms
* programatically. The two types of builder are {@link InProgressBuilder} and {@link EntityBuilder}.
* </p>
* {@link InProgressBuilder} takes the current {@link InProgress} and provides a programmatic
* interface to add and retract values from entities for which there exists an `Entid`. The provided
* {@link InProgress} is used to perform the transacts.
* </p>
* <pre>{@code
* long aEntid = txReport.getEntidForTempId("a");
* long bEntid = txReport.getEntidForTempId("b");
* InProgress inProgress = mentat.beginTransaction();
* InProgressBuilder builder = inProgress.builder();
* builder.add(bEntid, ":foo/boolean", true);
* builder.add(aEntid, ":foo/instant", newDate);
* inProgress = builder.transact().getInProgress();
* inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
* inProgress.commit();
* }
* }</pre>
* </p>
* {@link EntityBuilder} takes the current {@link InProgress} and either an `Entid` or a `tempid` to
* provide a programmatic interface to add and retract values from a specific entity. The provided
* {@link InProgress} is used to perform the transacts.
* </p>
* <pre>{@code
* long aEntid = txReport.getEntidForTempId("a");
* long bEntid = txReport.getEntidForTempId("b");
* InProgress inProgress = mentat.beginTransaction();
* EntityBuilder builder = inProgress.builderForEntid(bEntid);
* builder.add(":foo/boolean", true);
* builder.add(":foo/instant", newDate);
* inProgress = builder.transact().getInProgress();
* inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
* inProgress.commit();
* }</pre>
*/
public class InProgress extends RustObject {
public InProgress(Pointer pointer) {
this.rawPointer = pointer;
}
/**
* Creates an {@link InProgressBuilder} using this {@link InProgress} .
*
* @return an {@link InProgressBuilder} for this {@link InProgress}
*/
public InProgressBuilder builder() {
this.validate();
InProgressBuilder builder = new InProgressBuilder(JNA.INSTANCE.in_progress_builder(this.rawPointer));
this.rawPointer = null;
return builder;
}
/**
* Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`.
*
*
* @param entid The `Entid` for this entity.
* @return an `EntityBuilder` for this `InProgress`
*/
public EntityBuilder builderForEntid(long entid){
this.validate();
EntityBuilder builder = new EntityBuilder(JNA.INSTANCE.in_progress_entity_builder_from_entid(this.rawPointer, entid));
this.rawPointer = null;
return builder;
}
/**
* Creates an `EntityBuilder` using this `InProgress` for a new entity with `tempid`.
*
*
* @param tempid The temporary identifier for this entity.
* @return an `EntityBuilder` for this `InProgress`
*/
public EntityBuilder builderForTempid(String tempid){
this.validate();
EntityBuilder builder = new EntityBuilder(JNA.INSTANCE.in_progress_entity_builder_from_temp_id(this.rawPointer, tempid));
this.rawPointer = null;
return builder;
}
/**
Transacts the `transaction`
This does not commit the transaction. In order to do so, `commit` can be called.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the transaction failed.
- Throws: `ResultError.empty` if no `TxReport` is returned from the transact.
- Returns: The `TxReport` generated by the transact.
*/
/**
* Transacts the `transaction`
*
* This does not commit the transaction. In order to do so, `commit` can be called.
*
* TODO throw Exception on result failure.
*
* @param transaction The EDN string to be transacted.
* @return The `TxReport` generated by the transact.
*/
public TxReport transact(String transaction) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_transact(this.rawPointer, transaction);
if (result.isFailure()) {
Log.e("InProgress", result.err);
return null;
}
return new TxReport(result.ok);
}
/**
* Commits all the transacts that have been performed on this `InProgress`, either directly
* or through a Builder. This consumes the pointer associated with this {@link InProgress} such
* that no further assertions can be performed after the `commit` has completed.
*
* TODO throw exception if error occurs
*/
public void commit() {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_commit(this.rawPointer);
this.rawPointer = null;
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Rolls back all the transacts that have been performed on this `InProgress`, either directly
* or through a Builder.
*
* TODO throw exception if error occurs
*/
public void rollback() {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_rollback(this.rawPointer);
this.rawPointer = null;
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
@Override
public void close() throws IOException {
if (this.rawPointer != null) {
JNA.INSTANCE.in_progress_destroy(this.rawPointer);
}
}
}

View file

@ -0,0 +1,382 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* Copyright 2018 Mozilla
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
package com.mozilla.mentat;
import android.util.Log;
import com.sun.jna.Pointer;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
/**
* This class wraps a raw pointer that points to a Rust `InProgressBuilder` object.
*
* {@link InProgressBuilder} provides a programmatic interface to performing assertions for entities.
* It provides functions for adding and retracting values for attributes for an entity within
* an in progress transaction.
* <p/>
* The `transact` function will transact the assertions that have been added to the {@link InProgressBuilder}
* and pass back the {@link TxReport} that was generated by this transact and the {@link InProgress} that was
* used to perform the transact. This enables you to perform further transacts on the same {@link InProgress}
* before committing.
* <p/>
* <pre>{@code
* long aEntid = txReport.getEntidForTempId("a");
* long bEntid = txReport.getEntidForTempId("b");
* InProgressBuilder builder = mentat.getEntityBuilder();
* builder.add(aEntid, ":foo/boolean", true);
* builder.add(bEntid, ":foo/instant", newDate);
* InProgress inProgress = builder.transact().getInProgress();
* inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
* inProgress.commit();
* }</pre>
* <p/>
* The `commit` function will transact and commit the assertions that have been added to the
* {@link InProgressBuilder}.
* It will consume the {@link InProgress} used to perform the transact. It returns the {@link TxReport}
* generated by the transact. After calling `commit`, a new transaction must be started by calling
* <pre>Mentat.beginTransaction()</pre> in order to perform further actions.
* <p/>
* <pre>{@code
* long aEntid = txReport.getEntidForTempId("a");
* long bEntid = txReport.getEntidForTempId("b");
* InProgressBuilder builder = mentat.getEntityBuilder();
* builder.add(aEntid, ":foo/boolean", true);
* builder.add(bEntid, ":foo/instant", newDate);
* builder.commit();
* }</pre>
*/
public class InProgressBuilder extends RustObject {
public InProgressBuilder(Pointer pointer) {
this.rawPointer = pointer;
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
*
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(long entid, String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_long(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void addRef(long entid, String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_ref(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void addKeyword(long entid, String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_keyword(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(long entid, String keyword, boolean value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_boolean(this.rawPointer, entid, keyword, value ? 1 : 0);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(long entid, String keyword, double value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_double(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(long entid, String keyword, Date value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_timestamp(this.rawPointer, entid, keyword, value.getTime() * 1_000);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(long entid, String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_string(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Asserts the value of attribute `keyword` to be the provided `value`.
* // TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be asserted
*/
public void add(long entid, String keyword, UUID value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_add_uuid(this.rawPointer, entid, keyword, getPointerForUUID(value));
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(long entid, String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_long(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retractRef(long entid, String keyword, long value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_ref(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retractKeyword(long entid, String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_keyword(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(long entid, String keyword, boolean value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_boolean(this.rawPointer, entid, keyword, value ? 1 : 0);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(long entid, String keyword, double value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_double(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(long entid, String keyword, Date value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_timestamp(this.rawPointer, entid, keyword, value.getTime() * 1_000);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(long entid, String keyword, String value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_string(this.rawPointer, entid, keyword, value);
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Retracts the value of attribute `keyword` from the provided `value`.
*
* TODO throw exception if error occurs
*
* @param entid The `Entid` of the entity to be touched.
* @param keyword The name of the attribute in the format `:namespace/name`.
* @param value The value to be retracted
*/
public void retract(long entid, String keyword, UUID value) {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_retract_uuid(this.rawPointer, entid, keyword, this.getPointerForUUID(value));
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
}
}
/**
* Transacts the added assertions. This consumes the pointer associated with this {@link InProgressBuilder}
* such that no further assertions can be added after the `transact` has completed. To perform
* further assertions, use the {@link InProgress} inside the {@link InProgressTransactionResult}
* returned from this function.
* <p/>
* This does not commit the transaction. In order to do so, `commit` can be called on the
* {@link InProgress} inside the {@link InProgressTransactionResult} returned from this function.
*
* TODO throw exception if error occurs
*
* @return A {@link InProgressTransactionResult} containing the current {@link InProgress} and
* the {@link TxReport} generated by the transact.
*/
public InProgressTransactionResult transact() {
this.validate();
InProgressTransactionResult result = JNA.INSTANCE.in_progress_builder_transact(this.rawPointer);
this.rawPointer = null;
return result;
}
/**
* Transacts the added assertions and commits. This consumes the pointer associated with this
* {@link InProgressBuilder} and the associated {@link InProgress} such that no further assertions
* can be added after the `commit` has completed.
* <p/>
* To perform further assertions, a new `{@link InProgress} or {@link InProgressBuilder} should be
* created.
*
* TODO throw exception if error occurs
*
* @return
*/
public TxReport commit() {
this.validate();
RustResult result = JNA.INSTANCE.in_progress_builder_commit(this.rawPointer);
this.rawPointer = null;
if (result.isFailure()) {
Log.e("InProgressBuilder", result.err);
return null;
}
return new TxReport(result.ok);
}
@Override
public void close() throws IOException {
if (this.rawPointer != null) {
JNA.INSTANCE.in_progress_builder_destroy(this.rawPointer);
}
}
}

View file

@ -0,0 +1,56 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* Copyright 2018 Mozilla
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
package com.mozilla.mentat;
import android.util.Log;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class InProgressTransactionResult extends Structure implements Closeable {
public static class ByReference extends InProgressTransactionResult implements Structure.ByReference {
}
public static class ByValue extends InProgressTransactionResult implements Structure.ByValue {
}
public Pointer inProgress;
public RustResult.ByReference result;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("inProgress", "result");
}
public InProgress getInProgress() {
return new InProgress(this.inProgress);
}
public TxReport getReport() {
if (this.result.isFailure()) {
Log.e("InProgressTransactionResult", this.result.err);
return null;
}
return new TxReport(this.result.ok);
}
@Override
public void close() throws IOException {
if (this.getPointer() != null) {
JNA.INSTANCE.destroy(this.getPointer());
}
}
}

View file

@ -37,12 +37,72 @@ public interface JNA extends Library {
void typed_value_result_set_destroy(Pointer obj);
void typed_value_result_set_iter_destroy(Pointer obj);
void tx_report_destroy(Pointer obj);
void in_progress_destroy(Pointer obj);
void in_progress_builder_destroy(Pointer obj);
void entity_builder_destroy(Pointer obj);
// caching
RustResult store_cache_attribute_forward(Pointer store, String attribute);
RustResult store_cache_attribute_reverse(Pointer store, String attribute);
RustResult store_cache_attribute_bi_directional(Pointer store, String attribute);
// transact
RustResult store_transact(Pointer store, String transaction);
Pointer tx_report_entity_for_temp_id(Pointer report, String tempid);
long tx_report_get_entid(Pointer report);
long tx_report_get_tx_instant(Pointer report);
RustResult store_begin_transaction(Pointer store);
// in progress
RustResult in_progress_transact(Pointer in_progress, String transaction);
RustResult in_progress_commit(Pointer in_progress);
RustResult in_progress_rollback(Pointer in_progress);
Pointer in_progress_builder(Pointer in_progress);
Pointer in_progress_entity_builder_from_temp_id(Pointer in_progress, String temp_id);
Pointer in_progress_entity_builder_from_entid(Pointer in_progress, long entid);
// in_progress entity building
RustResult store_in_progress_builder(Pointer store);
RustResult in_progress_builder_add_string(Pointer builder, long entid, String kw, String value);
RustResult in_progress_builder_add_long(Pointer builder, long entid, String kw, long value);
RustResult in_progress_builder_add_ref(Pointer builder, long entid, String kw, long value);
RustResult in_progress_builder_add_keyword(Pointer builder, long entid, String kw, String value);
RustResult in_progress_builder_add_timestamp(Pointer builder, long entid, String kw, long value);
RustResult in_progress_builder_add_boolean(Pointer builder, long entid, String kw, int value);
RustResult in_progress_builder_add_double(Pointer builder, long entid, String kw, double value);
RustResult in_progress_builder_add_uuid(Pointer builder, long entid, String kw, Pointer value);
RustResult in_progress_builder_retract_string(Pointer builder, long entid, String kw, String value);
RustResult in_progress_builder_retract_long(Pointer builder, long entid, String kw, long value);
RustResult in_progress_builder_retract_ref(Pointer builder, long entid, String kw, long value);
RustResult in_progress_builder_retract_keyword(Pointer builder, long entid, String kw, String value);
RustResult in_progress_builder_retract_timestamp(Pointer builder, long entid, String kw, long value);
RustResult in_progress_builder_retract_boolean(Pointer builder, long entid, String kw, int value);
RustResult in_progress_builder_retract_double(Pointer builder, long entid, String kw, double value);
RustResult in_progress_builder_retract_uuid(Pointer builder, long entid, String kw, Pointer value);
InProgressTransactionResult in_progress_builder_transact(Pointer builder);
RustResult in_progress_builder_commit(Pointer builder);
// entity building
RustResult store_entity_builder_from_temp_id(Pointer store, String temp_id);
RustResult store_entity_builder_from_entid(Pointer store, long entid);
RustResult entity_builder_add_string(Pointer builder, String kw, String value);
RustResult entity_builder_add_long(Pointer builder, String kw, long value);
RustResult entity_builder_add_ref(Pointer builder, String kw, long value);
RustResult entity_builder_add_keyword(Pointer builder, String kw, String value);
RustResult entity_builder_add_boolean(Pointer builder, String kw, int value);
RustResult entity_builder_add_double(Pointer builder, String kw, double value);
RustResult entity_builder_add_timestamp(Pointer builder, String kw, long value);
RustResult entity_builder_add_uuid(Pointer builder, String kw, Pointer value);
RustResult entity_builder_retract_string(Pointer builder, String kw, String value);
RustResult entity_builder_retract_long(Pointer builder, String kw, long value);
RustResult entity_builder_retract_ref(Pointer builder, String kw, long value);
RustResult entity_builder_retract_keyword(Pointer builder, String kw, String value);
RustResult entity_builder_retract_boolean(Pointer builder, String kw, int value);
RustResult entity_builder_retract_double(Pointer builder, String kw, double value);
RustResult entity_builder_retract_timestamp(Pointer builder, String kw, long value);
RustResult entity_builder_retract_uuid(Pointer builder, String kw, Pointer value);
InProgressTransactionResult entity_builder_transact(Pointer builder);
RustResult entity_builder_commit(Pointer builder);
// sync
RustResult store_sync(Pointer store, String userUuid, String serverUri);
@ -95,7 +155,7 @@ public interface JNA extends Library {
String value_at_index_into_kw(Pointer rows, int index);
String value_at_index_into_string(Pointer rows, int index);
Pointer value_at_index_into_uuid(Pointer rows, int index);
long value_at_index_into_boolean(Pointer rows, int index);
int value_at_index_into_boolean(Pointer rows, int index);
double value_at_index_into_double(Pointer rows, int index);
long value_at_index_into_timestamp(Pointer rows, int index);
}

View file

@ -48,9 +48,38 @@ public class Mentat extends RustObject {
*/
public Mentat(Pointer rawPointer) { this.rawPointer = rawPointer; }
/**
* Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be
* looked up.
*
* TODO: Throw an exception if cache action fails. https://github.com/mozilla/mentat/issues/700
*
* @param attribute The attribute to cache
* @param direction The direction the attribute should be keyed.
* FORWARD caches values for an attribute keyed by entity
* (i.e. find values and entities that have this attribute, or find values of attribute for an entity)
* REVERSE caches entities for an attribute keyed by value.
* (i.e. find entities that have a particular value for an attribute).
* BOTH adds an attribute such that it is cached in both directions.
*/
public void cache(String attribute, CacheDirection direction) {
RustResult result = null;
switch (direction) {
case FORWARD:
result = JNA.INSTANCE.store_cache_attribute_forward(this.rawPointer, attribute);
case REVERSE:
result = JNA.INSTANCE.store_cache_attribute_reverse(this.rawPointer, attribute);
case BOTH:
result = JNA.INSTANCE.store_cache_attribute_bi_directional(this.rawPointer, attribute);
}
if (result.isFailure()) {
Log.e("Mentat", result.err);
}
}
/**
* Simple transact of an EDN string.
* TODO: Throw an exception if the transact fails
* TODO: Throw an exception if the transact fails. https://github.com/mozilla/mentat/issues/700
* @param transaction The string, as EDN, to be transacted.
* @return The {@link TxReport} of the completed transaction
*/
@ -88,7 +117,7 @@ public class Mentat extends RustObject {
/**
* Retrieve a single value of an attribute for an Entity
* TODO: Throw an exception if there is no the result contains an error.
* TODO: Throw an exception if the result contains an error. https://github.com/mozilla/mentat/issues/700
* @param attribute The string the attribute whose value is to be returned. The string is represented as `:namespace/name`.
* @param entid The `Entid` of the entity we want the value from.
* @return The {@link TypedValue} containing the value of the attribute for the entity.
@ -138,6 +167,92 @@ public class Mentat extends RustObject {
JNA.INSTANCE.store_unregister_observer(rawPointer, key);
}
/**
* Start a new transaction
*
* TODO: Throw an exception if the result contains an error. https://github.com/mozilla/mentat/issues/700
*
* @return The {@link InProgress} used to manage the transaction
*/
public InProgress beginTransaction() {
RustResult result = JNA.INSTANCE.store_begin_transaction(this.rawPointer);
if (result.isSuccess()) {
return new InProgress(result.ok);
}
if (result.isFailure()) {
Log.i("Mentat", result.err);
}
return null;
}
/**
* Creates a new transaction ({@link InProgress}) and returns an {@link InProgressBuilder} for
* that transaction.
*
* TODO: Throw an exception if the result contains an error. https://github.com/mozilla/mentat/issues/700
*
* @return an {@link InProgressBuilder} for a new transaction.
*/
public InProgressBuilder entityBuilder() {
RustResult result = JNA.INSTANCE.store_in_progress_builder(this.rawPointer);
if (result.isSuccess()) {
return new InProgressBuilder(result.ok);
}
if (result.isFailure()) {
Log.i("Mentat", result.err);
}
return null;
}
/**
* Creates a new transaction ({@link InProgress}) and returns an {@link EntityBuilder} for the
* entity with `entid` for that transaction.
*
* TODO: Throw an exception if the result contains an error. https://github.com/mozilla/mentat/issues/700
*
* @param entid The `Entid` for this entity.
* @return an {@link EntityBuilder} for a new transaction.
*/
public EntityBuilder entityBuilder(long entid) {
RustResult result = JNA.INSTANCE.store_entity_builder_from_entid(this.rawPointer, entid);
if (result.isSuccess()) {
return new EntityBuilder(result.ok);
}
if (result.isFailure()) {
Log.i("Mentat", result.err);
}
return null;
}
/**
* Creates a new transaction ({@link InProgress}) and returns an {@link EntityBuilder} for a new
* entity with `tempId` for that transaction.
*
* TODO: Throw an exception if the result contains an error. https://github.com/mozilla/mentat/issues/700
*
* @param tempId The temporary identifier for this entity.
* @return an {@link EntityBuilder} for a new transaction.
*/
public EntityBuilder entityBuilder(String tempId) {
RustResult result = JNA.INSTANCE.store_entity_builder_from_temp_id(this.rawPointer, tempId);
if (result.isSuccess()) {
return new EntityBuilder(result.ok);
}
if (result.isFailure()) {
Log.i("Mentat", result.err);
}
return null;
}
@Override
public void close() {
if (this.rawPointer != null) {

View file

@ -76,7 +76,7 @@ import java.util.UUID;
* <pre>{@code
* String query = "[: find ?a .\n" +
* " : where ... ]";
* mentat.query(query).runScalar(new ScalarResultHandler() {
* mentat.query(query).run(new ScalarResultHandler() {
* @Override
* public void handleValue(TypedValue value) {
* ...
@ -88,7 +88,7 @@ import java.util.UUID;
* <pre>{@code
* String query = "[: find [?a ...]\n" +
* " : where ... ]";
* mentat.query(query).runColl(new ScalarResultHandler() {
* mentat.query(query).run(new ScalarResultHandler() {
* @Override
* public void handleList(CollResult list) {
* ...
@ -100,7 +100,7 @@ import java.util.UUID;
* <pre>{@code
* String query = "[: find [?a ?b ?c]\n" +
* " : where ... ]";
* mentat.query(query).runTuple(new TupleResultHandler() {
* mentat.query(query).run(new TupleResultHandler() {
* @Override
* public void handleRow(TupleResult row) {
* ...
@ -121,7 +121,7 @@ public class Query extends RustObject {
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindLong(String varName, long value) {
Query bind(String varName, long value) {
this.validate();
JNA.INSTANCE.query_builder_bind_long(this.rawPointer, varName, value);
return this;
@ -173,7 +173,7 @@ public class Query extends RustObject {
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindBoolean(String varName, boolean value) {
Query bind(String varName, boolean value) {
this.validate();
JNA.INSTANCE.query_builder_bind_boolean(this.rawPointer, varName, value ? 1 : 0);
return this;
@ -186,7 +186,7 @@ public class Query extends RustObject {
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindDouble(String varName, double value) {
Query bind(String varName, double value) {
this.validate();
JNA.INSTANCE.query_builder_bind_double(this.rawPointer, varName, value);
return this;
@ -199,7 +199,7 @@ public class Query extends RustObject {
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindDate(String varName, Date value) {
Query bind(String varName, Date value) {
this.validate();
long timestamp = value.getTime() * 1000;
JNA.INSTANCE.query_builder_bind_timestamp(this.rawPointer, varName, timestamp);
@ -213,7 +213,7 @@ public class Query extends RustObject {
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindString(String varName, String value) {
Query bind(String varName, String value) {
this.validate();
JNA.INSTANCE.query_builder_bind_string(this.rawPointer, varName, value);
return this;
@ -226,15 +226,9 @@ public class Query extends RustObject {
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindUUID(String varName, UUID value) {
Query bind(String varName, UUID value) {
this.validate();
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(value.getMostSignificantBits());
bb.putLong(value.getLeastSignificantBits());
byte[] bytes = bb.array();
final Pointer bytesNativeArray = new Memory(bytes.length);
bytesNativeArray.write(0, bytes, 0, bytes.length);
JNA.INSTANCE.query_builder_bind_uuid(this.rawPointer, varName, bytesNativeArray);
JNA.INSTANCE.query_builder_bind_uuid(this.rawPointer, varName, getPointerForUUID(value));
return this;
}
@ -262,7 +256,7 @@ public class Query extends RustObject {
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void runScalar(final ScalarResultHandler handler) {
void run(final ScalarResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute_scalar(rawPointer);
rawPointer = null;
@ -285,7 +279,7 @@ public class Query extends RustObject {
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void runColl(final CollResultHandler handler) {
void run(final CollResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute_coll(rawPointer);
rawPointer = null;
@ -303,7 +297,7 @@ public class Query extends RustObject {
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void runTuple(final TupleResultHandler handler) {
void run(final TupleResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute_tuple(rawPointer);
rawPointer = null;

View file

@ -10,9 +10,12 @@
package com.mozilla.mentat;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.UUID;
/**
* Base class that wraps an non-optional {@link Pointer} representing a pointer to a Rust object.
@ -31,4 +34,22 @@ abstract class RustObject implements Closeable {
throw new NullPointerException(this.getClass() + " consumed");
}
}
public Pointer getPointerForUUID(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
byte[] bytes = bb.array();
final Pointer bytesNativeArray = new Memory(bytes.length);
bytesNativeArray.write(0, bytes, 0, bytes.length);
return bytesNativeArray;
}
public UUID getUUIDFromPointer(Pointer uuidPtr) {
byte[] bytes = uuidPtr.getByteArray(0, 16);
ByteBuffer bb = ByteBuffer.wrap(bytes);
long high = bb.getLong();
long low = bb.getLong();
return new UUID(high, low);
}
}

View file

@ -126,7 +126,7 @@ public class TupleResult extends RustObject {
*/
public Date asDate(Integer index) {
this.validate();
return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index));
return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index) * 1_000);
}
/**
@ -150,13 +150,7 @@ public class TupleResult extends RustObject {
*/
public UUID asUUID(Integer index) {
this.validate();
Pointer uuidPtr = JNA.INSTANCE.value_at_index_into_uuid(this.rawPointer, index);
byte[] bytes = uuidPtr.getByteArray(0, 16);
ByteBuffer bb = ByteBuffer.wrap(bytes);
long high = bb.getLong();
long low = bb.getLong();
return new UUID(high, low);
return getUUIDFromPointer(JNA.INSTANCE.value_at_index_into_uuid(this.rawPointer, index));
}
@Override

View file

@ -137,12 +137,7 @@ public class TypedValue extends RustObject {
*/
public UUID asUUID() {
if (!this.isConsumed()) {
Pointer uuidPtr = JNA.INSTANCE.typed_value_into_uuid(this.rawPointer);
byte[] bytes = uuidPtr.getByteArray(0, 16);
ByteBuffer bb = ByteBuffer.wrap(bytes);
long high = bb.getLong();
long low = bb.getLong();
this.value = new UUID(high, low);
this.value = getUUIDFromPointer(JNA.INSTANCE.typed_value_into_uuid(this.rawPointer));
this.rawPointer = null;
}
return (UUID)this.value;

View file

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
7B64E44D209094520063909F /* InProgressBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B64E44A209094510063909F /* InProgressBuilder.swift */; };
7B64E44E209094520063909F /* EntityBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B64E44B209094510063909F /* EntityBuilder.swift */; };
7B64E44F209094520063909F /* InProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B64E44C209094520063909F /* InProgress.swift */; };
7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */; };
7BAE75A22089020E00895D37 /* libmentat_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */; };
7BAE75A42089022B00895D37 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE75A32089022B00895D37 /* libsqlite3.tbd */; };
@ -39,6 +42,9 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
7B64E44A209094510063909F /* InProgressBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InProgressBuilder.swift; sourceTree = "<group>"; };
7B64E44B209094510063909F /* EntityBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityBuilder.swift; sourceTree = "<group>"; };
7B64E44C209094520063909F /* InProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InProgress.swift; sourceTree = "<group>"; };
7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Result+Unwrap.swift"; path = "Mentat/Extensions/Result+Unwrap.swift"; sourceTree = SOURCE_ROOT; };
7B911E1A2085081D000998CB /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = "../../../../sync-storage-prototype/rust/target/universal/release/libtoodle.a"; sourceTree = "<group>"; };
7BAE75A32089022B00895D37 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
@ -196,6 +202,9 @@
7BEB7D26207BE5BB000369AD /* Transact */ = {
isa = PBXGroup;
children = (
7B64E44B209094510063909F /* EntityBuilder.swift */,
7B64E44C209094520063909F /* InProgress.swift */,
7B64E44A209094510063909F /* InProgressBuilder.swift */,
7BEB7D2B207D03DA000369AD /* TxReport.swift */,
);
path = Transact;
@ -321,8 +330,11 @@
7BDB96CC207B7684009D0651 /* Errors.swift in Sources */,
7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */,
7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */,
7B64E44E209094520063909F /* EntityBuilder.swift in Sources */,
7B64E44D209094520063909F /* InProgressBuilder.swift in Sources */,
7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */,
7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */,
7B64E44F209094520063909F /* InProgress.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -37,14 +37,6 @@
BlueprintName = "MentatTests"
ReferencedContainer = "container:Mentat.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "MentatTests/testBindBoolean()">
</Test>
<Test
Identifier = "MentatTests/testBindDate()">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>

View file

@ -1,4 +1,3 @@
//
/* Copyright 2018 Mozilla
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use

View file

@ -29,6 +29,12 @@ protocol Observable {
func unregister(key: String)
}
enum CacheDirection {
case forward;
case reverse;
case both;
}
/**
The primary class for accessing Mentat's API.
This class provides all of the basic API that can be found in Mentat's Store struct.
@ -56,11 +62,36 @@ class Mentat: RustObject {
self.init(raw: store_open(storeURI))
}
/**
Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be
looked up.
- Parameter attribute: The attribute to cache
- Parameter direction: The direction the attribute should be keyed.
`forward` caches values for an attribute keyed by entity
(i.e. find values and entities that have this attribute, or find values of attribute for an entity)
`reverse` caches entities for an attribute keyed by value.
(i.e. find entities that have a particular value for an attribute).
`both` adds an attribute such that it is cached in both directions.
- Throws: `ResultError.error` if an error occured while trying to cache the attribute.
*/
func cache(attribute: String, direction: CacheDirection) throws {
switch direction {
case .forward:
try store_cache_attribute_forward(self.raw, attribute).pointee.tryUnwrap()
case .reverse:
try store_cache_attribute_reverse(self.raw, attribute).pointee.tryUnwrap()
case .both:
try store_cache_attribute_bi_directional(self.raw, attribute).pointee.tryUnwrap()
}
}
/**
Simple transact of an EDN string.
- Parameter transaction: The string, as EDN, to be transacted
- Throws: `MentatError` if the an error occured during the transaction, or the TxReport is nil.
- Throws: `ResultError.error` if the an error occured during the transaction, or the TxReport is nil.
- Returns: The `TxReport` of the completed transaction
*/
@ -69,8 +100,67 @@ class Mentat: RustObject {
return TxReport(raw: try result.unwrap())
}
/**
Start a new transaction.
- Throws: `ResultError.error` if the creation of the transaction fails.
- Throws: `ResultError.empty` if no `InProgress` is created.
- Returns: The `InProgress` used to manage the transaction
*/
func beginTransaction() throws -> InProgress {
let result = store_begin_transaction(self.raw).pointee;
return InProgress(raw: try result.unwrap())
}
/**
Creates a new transaction (`InProgress`) and returns an `InProgressBuilder` for that transaction.
- Throws: `ResultError.error` if the creation of the transaction fails.
- Throws: `ResultError.empty` if no `InProgressBuilder` is created.
- Returns: an `InProgressBuilder` for this `InProgress`
*/
func entityBuilder() throws -> InProgressBuilder {
let result = store_in_progress_builder(self.raw).pointee
return InProgressBuilder(raw: try result.unwrap())
}
/**
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for the entity with `entid`
for that transaction.
- Parameter entid: The `Entid` for this entity.
- Throws: `ResultError.error` if the creation of the transaction fails.
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
- Returns: an `EntityBuilder` for this `InProgress`
*/
func entityBuilder(forEntid entid: Entid) throws -> EntityBuilder {
let result = store_entity_builder_from_entid(self.raw, entid).pointee
return EntityBuilder(raw: try result.unwrap())
}
/**
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for a new entity with `tempId`
for that transaction.
- Parameter tempId: The temporary identifier for this entity.
- Throws: `ResultError.error` if the creation of the transaction fails.
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
- Returns: an `EntityBuilder` for this `InProgress`
*/
func entityBuilder(forTempId tempId: String) throws -> EntityBuilder {
let result = store_entity_builder_from_temp_id(self.raw, tempId).pointee
return EntityBuilder(raw: try result.unwrap())
}
/**
Get the the `Entid` of the attribute.
- Parameter attribute: The string represeting the attribute whose `Entid` we are after.
The string is represented as `:namespace/name`.

View file

@ -0,0 +1,355 @@
/* Copyright 2018 Mozilla
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
import Foundation
import MentatStore
/**
This class wraps a raw pointer that points to a Rust `EntityBuilder<InProgressBuilder>` object.
`EntityBuilder` provides a programmatic interface to performing assertions on a specific entity.
It provides functions for adding and retracting values for attributes for an entity within
an in progress transaction.
The `transact` function will transact the assertions that have been added to the `EntityBuilder`
and pass back the `TxReport` that was generated by this transact and the `InProgress` that was
used to perform the transact. This enables you to perform further transacts on the same `InProgress`
before committing.
```
let aEntid = txReport.entid(forTempId: "a")
let bEntid = txReport.entid(forTempId: "b")
do {
let builder = try mentat.entityBuilder(forEntid: bEntid)
try builder.add(keyword: ":foo/boolean", boolean: true)
try builder.add(keyword: ":foo/instant", date: newDate)
let (inProgress, report) = try builder.transact()
try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]")
try inProgress.commit()
} catch {
...
}
```
The `commit` function will transact and commit the assertions that have been added to the `EntityBuilder`.
It will consume the `InProgress` used to perform the transact. It returns the `TxReport` generated by
the transact. After calling `commit`, a new transaction must be started by calling `Mentat.beginTransaction()`
in order to perform further actions.
```
let aEntid = txReport.entid(forTempId: "a")
do {
let builder = try mentat.entityBuilder(forEntid: aEntid)
try builder.add(keyword: ":foo/boolean", boolean: true)
try builder.add(keyword: ":foo/instant", date: newDate)
let report = try builder.commit()
...
} catch {
...
}
```
*/
class EntityBuilder: OptionalRustObject {
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/long`.
*/
func add(keyword: String, long value: Int64) throws {
try entity_builder_add_long(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/ref`.
*/
func add(keyword: String, reference value: Int64) throws {
try entity_builder_add_ref(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/keyword`.
*/
func add(keyword: String, keyword value: String) throws {
try entity_builder_add_keyword(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/boolean`.
*/
func add(keyword: String, boolean value: Bool) throws {
try entity_builder_add_boolean(try self.validPointer(), keyword, value ? 1 : 0).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/double`.
*/
func add(keyword: String, double value: Double) throws {
try entity_builder_add_double(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/instant`.
*/
func add(keyword: String, date value: Date) throws {
try entity_builder_add_timestamp(try self.validPointer(), keyword, value.toMicroseconds()).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/string`.
*/
func add(keyword: String, string value: String) throws {
try entity_builder_add_string(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/uuid`.
*/
func add(keyword: String, uuid value: UUID) throws {
var rawUuid = value.uuid
let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in
try entity_builder_add_uuid(try self.validPointer(), keyword, uuidPtr).pointee.tryUnwrap()
}
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/long`.
*/
func retract(keyword: String, long value: Int64) throws {
try entity_builder_retract_long(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/ref`.
*/
func retract(keyword: String, reference value: Int64) throws {
try entity_builder_retract_ref(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/keyword`.
*/
func retract(keyword: String, keyword value: String) throws {
try entity_builder_retract_keyword(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/boolean`.
*/
func retract(keyword: String, boolean value: Bool) throws {
try entity_builder_retract_boolean(try self.validPointer(), keyword, value ? 1 : 0).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/double`.
*/
func retract(keyword: String, double value: Double) throws {
try entity_builder_retract_double(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/instant`.
*/
func retract(keyword: String, date value: Date) throws {
try entity_builder_retract_timestamp(try self.validPointer(), keyword, value.toMicroseconds()).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/string`.
*/
func retract(keyword: String, string value: String) throws {
try entity_builder_retract_string(try self.validPointer(), keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value`.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/uuid`.
*/
func retract(keyword: String, uuid value: UUID) throws {
var rawUuid = value.uuid
let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in
try entity_builder_retract_uuid(try self.validPointer(), keyword, uuidPtr).pointee.tryUnwrap()
}
}
/**
Transacts the added assertions. This consumes the pointer associated with this `EntityBuilder`
such that no further assertions can be added after the `transact` has completed. To perform
further assertions, use the `InProgress` returned from this function.
This does not commit the transaction. In order to do so, `commit` can be called on the `InProgress` returned
from this function.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if an error occured during the execution of the transact.
- Returns: The current `InProgress` and the `TxReport` generated by the transact.
*/
func transact() throws -> (InProgress, TxReport?) {
defer {
self.raw = nil
}
let result = entity_builder_transact(try self.validPointer()).pointee
let inProgress = InProgress(raw: result.inProgress)
guard let report = try result.result.pointee.tryUnwrap() else {
return (inProgress, nil)
}
return (inProgress, TxReport(raw: report))
}
/**
Transacts the added assertions and commits. This consumes the pointer associated with this `EntityBuilder`
and the associated `InProgress` such that no further assertions can be added after the `commit` has completed.
To perform further assertions, a new `InProgress` or `EntityBuilder` should be created.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if an error occured during the execution of the transact.
- Returns: The `TxReport` generated by the transact.
*/
func commit() throws -> TxReport {
defer {
self.raw = nil
}
return TxReport(raw: try entity_builder_commit(try self.validPointer()).pointee.unwrap())
}
override func cleanup(pointer: OpaquePointer) {
entity_builder_destroy(pointer)
}
}

View file

@ -0,0 +1,184 @@
/* Copyright 2018 Mozilla
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
import Foundation
import MentatStore
/**
This class wraps a raw pointer that points to a Rust `InProgress` object.
`InProgress` allows for multiple transacts to be performed in a single transaction.
Each transact performed results in a `TxReport` that can be used to gather information
to be used in subsequent transacts.
Committing an `InProgress` commits all the transacts that have been performed using
that `InProgress`.
Rolling back and `InProgress` rolls back all the transacts that have been performed
using that `InProgress`.
```
do {
let inProgress = try mentat.beginTransaction()
let txReport = try inProgress.transact(transaction: "[[:db/add "a" :foo/long 22]]")
let aEntid = txReport.entid(forTempId: "a")
let report = try inProgress.transact(transaction: "[[:db/add "b" :foo/ref \(aEntid)] [:db/add "b" :foo/boolean true]]")
try inProgress.commit()
} catch {
...
}
```
`InProgress` also provides a number of functions to generating an builder to assert datoms programatically.
The two types of builder are `InProgressBuilder` and `EntityBuilder`.
`InProgressBuilder` takes the current `InProgress` and provides a programmatic interface to add
and retract values from entities for which there exists an `Entid`. The provided `InProgress`
is used to perform the transacts.
```
let aEntid = txReport.entid(forTempId: "a")
let bEntid = txReport.entid(forTempId: "b")
do {
let inProgress = try mentat.beginTransaction()
let builder = try inProgress.builder()
try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true)
try builder.add(entid: aEntid, keyword: ":foo/instant", date: newDate)
let (inProgress, report) = try builder.transact()
try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]")
try inProgress.commit()
} catch {
...
}
```
`EntityBuilder` takes the current `InProgress` and either an `Entid` or a `tempid` to provide
a programmatic interface to add and retract values from a specific entity. The provided `InProgress`
is used to perform the transacts.
```
do {
let transaction = try mentat.beginTransaction()
let builder = try transaction.builder(forTempId: "b")
try builder.add(keyword: ":foo/boolean", boolean: true)
try builder.add(keyword: ":foo/instant", date: newDate)
let (inProgress, report) = try builder.transact()
let bEntid = report.entid(forTempId: "b")
try inProgress.transact(transaction: "[[:db/add \(bEntid) :foo/long 22]]")
try inProgress.commit()
} catch {
...
}
```
*/
class InProgress: OptionalRustObject {
/**
Creates an `InProgressBuilder` using this `InProgress`.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the `InProgress`
has already been committed, or converted into a Builder.
- Returns: an `InProgressBuilder` for this `InProgress`
*/
func builder() throws -> InProgressBuilder {
defer {
self.raw = nil
}
return InProgressBuilder(raw: in_progress_builder(try self.validPointer()))
}
/**
Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`.
- Parameter entid: The `Entid` for this entity.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the `InProgress`
has already been committed, or converted into a Builder.
- Returns: an `EntityBuilder` for this `InProgress`
*/
func builder(forEntid entid: Int64) throws -> EntityBuilder {
defer {
self.raw = nil
}
return EntityBuilder(raw: in_progress_entity_builder_from_entid(try self.validPointer(), entid))
}
/**
Creates an `EntityBuilder` using this `InProgress` for a new entity with `tempId`.
- Parameter tempId: The temporary identifier for this entity.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the `InProgress`
has already been committed, or converted into a Builder.
- Returns: an `EntityBuilder` for this `InProgress`
*/
func builder(forTempId tempId: String) throws -> EntityBuilder {
defer {
self.raw = nil
}
return EntityBuilder(raw: in_progress_entity_builder_from_temp_id(try self.validPointer(), tempId))
}
/**
Transacts the `transaction`
This does not commit the transaction. In order to do so, `commit` can be called.
- Parameter transaction: The EDN string to be transacted.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the transaction failed.
- Throws: `ResultError.empty` if no `TxReport` is returned from the transact.
- Returns: The `TxReport` generated by the transact.
*/
func transact(transaction: String) throws -> TxReport {
let result = in_progress_transact(try self.validPointer(), transaction).pointee
return TxReport(raw: try result.unwrap())
}
/**
Commits all the transacts that have been performed on this `InProgress`, either directly
or through a Builder.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the commit failed.
*/
func commit() throws {
defer {
self.raw = nil
}
try in_progress_commit(try self.validPointer()).pointee.tryUnwrap()
}
/**
Rolls back all the transacts that have been performed on this `InProgress`, either directly
or through a Builder.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the rollback failed.
*/
func rollback() throws {
defer {
self.raw = nil
}
try in_progress_rollback(try self.validPointer()).pointee.tryUnwrap()
}
override func cleanup(pointer: OpaquePointer) {
in_progress_destroy(pointer)
}
}

View file

@ -0,0 +1,372 @@
/* Copyright 2018 Mozilla
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. */
import Foundation
import MentatStore
/**
This class wraps a raw pointer that points to a Rust `InProgressBuilder` object.
`InProgressBuilder` provides a programmatic interface to performing assertions for entities.
It provides functions for adding and retracting values for attributes for an entity within
an in progress transaction.
The `transact` function will transact the assertions that have been added to the `InProgressBuilder`
and pass back the `TxReport` that was generated by this transact and the `InProgress` that was
used to perform the transact. This enables you to perform further transacts on the same `InProgress`
before committing.
```
let aEntid = txReport.entid(forTempId: "a")
let bEntid = txReport.entid(forTempId: "b")
do {
let builder = try mentat.entityBuilder()
try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true)
try builder.add(entid: aEntid, keyword: ":foo/instant", date: newDate)
let (inProgress, report) = try builder.transact()
try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]")
try inProgress.commit()
...
} catch {
...
}
```
The `commit` function will transact and commit the assertions that have been added to the `EntityBuilder`.
It will consume the `InProgress` used to perform the transact. It returns the `TxReport` generated by
the transact. After calling `commit`, a new transaction must be started by calling `Mentat.beginTransaction()`
in order to perform further actions.
```
let aEntid = txReport.entid(forTempId: "a")
let bEntid = txReport.entid(forTempId: "b")
do {
let builder = try mentat.entityBuilder(forEntid: aEntid)
try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true)
try builder.add(entid: aEntid, keyword: ":foo/instant", date: newDate)
let report = try builder.commit()
...
} catch {
...
}
```
*/
class InProgressBuilder: OptionalRustObject {
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/long`.
*/
func add(entid: Entid, keyword: String, long value: Int64) throws {
try in_progress_builder_add_long(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/ref`.
*/
func add(entid: Entid, keyword: String, reference value: Entid) throws {
try in_progress_builder_add_ref(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/keyword`.
*/
func add(entid: Entid, keyword: String, keyword value: String) throws {
try in_progress_builder_add_keyword(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/boolean`.
*/
func add(entid: Entid, keyword: String, boolean value: Bool) throws {
try in_progress_builder_add_boolean(try self.validPointer(), entid, keyword, value ? 1 : 0).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/double`.
*/
func add(entid: Entid, keyword: String, double value: Double) throws {
try in_progress_builder_add_double(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/instant`.
*/
func add(entid: Entid, keyword: String, date value: Date) throws {
try in_progress_builder_add_timestamp(try self.validPointer(), entid, keyword, value.toMicroseconds()).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/string`.
*/
func add(entid: Entid, keyword: String, string value: String) throws {
try in_progress_builder_add_string(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be asserted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/uuid`.
*/
func add(entid: Entid, keyword: String, uuid value: UUID) throws {
var rawUuid = value.uuid
let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in
try in_progress_builder_add_uuid(try self.validPointer(), entid, keyword, uuidPtr).pointee.tryUnwrap()
}
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/long`.
*/
func retract(entid: Entid, keyword: String, long value: Int64) throws {
try in_progress_builder_retract_long(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/ref`.
*/
func retract(entid: Entid, keyword: String, reference value: Entid) throws {
try in_progress_builder_retract_ref(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/keyword`.
*/
func retract(entid: Entid, keyword: String, keyword value: String) throws {
try in_progress_builder_retract_keyword(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/boolean`.
*/
func retract(entid: Entid, keyword: String, boolean value: Bool) throws {
try in_progress_builder_retract_boolean(try self.validPointer(), entid, keyword, value ? 1 : 0).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/double`.
*/
func retract(entid: Entid, keyword: String, double value: Double) throws {
try in_progress_builder_retract_double(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/instant`.
*/
func retract(entid: Entid, keyword: String, date value: Date) throws {
try in_progress_builder_retract_timestamp(try self.validPointer(), entid, keyword, value.toMicroseconds()).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/string`.
*/
func retract(entid: Entid, keyword: String, string value: String) throws {
try in_progress_builder_retract_string(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap()
}
/**
Retracts the value of attribute `keyword` from the provided `value` for entity `entid`.
- Parameter entid: The `Entid` of the entity to be touched.
- Parameter keyword: The name of the attribute in the format `:namespace/name`.
- Parameter value: The value to be retracted
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type
is not `:db.type/uuid`.
*/
func retract(entid: Entid, keyword: String, uuid value: UUID) throws {
var rawUuid = value.uuid
let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in
try in_progress_builder_retract_uuid(try self.validPointer(), entid, keyword, uuidPtr).pointee.tryUnwrap()
}
}
/**
Transacts the added assertions. This consumes the pointer associated with this `InProgressBuilder`
such that no further assertions can be added after the `transact` has completed. To perform
further assertions, use the `InProgress` returned from this function.
This does not commit the transaction. In order to do so, `commit` can be called on the `InProgress` returned
from this function.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if an error occured during the execution of the transact.
- Returns: The current `InProgress` and the `TxReport` generated by the transact.
*/
func transact() throws -> (InProgress, TxReport?) {
defer {
self.raw = nil
}
let result = in_progress_builder_transact(try self.validPointer()).pointee
let inProgress = InProgress(raw: result.inProgress)
guard let report = try result.result.pointee.tryUnwrap() else {
return (inProgress, nil)
}
return (inProgress, TxReport(raw: report))
}
/**
Transacts the added assertions and commits. This consumes the pointer associated with this `InProgressBuilder`
and the associated `InProgress` such that no further assertions can be added after the `commit` has completed.
To perform further assertions, a new `InProgress` or `InProgressBuilder` should be created.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder
has already been transacted or committed.
- Throws: `ResultError.error` if an error occured during the execution of the transact.
- Returns: The `TxReport` generated by the transact.
*/
func commit() throws -> TxReport {
defer {
self.raw = nil
}
return TxReport(raw: try in_progress_builder_commit(try self.validPointer()).pointee.unwrap())
}
override func cleanup(pointer: OpaquePointer) {
in_progress_builder_destroy(pointer)
}
}

View file

@ -13,7 +13,7 @@ import Foundation
import MentatStore
/**
This class wraps a raw pointer than points to a Rust `TxReport` object.
This class wraps a raw pointer that points to a Rust `TxReport` object.
The `TxReport` contains information about a successful Mentat transaction.

View file

@ -60,6 +60,16 @@ struct Option {
};
typedef struct Option Option;
/*
A mapping of the InProgressTransactResult repr(C) Rust object.
The memory for this is managed by Swift.
*/
struct InProgressTransactResult {
struct InProgress*_Nonnull inProgress;
struct Result*_Nonnull result;
};
typedef struct InProgressTransactResult InProgressTransactResult;
/*
A Mapping for the ValueType Rust object.
*/
@ -100,13 +110,72 @@ void typed_value_list_destroy(struct QueryResultRow* _Nullable obj);
void typed_value_list_iter_destroy(struct QueryRowIterator* _Nullable obj);
void typed_value_result_set_destroy(struct QueryResultRows* _Nullable obj);
void typed_value_result_set_iter_destroy(struct QueryRowsIterator* _Nullable obj);
void in_progress_destroy(struct InProgress* _Nullable obj);
void in_progress_builder_destroy(struct InProgressBuilder* _Nullable obj);
void entity_builder_destroy(struct EntityBuilder* _Nullable obj);
// caching
struct Result*_Nonnull store_cache_attribute_forward(struct Store*_Nonnull store, const char* _Nonnull attribute);
struct Result*_Nonnull store_cache_attribute_reverse(struct Store*_Nonnull store, const char* _Nonnull attribute);
struct Result*_Nonnull store_cache_attribute_bi_directional(struct Store*_Nonnull store, const char* _Nonnull attribute);
// transact
struct Result*_Nonnull store_transact(struct Store*_Nonnull store, const char* _Nonnull transaction);
const int64_t* _Nullable tx_report_entity_for_temp_id(const struct TxReport* _Nonnull report, const char* _Nonnull tempid);
int64_t tx_report_get_entid(const struct TxReport* _Nonnull report);
int64_t tx_report_get_tx_instant(const struct TxReport* _Nonnull report);
struct Result*_Nonnull store_begin_transaction(struct Store*_Nonnull store);
// in progress
struct Result*_Nonnull in_progress_transact(struct InProgress*_Nonnull in_progress, const char* _Nonnull transaction);
struct Result*_Nonnull in_progress_commit(struct InProgress*_Nonnull in_progress);
struct Result*_Nonnull in_progress_rollback(struct InProgress*_Nonnull in_progress);
// in_progress entity building
struct Result*_Nonnull store_in_progress_builder(struct Store*_Nonnull store);
struct InProgressBuilder*_Nonnull in_progress_builder(struct InProgress*_Nonnull in_progress);
struct EntityBuilder*_Nonnull in_progress_entity_builder_from_temp_id(struct InProgress*_Nonnull in_progress, const char*_Nonnull temp_id);
struct EntityBuilder*_Nonnull in_progress_entity_builder_from_entid(struct InProgress*_Nonnull in_progress, const int64_t entid);
struct Result*_Nonnull in_progress_builder_add_string(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull in_progress_builder_add_long(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull in_progress_builder_add_ref(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull in_progress_builder_add_keyword(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull in_progress_builder_add_timestamp(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull in_progress_builder_add_boolean(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int32_t value);
struct Result*_Nonnull in_progress_builder_add_double(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const double value);
struct Result*_Nonnull in_progress_builder_add_uuid(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const uuid_t* _Nonnull value);
struct Result*_Nonnull in_progress_builder_retract_string(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull in_progress_builder_retract_long(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull in_progress_builder_retract_ref(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull in_progress_builder_retract_keyword(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull in_progress_builder_retract_timestamp(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull in_progress_builder_retract_boolean(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int32_t value);
struct Result*_Nonnull in_progress_builder_retract_double(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const double value);
struct Result*_Nonnull in_progress_builder_retract_uuid(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const uuid_t* _Nonnull value);
struct InProgressTransactResult*_Nonnull in_progress_builder_transact(struct InProgressBuilder*_Nonnull builder);
struct Result*_Nonnull in_progress_builder_commit(struct InProgressBuilder*_Nonnull builder);
// entity building
struct Result*_Nonnull store_entity_builder_from_temp_id(struct Store*_Nonnull store, const char*_Nonnull temp_id);
struct Result*_Nonnull store_entity_builder_from_entid(struct Store*_Nonnull store, const int64_t entid);
struct Result*_Nonnull entity_builder_add_string(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull entity_builder_add_long(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull entity_builder_add_ref(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull entity_builder_add_keyword(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull entity_builder_add_boolean(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int32_t value);
struct Result*_Nonnull entity_builder_add_double(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const double value);
struct Result*_Nonnull entity_builder_add_timestamp(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull entity_builder_add_uuid(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const uuid_t* _Nonnull value);
struct Result*_Nonnull entity_builder_retract_string(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull entity_builder_retract_long(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull entity_builder_retract_ref(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull entity_builder_retract_keyword(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value);
struct Result*_Nonnull entity_builder_retract_boolean(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int32_t value);
struct Result*_Nonnull entity_builder_retract_double(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const double value);
struct Result*_Nonnull entity_builder_retract_timestamp(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value);
struct Result*_Nonnull entity_builder_retract_uuid(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const uuid_t* _Nonnull value);
struct InProgressTransactResult*_Nonnull entity_builder_transact(struct InProgressBuilder*_Nonnull builder);
struct Result*_Nonnull entity_builder_commit(struct EntityBuilder*_Nonnull builder);
// Sync
struct Result*_Nonnull store_sync(struct Store*_Nonnull store, const char* _Nonnull user_uuid, const char* _Nonnull server_uri);

View file

@ -17,12 +17,12 @@ class MentatTests: XCTestCase {
var citiesSchema: String?
var seattleData: String?
var store: Mentat?
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
@ -120,7 +120,8 @@ class MentatTests: XCTestCase {
[:db/add "u" :db/cardinality :db.cardinality/one]
]
"""
let report = try mentat.transact(transaction: schema)
let transaction = try mentat.beginTransaction();
let report = try transaction.transact(transaction: schema)
let stringEntid = report.entid(forTempId: "s")!
let data = """
@ -142,7 +143,8 @@ class MentatTests: XCTestCase {
[:db/add "b" :foo/uuid #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644"]
]
"""
let dataReport = try mentat.transact(transaction: data)
let dataReport = try transaction.transact(transaction: data)
try transaction.commit();
return (report, dataReport)
} catch {
assertionFailure(error.localizedDescription)
@ -317,7 +319,7 @@ class MentatTests: XCTestCase {
guard let rows = relResult else {
return assertionFailure("No results received")
}
var i = 0
rows.forEach({ (row) in
let (name, category) = expectedResults[i]
@ -760,6 +762,483 @@ class MentatTests: XCTestCase {
}
}
func test3InProgressTransact() {
let mentat = Mentat()
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
XCTAssertNotNil(report)
}
func testInProgressRollback() {
let mentat = Mentat()
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
XCTAssertNotNil(report)
let aEntid = report!.entid(forTempId: "a")!
let preLongValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid)
XCTAssertEqual(25, preLongValue?.asLong())
let inProgress = try! mentat.beginTransaction()
XCTAssertNoThrow(try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]"))
XCTAssertNoThrow(try inProgress.rollback())
let postLongValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid)
XCTAssertEqual(25, postLongValue?.asLong())
}
func testInProgressEntityBuilder() {
let mentat = Mentat()
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let longEntid = schemaReport!.entid(forTempId: "l")!
let stringEntid = schemaReport!.entid(forTempId: "s")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(false, result?.asBool(index: 0))
let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00")
XCTAssertEqual(previousDate, result?.asDate(index: 1))
let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")!
XCTAssertEqual(previousUuid, result?.asUUID(index: 2))
XCTAssertEqual(50, result?.asLong(index: 3))
XCTAssertEqual(22.46, result?.asDouble(index: 4))
XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5))
XCTAssertEqual(":foo/string", result?.asKeyword(index: 6))
XCTAssertEqual(stringEntid, result?.asEntid(index: 7))
})
let builder = try! mentat.entityBuilder()
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true))
let newDate = Date()
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/instant", date: newDate))
let newUUID = UUID()
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/uuid", uuid: newUUID))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/long", long: 75))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/double", double: 81.3))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/string", string: "Become who you are!"))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/keyword", keyword: ":foo/long"))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/ref", reference: longEntid))
XCTAssertNoThrow(try builder.commit())
// test that the values have changed
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(true, result?.asBool(index: 0))
XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1)))
XCTAssertEqual(newUUID, result?.asUUID(index: 2))
XCTAssertEqual(75, result?.asLong(index: 3))
XCTAssertEqual(81.3, result?.asDouble(index: 4))
XCTAssertEqual("Become who you are!", result?.asString(index: 5))
XCTAssertEqual(":foo/long", result?.asKeyword(index: 6))
XCTAssertEqual(longEntid, result?.asEntid(index: 7))
})
}
func testEntityBuilderForEntid() {
let mentat = Mentat()
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let longEntid = schemaReport!.entid(forTempId: "l")!
let stringEntid = schemaReport!.entid(forTempId: "s")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(false, result?.asBool(index: 0))
let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00")
XCTAssertEqual(previousDate, result?.asDate(index: 1))
let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")!
XCTAssertEqual(previousUuid, result?.asUUID(index: 2))
XCTAssertEqual(50, result?.asLong(index: 3))
XCTAssertEqual(22.46, result?.asDouble(index: 4))
XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5))
XCTAssertEqual(":foo/string", result?.asKeyword(index: 6))
XCTAssertEqual(stringEntid, result?.asEntid(index: 7))
})
let builder = try! mentat.entityBuilder(forEntid: bEntid)
XCTAssertNoThrow(try builder.add(keyword: ":foo/boolean", boolean: true))
let newDate = Date()
XCTAssertNoThrow(try builder.add(keyword: ":foo/instant", date: newDate))
let newUUID = UUID()
XCTAssertNoThrow(try builder.add(keyword: ":foo/uuid", uuid: newUUID))
XCTAssertNoThrow(try builder.add(keyword: ":foo/long", long: 75))
XCTAssertNoThrow(try builder.add(keyword: ":foo/double", double: 81.3))
XCTAssertNoThrow(try builder.add(keyword: ":foo/string", string: "Become who you are!"))
XCTAssertNoThrow(try builder.add(keyword: ":foo/keyword", keyword: ":foo/long"))
XCTAssertNoThrow(try builder.add(keyword: ":foo/ref", reference: longEntid))
XCTAssertNoThrow(try builder.commit())
// test that the values have changed
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(true, result?.asBool(index: 0))
XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1)))
XCTAssertEqual(newUUID, result?.asUUID(index: 2))
XCTAssertEqual(75, result?.asLong(index: 3))
XCTAssertEqual(81.3, result?.asDouble(index: 4))
XCTAssertEqual("Become who you are!", result?.asString(index: 5))
XCTAssertEqual(":foo/long", result?.asKeyword(index: 6))
XCTAssertEqual(longEntid, result?.asEntid(index: 7))
})
}
func testEntityBuilderForTempid() {
let mentat = Mentat()
let (schemaReport, _) = self.populateWithTypesSchema(mentat: mentat)
let longEntid = schemaReport!.entid(forTempId: "l")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let builder = try! mentat.entityBuilder(forTempId: "c")
XCTAssertNoThrow(try builder.add(keyword: ":foo/boolean", boolean: true))
let newDate = Date()
XCTAssertNoThrow(try builder.add(keyword: ":foo/instant", date: newDate))
let newUUID = UUID()
XCTAssertNoThrow(try builder.add(keyword: ":foo/uuid", uuid: newUUID))
XCTAssertNoThrow(try builder.add(keyword: ":foo/long", long: 75))
XCTAssertNoThrow(try builder.add(keyword: ":foo/double", double: 81.3))
XCTAssertNoThrow(try builder.add(keyword: ":foo/string", string: "Become who you are!"))
XCTAssertNoThrow(try builder.add(keyword: ":foo/keyword", keyword: ":foo/long"))
XCTAssertNoThrow(try builder.add(keyword: ":foo/ref", reference: longEntid))
let report = try! builder.commit()
let cEntid = report.entid(forTempId: "c")!
// test that the values have changed
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: cEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(true, result?.asBool(index: 0))
XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1)))
XCTAssertEqual(newUUID, result?.asUUID(index: 2))
XCTAssertEqual(75, result?.asLong(index: 3))
XCTAssertEqual(81.3, result?.asDouble(index: 4))
XCTAssertEqual("Become who you are!", result?.asString(index: 5))
XCTAssertEqual(":foo/long", result?.asKeyword(index: 6))
XCTAssertEqual(longEntid, result?.asEntid(index: 7))
})
}
func testInProgressBuilderTransact() {
let mentat = Mentat()
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = dataReport!.entid(forTempId: "a")!
let bEntid = dataReport!.entid(forTempId: "b")!
let longEntid = schemaReport!.entid(forTempId: "l")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let builder = try! mentat.entityBuilder()
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true))
let newDate = Date()
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/instant", date: newDate))
let newUUID = UUID()
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/uuid", uuid: newUUID))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/long", long: 75))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/double", double: 81.3))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/string", string: "Become who you are!"))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/keyword", keyword: ":foo/long"))
XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/ref", reference: longEntid))
let (inProgress, report) = try! builder.transact()
XCTAssertNotNil(inProgress)
XCTAssertNotNil(report)
XCTAssertNoThrow(try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]"))
XCTAssertNoThrow(try inProgress.commit())
// test that the values have changed
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(true, result?.asBool(index: 0))
XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1)))
XCTAssertEqual(newUUID, result?.asUUID(index: 2))
XCTAssertEqual(75, result?.asLong(index: 3))
XCTAssertEqual(81.3, result?.asDouble(index: 4))
XCTAssertEqual("Become who you are!", result?.asString(index: 5))
XCTAssertEqual(":foo/long", result?.asKeyword(index: 6))
XCTAssertEqual(longEntid, result?.asEntid(index: 7))
})
let longValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid)
XCTAssertEqual(22, longValue?.asLong())
}
func testEntityBuilderTransact() {
let mentat = Mentat()
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = dataReport!.entid(forTempId: "a")!
let bEntid = dataReport!.entid(forTempId: "b")!
let longEntid = schemaReport!.entid(forTempId: "l")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let builder = try! mentat.entityBuilder(forEntid: bEntid)
XCTAssertNoThrow(try builder.add(keyword: ":foo/boolean", boolean: true))
let newDate = Date()
XCTAssertNoThrow(try builder.add(keyword: ":foo/instant", date: newDate))
let newUUID = UUID()
XCTAssertNoThrow(try builder.add(keyword: ":foo/uuid", uuid: newUUID))
XCTAssertNoThrow(try builder.add(keyword: ":foo/long", long: 75))
XCTAssertNoThrow(try builder.add(keyword: ":foo/double", double: 81.3))
XCTAssertNoThrow(try builder.add(keyword: ":foo/string", string: "Become who you are!"))
XCTAssertNoThrow(try builder.add(keyword: ":foo/keyword", keyword: ":foo/long"))
XCTAssertNoThrow(try builder.add(keyword: ":foo/ref", reference: longEntid))
let (inProgress, report) = try! builder.transact()
XCTAssertNotNil(inProgress)
XCTAssertNotNil(report)
XCTAssertNoThrow(try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]"))
XCTAssertNoThrow(try inProgress.commit())
// test that the values have changed
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(true, result?.asBool(index: 0))
XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1)))
XCTAssertEqual(newUUID, result?.asUUID(index: 2))
XCTAssertEqual(75, result?.asLong(index: 3))
XCTAssertEqual(81.3, result?.asDouble(index: 4))
XCTAssertEqual("Become who you are!", result?.asString(index: 5))
XCTAssertEqual(":foo/long", result?.asKeyword(index: 6))
XCTAssertEqual(longEntid, result?.asEntid(index: 7))
})
let longValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid)
XCTAssertEqual(22, longValue?.asLong())
}
func testEntityBuilderRetract() {
let mentat = Mentat()
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let stringEntid = schemaReport!.entid(forTempId: "s")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00")!
let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")!
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(false, result?.asBool(index: 0))
XCTAssertEqual(previousDate, result?.asDate(index: 1))
XCTAssertEqual(previousUuid, result?.asUUID(index: 2))
XCTAssertEqual(50, result?.asLong(index: 3))
XCTAssertEqual(22.46, result?.asDouble(index: 4))
XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5))
XCTAssertEqual(":foo/string", result?.asKeyword(index: 6))
XCTAssertEqual(stringEntid, result?.asEntid(index: 7))
})
let builder = try! mentat.entityBuilder(forEntid: bEntid)
XCTAssertNoThrow(try builder.retract(keyword: ":foo/boolean", boolean: false))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/instant", date: previousDate))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/uuid", uuid: previousUuid))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/long", long: 50))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/double", double: 22.46))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/string", string: "Silence is worse; all truths that are kept silent become poisonous."))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/keyword", keyword: ":foo/string"))
XCTAssertNoThrow(try builder.retract(keyword: ":foo/ref", reference: stringEntid))
XCTAssertNoThrow(try builder.commit())
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNil(result)
})
}
func testInProgressEntityBuilderRetract() {
let mentat = Mentat()
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let stringEntid = schemaReport!.entid(forTempId: "s")!
// test that the values are as expected
let query = """
[:find [?b ?i ?u ?l ?d ?s ?k ?r]
:in ?e
:where [?e :foo/boolean ?b]
[?e :foo/instant ?i]
[?e :foo/uuid ?u]
[?e :foo/long ?l]
[?e :foo/double ?d]
[?e :foo/string ?s]
[?e :foo/keyword ?k]
[?e :foo/ref ?r]]
"""
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00")!
let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")!
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNotNil(result)
XCTAssertEqual(false, result?.asBool(index: 0))
XCTAssertEqual(previousDate, result?.asDate(index: 1))
XCTAssertEqual(previousUuid, result?.asUUID(index: 2))
XCTAssertEqual(50, result?.asLong(index: 3))
XCTAssertEqual(22.46, result?.asDouble(index: 4))
XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5))
XCTAssertEqual(":foo/string", result?.asKeyword(index: 6))
XCTAssertEqual(stringEntid, result?.asEntid(index: 7))
})
let builder = try! mentat.entityBuilder()
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/boolean", boolean: false))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/instant", date: previousDate))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/uuid", uuid: previousUuid))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/long", long: 50))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/double", double: 22.46))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/string", string: "Silence is worse; all truths that are kept silent become poisonous."))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/keyword", keyword: ":foo/string"))
XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/ref", reference: stringEntid))
XCTAssertNoThrow(try builder.commit())
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in
XCTAssertNil(result)
})
}
func testCaching() {
let query = """
[:find ?district :where
[?neighborhood :neighborhood/name \"Beacon Hill\"]
[?neighborhood :neighborhood/district ?d]
[?d :district/name ?district]]
"""
let mentat = openAndInitializeCitiesStore()
struct QueryTimer {
private var _start: UInt64
private var _end: UInt64
init() {
self._start = 0
self._end = 0
}
private func currentTimeNanos() -> UInt64 {
var info = mach_timebase_info()
guard mach_timebase_info(&info) == KERN_SUCCESS else { return 0 }
let currentTime = mach_absolute_time()
return currentTime * UInt64(info.numer) / UInt64(info.denom)
}
mutating func start() {
self._start = self.currentTimeNanos()
}
mutating func end() {
self._end = self.currentTimeNanos()
}
func duration() -> UInt64 {
return self._end - self._start
}
}
var uncachedTimer = QueryTimer()
uncachedTimer.start()
XCTAssertNoThrow(try mentat.query(query: query).run { (result) in
uncachedTimer.end()
XCTAssertNotNil(result)
})
XCTAssertNoThrow(try mentat.cache(attribute: ":neighborhood/name", direction: CacheDirection.reverse))
XCTAssertNoThrow(try mentat.cache(attribute: ":neighborhood/district", direction: CacheDirection.forward))
var cachedTimer = QueryTimer()
cachedTimer.start()
XCTAssertNoThrow(try mentat.query(query: query).run { (result) in
cachedTimer.end()
XCTAssertNotNil(result)
})
let timingDifference = uncachedTimer.duration() - cachedTimer.duration()
print("Cached query is \(timingDifference) nanoseconds faster than the uncached query")
XCTAssertLessThan(cachedTimer.duration(), uncachedTimer.duration())
}
// TODO: Add tests for transaction observation
}