Update FFI API for use with Swift Library.

Fixing issues raised by tests added to swift library.
Improving the API
This commit is contained in:
Emily Toop 2018-04-12 11:21:42 +01:00
parent 5fbf17f4f9
commit 10e3d52902
4 changed files with 119 additions and 32 deletions

View file

@ -4,11 +4,12 @@ version = "0.0.1"
authors = ["Emily Toop <etoop@mozilla.com>"] authors = ["Emily Toop <etoop@mozilla.com>"]
[lib] [lib]
name = "mentat" name = "mentat_ffi"
crate-type = ["staticlib", "cdylib"] crate-type = ["lib", "staticlib", "cdylib"]
[dependencies] [dependencies]
libc = "0.2" libc = "0.2.32"
[dependencies.mentat] [dependencies.mentat]
path = ".." path = "../"

View file

@ -42,6 +42,7 @@ pub use mentat::{
Syncable, Syncable,
TypedValue, TypedValue,
TxObserver, TxObserver,
TxReport,
Uuid, Uuid,
ValueType, ValueType,
Variable, Variable,
@ -62,16 +63,23 @@ pub type TypedValueListIterator = vec::IntoIter<Vec<TypedValue>>;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExternTxReport { pub struct ExternTempId {
pub key: *const c_char,
pub value: i64,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct TransactionChange {
pub txid: Entid, pub txid: Entid,
pub changes: Box<[Entid]>,
pub changes_len: usize, pub changes_len: usize,
pub changes: Box<[Entid]>,
} }
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
pub struct ExternTxReportList { pub struct TxChangeList {
pub reports: Box<[ExternTxReport]>, pub reports: Box<[TransactionChange]>,
pub len: usize, pub len: usize,
} }
@ -141,8 +149,8 @@ pub extern "C" fn store_open(uri: *const c_char) -> *mut Store {
pub unsafe extern "C" fn store_transact(store: *mut Store, transaction: *const c_char) -> *mut ExternResult { pub unsafe extern "C" fn store_transact(store: *mut Store, transaction: *const c_char) -> *mut ExternResult {
let store = &mut*store; let store = &mut*store;
let transaction = c_char_to_string(transaction); let transaction = c_char_to_string(transaction);
let result = store.begin_transaction().map(|mut in_progress| { let result = store.begin_transaction().and_then(|mut in_progress| {
in_progress.transact(&transaction).map(|tx_report| { in_progress.transact(&transaction).and_then(|tx_report| {
in_progress.commit() in_progress.commit()
.map(|_| tx_report) .map(|_| tx_report)
}) })
@ -150,6 +158,29 @@ pub unsafe extern "C" fn store_transact(store: *mut Store, transaction: *const c
Box::into_raw(Box::new(result.into())) Box::into_raw(Box::new(result.into()))
} }
#[no_mangle]
pub unsafe extern "C" fn tx_report_get_entid(tx_report: *mut TxReport) -> i64 {
let tx_report = &*tx_report;
tx_report.tx_id
}
#[no_mangle]
pub unsafe extern "C" fn tx_report_get_tx_instant(tx_report: *mut TxReport) -> i64 {
let tx_report = &*tx_report;
tx_report.tx_instant.timestamp()
}
#[no_mangle]
pub unsafe extern "C" fn tx_report_entity_for_temp_id(tx_report: *mut TxReport, tempid: *const c_char) -> *mut i64 {
let tx_report = &*tx_report;
let key = c_char_to_string(tempid);
if let Some(entid) = tx_report.tempids.get(&key) {
Box::into_raw(Box::new(entid.clone()))
} else {
std::ptr::null_mut()
}
}
// TODO: cache // TODO: cache
// TODO: q_once // TODO: q_once
@ -161,14 +192,6 @@ pub unsafe extern "C" fn store_query<'a>(store: *mut Store, query: *const c_char
Box::into_raw(Box::new(query_builder)) Box::into_raw(Box::new(query_builder))
} }
#[no_mangle]
pub unsafe extern "C" fn query_builder_bind_int(query_builder: *mut QueryBuilder, var: *const c_char, value: c_int) {
let var = c_char_to_string(var);
let query_builder = &mut*query_builder;
let value = value as i32;
query_builder.bind_value(&var, value);
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn query_builder_bind_long(query_builder: *mut QueryBuilder, var: *const c_char, value: i64) { pub unsafe extern "C" fn query_builder_bind_long(query_builder: *mut QueryBuilder, var: *const c_char, value: i64) {
let var = c_char_to_string(var); let var = c_char_to_string(var);
@ -294,9 +317,9 @@ pub unsafe extern "C" fn typed_value_as_kw(typed_value: *mut TypedValue) -> *co
//as_boolean //as_boolean
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn typed_value_as_boolean(typed_value: *mut TypedValue) -> bool { pub unsafe extern "C" fn typed_value_as_boolean(typed_value: *mut TypedValue) -> i32 {
let typed_value = Box::from_raw(typed_value); let typed_value = Box::from_raw(typed_value);
typed_value.into_boolean().expect("Typed value cannot be coerced into a Boolean") if typed_value.into_boolean().expect("Typed value cannot be coerced into a Boolean") { 1 } else { 0 }
} }
//as_double //as_double
@ -310,7 +333,8 @@ pub unsafe extern "C" fn typed_value_as_double(typed_value: *mut TypedValue) ->
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn typed_value_as_timestamp(typed_value: *mut TypedValue) -> i64 { pub unsafe extern "C" fn typed_value_as_timestamp(typed_value: *mut TypedValue) -> i64 {
let typed_value = Box::from_raw(typed_value); let typed_value = Box::from_raw(typed_value);
let val = typed_value.into_timestamp().expect("Typed value cannot be coerced into a Timestamp"); let t = typed_value.value_type();
let val = typed_value.into_timestamp().expect(&format!("Typed value of type {:?} cannot be coerced into a Timestamp", t));
val val
} }
@ -328,6 +352,13 @@ pub unsafe extern "C" fn typed_value_as_uuid(typed_value: *mut TypedValue) -> *
string_to_c_char(typed_value.into_uuid_string().expect("Typed value cannot be coerced into a Uuid")) string_to_c_char(typed_value.into_uuid_string().expect("Typed value cannot be coerced into a Uuid"))
} }
//value_type
#[no_mangle]
pub unsafe extern "C" fn typed_value_value_type(typed_value: *mut TypedValue) -> ValueType {
let typed_value = &*typed_value;
typed_value.value_type()
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn row_at_index(rows: *mut Vec<Vec<TypedValue>>, index: c_int) -> *mut Vec<TypedValue> { pub unsafe extern "C" fn row_at_index(rows: *mut Vec<Vec<TypedValue>>, index: c_int) -> *mut Vec<TypedValue> {
let result = &*rows; let result = &*rows;
@ -353,9 +384,9 @@ pub unsafe extern "C" fn values_iter(values: *mut Vec<TypedValue>) -> *mut Type
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn values_iter_next(iter: *mut TypedValueIterator) -> *const TypedValue { pub unsafe extern "C" fn values_iter_next(iter: *mut TypedValueIterator) -> *mut TypedValue {
let iter = &mut *iter; let iter = &mut *iter;
iter.next().map_or(std::ptr::null_mut(), |v| &v as *const TypedValue) iter.next().map_or(std::ptr::null_mut(), |v| Box::into_raw(Box::new(v)))
} }
//as_long //as_long
@ -396,7 +427,7 @@ pub unsafe extern "C" fn values_iter_next_as_double(iter: *mut TypedValueIterato
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn values_iter_next_as_timestamp(iter: *mut TypedValueIterator) -> *const i64 { pub unsafe extern "C" fn values_iter_next_as_timestamp(iter: *mut TypedValueIterator) -> *const i64 {
let iter = &mut *iter; let iter = &mut *iter;
iter.next().map_or(std::ptr::null_mut(), |v| v.into_timestamp().expect("Typed value cannot be coerced into a Timestamp") as *const i64) iter.next().map_or(std::ptr::null_mut(), |v| { let t = v.value_type(); v.into_timestamp().expect(&format!("Typed value of type {:?} cannot be coerced into a Timestamp", t)) as *const i64 })
} }
//as_string //as_string
@ -499,24 +530,24 @@ pub unsafe extern "C" fn store_register_observer(store: *mut Store,
key: *const c_char, key: *const c_char,
attributes: *const Entid, attributes: *const Entid,
attributes_len: usize, attributes_len: usize,
callback: extern fn(key: *const c_char, reports: &ExternTxReportList)) { callback: extern fn(key: *const c_char, reports: &TxChangeList)) {
let store = &mut*store; let store = &mut*store;
let mut attribute_set = BTreeSet::new(); let mut attribute_set = BTreeSet::new();
let slice = slice::from_raw_parts(attributes, attributes_len); let slice = slice::from_raw_parts(attributes, attributes_len);
attribute_set.extend(slice.iter()); attribute_set.extend(slice.iter());
let key = c_char_to_string(key); let key = c_char_to_string(key);
let tx_observer = Arc::new(TxObserver::new(attribute_set, move |obs_key, batch| { let tx_observer = Arc::new(TxObserver::new(attribute_set, move |obs_key, batch| {
let extern_reports: Vec<ExternTxReport> = batch.into_iter().map(|(tx_id, changes)| { let extern_reports: Vec<TransactionChange> = batch.into_iter().map(|(tx_id, changes)| {
let changes: Vec<Entid> = changes.into_iter().map(|i|*i).collect(); let changes: Vec<Entid> = changes.into_iter().map(|i|*i).collect();
let len = changes.len(); let len = changes.len();
ExternTxReport { TransactionChange {
txid: *tx_id, txid: *tx_id,
changes: changes.into_boxed_slice(), changes: changes.into_boxed_slice(),
changes_len: len, changes_len: len,
} }
}).collect(); }).collect();
let len = extern_reports.len(); let len = extern_reports.len();
let reports = ExternTxReportList { let reports = TxChangeList {
reports: extern_reports.into_boxed_slice(), reports: extern_reports.into_boxed_slice(),
len: len, len: len,
}; };
@ -543,7 +574,7 @@ pub unsafe extern "C" fn store_entid_for_attribute(store: *mut Store, attr: *con
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn tx_report_list_entry_at(tx_report_list: *mut ExternTxReportList, index: c_int) -> *const ExternTxReport { pub unsafe extern "C" fn tx_change_list_entry_at(tx_report_list: *mut TxChangeList, index: c_int) -> *const TransactionChange {
let tx_report_list = &*tx_report_list; let tx_report_list = &*tx_report_list;
let index = index as usize; let index = index as usize;
let report = Box::new(tx_report_list.reports[index].clone()); let report = Box::new(tx_report_list.reports[index].clone());
@ -551,7 +582,7 @@ pub unsafe extern "C" fn tx_report_list_entry_at(tx_report_list: *mut ExternTxRe
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn changelist_entry_at(tx_report: *mut ExternTxReport, index: c_int) -> Entid { pub unsafe extern "C" fn changelist_entry_at(tx_report: *mut TransactionChange, index: c_int) -> Entid {
let tx_report = &*tx_report; let tx_report = &*tx_report;
let index = index as usize; let index = index as usize;
tx_report.changes[index].clone() tx_report.changes[index].clone()
@ -640,8 +671,7 @@ pub unsafe extern "C" fn store_set_uuid_for_attribute_on_entid(store: *mut Store
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn destroy(obj: *mut c_void) { pub unsafe extern "C" fn destroy(obj: *mut c_void) {
if !obj.is_null() { if !obj.is_null() {
let obj_to_release = Box::from_raw(obj); let _ = Box::from_raw(obj);
println!("object to release {:?}", obj_to_release);
} }
} }
@ -657,6 +687,8 @@ define_destructor!(query_builder_destroy, QueryBuilder);
define_destructor!(store_destroy, Store); define_destructor!(store_destroy, Store);
define_destructor!(tx_report_destroy, TxReport);
define_destructor!(typed_value_destroy, TypedValue); define_destructor!(typed_value_destroy, TypedValue);
define_destructor!(typed_value_list_destroy, Vec<TypedValue>); define_destructor!(typed_value_list_destroy, Vec<TypedValue>);

View file

@ -54,6 +54,7 @@ pub use mentat_query::{
pub use mentat_db::{ pub use mentat_db::{
CORE_SCHEMA_VERSION, CORE_SCHEMA_VERSION,
DB_SCHEMA_CORE, DB_SCHEMA_CORE,
AttributeSet,
TxObserver, TxObserver,
TxReport, TxReport,
new_connection, new_connection,

View file

@ -14,9 +14,11 @@ use std::collections::{
}; };
use mentat_core::{ use mentat_core::{
DateTime,
Entid, Entid,
NamespacedKeyword, NamespacedKeyword,
TypedValue, TypedValue,
Utc,
ValueType, ValueType,
}; };
@ -72,6 +74,11 @@ impl<'a> QueryBuilder<'a> {
self self
} }
pub fn bind_date_time(&mut self, var: &str, value: DateTime<Utc>) -> &mut Self {
self.values.insert(Variable::from_valid_name(var), TypedValue::Instant(value));
self
}
pub fn bind_type(&mut self, var: &str, value_type: ValueType) -> &mut Self { pub fn bind_type(&mut self, var: &str, value_type: ValueType) -> &mut Self {
self.types.insert(Variable::from_valid_name(var), value_type); self.types.insert(Variable::from_valid_name(var), value_type);
self self
@ -114,6 +121,11 @@ mod test {
Store, Store,
}; };
use mentat_core::{
DateTime,
Utc,
};
#[test] #[test]
fn test_scalar_query() { fn test_scalar_query() {
let mut store = Store::open("").expect("store connection"); let mut store = Store::open("").expect("store connection");
@ -386,4 +398,45 @@ mod test {
assert_eq!(results.get(0).map_or(None, |t| t.to_owned().into_boolean()).expect("boolean"), true); assert_eq!(results.get(0).map_or(None, |t| t.to_owned().into_boolean()).expect("boolean"), true);
assert_eq!(results.get(1).map_or(None, |t| t.to_owned().into_long()).expect("long"), 25); assert_eq!(results.get(1).map_or(None, |t| t.to_owned().into_long()).expect("long"), 25);
} }
#[test]
fn test_bind_date() {
let mut store = Store::open("").expect("store connection");
store.transact(r#"[
[:db/add "s" :db/ident :foo/boolean]
[:db/add "s" :db/valueType :db.type/boolean]
[:db/add "s" :db/cardinality :db.cardinality/one]
[:db/add "t" :db/ident :foo/long]
[:db/add "t" :db/valueType :db.type/long]
[:db/add "t" :db/cardinality :db.cardinality/one]
[:db/add "i" :db/ident :foo/instant]
[:db/add "i" :db/valueType :db.type/instant]
[:db/add "i" :db/cardinality :db.cardinality/one]
]"#).expect("successful transaction");
let report = store.transact(r#"[
[:db/add "l" :foo/boolean true]
[:db/add "l" :foo/long 25]
[:db/add "m" :foo/boolean false]
[:db/add "m" :foo/long 26]
[:db/add "n" :foo/boolean true]
[:db/add "n" :foo/long 27]
[:db/add "p" :foo/boolean false]
[:db/add "p" :foo/long 28]
[:db/add "p" :foo/instant #inst "2018-04-11T15:08:00.000Z"]
]"#).expect("successful transaction");
let p_entid = report.tempids.get("p").expect("found it").clone();
let time = DateTime::parse_from_rfc3339("2018-04-11T19:17:00.000Z")
.map(|t| t.with_timezone(&Utc))
.expect("expected valid date");
let results = QueryBuilder::new(&mut store, r#"[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"#)
.bind_date_time("?now", time)
.execute_tuple().expect("TupleResult")
.unwrap_or(vec![]);
assert_eq!(results.get(0).map_or(None, |t| t.to_owned().into_entid()).expect("entid"), p_entid);
let instant = results.get(1).map_or(None, |t| t.to_owned().into_timestamp()).expect("instant");
assert_eq!(instant, 1523459280);
}
} }