diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 5aaa23a5..a9fba3b3 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -4,11 +4,12 @@ version = "0.0.1" authors = ["Emily Toop "] [lib] -name = "mentat" -crate-type = ["staticlib", "cdylib"] +name = "mentat_ffi" +crate-type = ["lib", "staticlib", "cdylib"] [dependencies] -libc = "0.2" +libc = "0.2.32" [dependencies.mentat] -path = ".." +path = "../" + diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index b692f90e..8844ee65 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -42,6 +42,7 @@ pub use mentat::{ Syncable, TypedValue, TxObserver, + TxReport, Uuid, ValueType, Variable, @@ -62,16 +63,23 @@ pub type TypedValueListIterator = vec::IntoIter>; #[repr(C)] #[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 changes: Box<[Entid]>, pub changes_len: usize, + pub changes: Box<[Entid]>, } #[repr(C)] #[derive(Debug)] -pub struct ExternTxReportList { - pub reports: Box<[ExternTxReport]>, +pub struct TxChangeList { + pub reports: Box<[TransactionChange]>, 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 { let store = &mut*store; let transaction = c_char_to_string(transaction); - let result = store.begin_transaction().map(|mut in_progress| { - in_progress.transact(&transaction).map(|tx_report| { + let result = store.begin_transaction().and_then(|mut in_progress| { + in_progress.transact(&transaction).and_then(|tx_report| { in_progress.commit() .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())) } +#[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: 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)) } -#[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] 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); @@ -294,9 +317,9 @@ pub unsafe extern "C" fn typed_value_as_kw(typed_value: *mut TypedValue) -> *co //as_boolean #[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); - 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 @@ -310,7 +333,8 @@ pub unsafe extern "C" fn typed_value_as_double(typed_value: *mut TypedValue) -> #[no_mangle] pub unsafe extern "C" fn typed_value_as_timestamp(typed_value: *mut TypedValue) -> i64 { 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 } @@ -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")) } +//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] pub unsafe extern "C" fn row_at_index(rows: *mut Vec>, index: c_int) -> *mut Vec { let result = &*rows; @@ -353,9 +384,9 @@ pub unsafe extern "C" fn values_iter(values: *mut Vec) -> *mut Type } #[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; - 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 @@ -396,7 +427,7 @@ pub unsafe extern "C" fn values_iter_next_as_double(iter: *mut TypedValueIterato #[no_mangle] pub unsafe extern "C" fn values_iter_next_as_timestamp(iter: *mut TypedValueIterator) -> *const i64 { 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 @@ -499,24 +530,24 @@ pub unsafe extern "C" fn store_register_observer(store: *mut Store, key: *const c_char, attributes: *const Entid, 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 mut attribute_set = BTreeSet::new(); let slice = slice::from_raw_parts(attributes, attributes_len); attribute_set.extend(slice.iter()); let key = c_char_to_string(key); let tx_observer = Arc::new(TxObserver::new(attribute_set, move |obs_key, batch| { - let extern_reports: Vec = batch.into_iter().map(|(tx_id, changes)| { + let extern_reports: Vec = batch.into_iter().map(|(tx_id, changes)| { let changes: Vec = changes.into_iter().map(|i|*i).collect(); let len = changes.len(); - ExternTxReport { + TransactionChange { txid: *tx_id, changes: changes.into_boxed_slice(), changes_len: len, } }).collect(); let len = extern_reports.len(); - let reports = ExternTxReportList { + let reports = TxChangeList { reports: extern_reports.into_boxed_slice(), len: len, }; @@ -543,7 +574,7 @@ pub unsafe extern "C" fn store_entid_for_attribute(store: *mut Store, attr: *con } #[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 index = index as usize; 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] -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 index = index as usize; 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] pub unsafe extern "C" fn destroy(obj: *mut c_void) { if !obj.is_null() { - let obj_to_release = Box::from_raw(obj); - println!("object to release {:?}", obj_to_release); + let _ = Box::from_raw(obj); } } @@ -657,6 +687,8 @@ define_destructor!(query_builder_destroy, QueryBuilder); define_destructor!(store_destroy, Store); +define_destructor!(tx_report_destroy, TxReport); + define_destructor!(typed_value_destroy, TypedValue); define_destructor!(typed_value_list_destroy, Vec); diff --git a/src/lib.rs b/src/lib.rs index bc8683f8..23e441d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,7 @@ pub use mentat_query::{ pub use mentat_db::{ CORE_SCHEMA_VERSION, DB_SCHEMA_CORE, + AttributeSet, TxObserver, TxReport, new_connection, diff --git a/src/query_builder.rs b/src/query_builder.rs index 54a0ff2b..1f57d7d5 100644 --- a/src/query_builder.rs +++ b/src/query_builder.rs @@ -14,9 +14,11 @@ use std::collections::{ }; use mentat_core::{ + DateTime, Entid, NamespacedKeyword, TypedValue, + Utc, ValueType, }; @@ -72,6 +74,11 @@ impl<'a> QueryBuilder<'a> { self } + pub fn bind_date_time(&mut self, var: &str, value: DateTime) -> &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 { self.types.insert(Variable::from_valid_name(var), value_type); self @@ -114,6 +121,11 @@ mod test { Store, }; + use mentat_core::{ + DateTime, + Utc, + }; + #[test] fn test_scalar_query() { 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(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); + } }