* Pre: unused import in translate.rs. * Part 2: take a dependency on rusqlite for query arguments. * Part 1: flatten V2 schema into V1. Add UUID and URI. Bump expected ident and bootstrap datom count in tests. * Part 5: parse edn::Value::Uuid. * Part 3: extend ValueType and TypedValue to include Uuid. * Part 4: add Uuid to query arguments. * Part 6: extend db to support Uuid. * Part 8: add a tx-parser test for #f NaN and #uuid. * Part 7: parse and algebrize UUIDs in queries. * Part 1: parse #inst in EDN and throughout query engine. * Part 3: handle instants in db. * Part 2: instants never matches integers in queries. * Part 4: use DateTime for tx_instants. * Add a test for adding and querying UUIDs and instants. * Review comments.
This commit is contained in:
parent
bd389d2f0d
commit
daca8def57
28 changed files with 495 additions and 126 deletions
|
@ -17,6 +17,7 @@ members = []
|
|||
rustc_version = "0.1.7"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.3"
|
||||
clap = "2.19.3"
|
||||
error-chain = "0.8.1"
|
||||
nickel = "0.9.0"
|
||||
|
|
|
@ -25,7 +25,20 @@ use std::rc::Rc;
|
|||
use enum_set::EnumSet;
|
||||
|
||||
use self::ordered_float::OrderedFloat;
|
||||
use self::edn::NamespacedKeyword;
|
||||
use self::edn::{
|
||||
NamespacedKeyword,
|
||||
};
|
||||
|
||||
pub use edn::{
|
||||
Uuid,
|
||||
};
|
||||
|
||||
pub use edn::{
|
||||
DateTime,
|
||||
FromMicros,
|
||||
ToMicros,
|
||||
UTC,
|
||||
};
|
||||
|
||||
/// Core types defining a Mentat knowledge base.
|
||||
|
||||
|
@ -48,6 +61,7 @@ pub enum ValueType {
|
|||
Double,
|
||||
String,
|
||||
Keyword,
|
||||
Uuid,
|
||||
}
|
||||
|
||||
impl ValueType {
|
||||
|
@ -61,6 +75,7 @@ impl ValueType {
|
|||
s.insert(ValueType::Double);
|
||||
s.insert(ValueType::String);
|
||||
s.insert(ValueType::Keyword);
|
||||
s.insert(ValueType::Uuid);
|
||||
s
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +101,7 @@ impl ValueType {
|
|||
ValueType::Double => values::DB_TYPE_DOUBLE.clone(),
|
||||
ValueType::String => values::DB_TYPE_STRING.clone(),
|
||||
ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(),
|
||||
ValueType::Uuid => values::DB_TYPE_UUID.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +116,7 @@ impl fmt::Display for ValueType {
|
|||
ValueType::Double => ":db.type/double",
|
||||
ValueType::String => ":db.type/string",
|
||||
ValueType::Keyword => ":db.type/keyword",
|
||||
ValueType::Uuid => ":db.type/uuid",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -113,9 +130,11 @@ pub enum TypedValue {
|
|||
Boolean(bool),
|
||||
Long(i64),
|
||||
Double(OrderedFloat<f64>),
|
||||
Instant(DateTime<UTC>),
|
||||
// TODO: &str throughout?
|
||||
String(Rc<String>),
|
||||
Keyword(Rc<NamespacedKeyword>),
|
||||
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
|
||||
}
|
||||
|
||||
impl TypedValue {
|
||||
|
@ -136,9 +155,11 @@ impl TypedValue {
|
|||
&TypedValue::Ref(_) => ValueType::Ref,
|
||||
&TypedValue::Boolean(_) => ValueType::Boolean,
|
||||
&TypedValue::Long(_) => ValueType::Long,
|
||||
&TypedValue::Instant(_) => ValueType::Instant,
|
||||
&TypedValue::Double(_) => ValueType::Double,
|
||||
&TypedValue::String(_) => ValueType::String,
|
||||
&TypedValue::Keyword(_) => ValueType::Keyword,
|
||||
&TypedValue::Uuid(_) => ValueType::Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,6 +176,10 @@ impl TypedValue {
|
|||
pub fn typed_string(s: &str) -> TypedValue {
|
||||
TypedValue::String(Rc::new(s.to_string()))
|
||||
}
|
||||
|
||||
pub fn current_instant() -> TypedValue {
|
||||
TypedValue::Instant(UTC::now())
|
||||
}
|
||||
}
|
||||
|
||||
// Put this here rather than in `db` simply because it's widely needed.
|
||||
|
@ -173,6 +198,7 @@ impl SQLValueType for ValueType {
|
|||
ValueType::Long => 5,
|
||||
ValueType::Double => 5,
|
||||
ValueType::String => 10,
|
||||
ValueType::Uuid => 11,
|
||||
ValueType::Keyword => 13,
|
||||
}
|
||||
}
|
||||
|
@ -182,6 +208,8 @@ impl SQLValueType for ValueType {
|
|||
///
|
||||
/// ```
|
||||
/// use mentat_core::{ValueType, SQLValueType};
|
||||
/// assert!(!ValueType::Instant.accommodates_integer(1493399581314));
|
||||
/// assert!(!ValueType::Instant.accommodates_integer(1493399581314000));
|
||||
/// assert!(ValueType::Boolean.accommodates_integer(1));
|
||||
/// assert!(!ValueType::Boolean.accommodates_integer(-1));
|
||||
/// assert!(!ValueType::Boolean.accommodates_integer(10));
|
||||
|
@ -190,11 +218,13 @@ impl SQLValueType for ValueType {
|
|||
fn accommodates_integer(&self, int: i64) -> bool {
|
||||
use ValueType::*;
|
||||
match *self {
|
||||
Instant | Long | Double => true,
|
||||
Instant => false, // Always use #inst.
|
||||
Long | Double => true,
|
||||
Ref => int >= 0,
|
||||
Boolean => (int == 0) || (int == 1),
|
||||
ValueType::String => false,
|
||||
Keyword => false,
|
||||
Uuid => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ lazy_static! {
|
|||
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG),
|
||||
(ns_keyword!("db.type", "double"), entids::DB_TYPE_DOUBLE),
|
||||
(ns_keyword!("db.type", "string"), entids::DB_TYPE_STRING),
|
||||
(ns_keyword!("db.type", "uuid"), entids::DB_TYPE_UUID),
|
||||
(ns_keyword!("db.type", "uri"), entids::DB_TYPE_URI),
|
||||
(ns_keyword!("db.type", "boolean"), entids::DB_TYPE_BOOLEAN),
|
||||
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT),
|
||||
(ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES),
|
||||
|
@ -69,14 +71,9 @@ lazy_static! {
|
|||
(ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE),
|
||||
(ns_keyword!("db.unique", "identity"), entids::DB_UNIQUE_IDENTITY),
|
||||
(ns_keyword!("db", "doc"), entids::DB_DOC),
|
||||
]
|
||||
};
|
||||
|
||||
static ref V2_IDENTS: Vec<(symbols::NamespacedKeyword, i64)> = {
|
||||
[(*V1_IDENTS).clone(),
|
||||
vec![(ns_keyword!("db.schema", "version"), entids::DB_SCHEMA_VERSION),
|
||||
(ns_keyword!("db.schema", "version"), entids::DB_SCHEMA_VERSION),
|
||||
(ns_keyword!("db.schema", "attribute"), entids::DB_SCHEMA_ATTRIBUTE),
|
||||
]].concat()
|
||||
]
|
||||
};
|
||||
|
||||
static ref V1_PARTS: Vec<(symbols::NamespacedKeyword, i64, i64)> = {
|
||||
|
@ -86,13 +83,6 @@ lazy_static! {
|
|||
]
|
||||
};
|
||||
|
||||
static ref V2_PARTS: Vec<(symbols::NamespacedKeyword, i64, i64)> = {
|
||||
vec![(ns_keyword!("db.part", "db"), 0, (1 + V2_IDENTS.len()) as i64),
|
||||
(ns_keyword!("db.part", "user"), 0x10000, 0x10000),
|
||||
(ns_keyword!("db.part", "tx"), TX0, TX0),
|
||||
]
|
||||
};
|
||||
|
||||
static ref V1_SYMBOLIC_SCHEMA: Value = {
|
||||
let s = r#"
|
||||
{:db/ident {:db/valueType :db.type/keyword
|
||||
|
@ -108,7 +98,7 @@ lazy_static! {
|
|||
;; TODO: support user-specified functions in the future.
|
||||
;; :db.install/function {:db/valueType :db.type/ref
|
||||
;; :db/cardinality :db.cardinality/many}
|
||||
:db/txInstant {:db/valueType :db.type/long
|
||||
:db/txInstant {:db/valueType :db.type/instant
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
:db/valueType {:db/valueType :db.type/ref
|
||||
|
@ -126,16 +116,8 @@ lazy_static! {
|
|||
:db/fulltext {:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/noHistory {:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}}"#;
|
||||
edn::parse::value(s)
|
||||
.map(|v| v.without_spans())
|
||||
.map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into()))
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
static ref V2_SYMBOLIC_SCHEMA: Value = {
|
||||
let s = r#"
|
||||
{:db.alter/attribute {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db.alter/attribute {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many}
|
||||
:db.schema/version {:db/valueType :db.type/long
|
||||
:db/cardinality :db.cardinality/one}
|
||||
|
@ -146,13 +128,9 @@ lazy_static! {
|
|||
:db/index true
|
||||
:db/unique :db.unique/value
|
||||
:db/cardinality :db.cardinality/many}}"#;
|
||||
let right = edn::parse::value(s)
|
||||
edn::parse::value(s)
|
||||
.map(|v| v.without_spans())
|
||||
.map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V2_SYMBOLIC_SCHEMA".into()))
|
||||
.unwrap();
|
||||
|
||||
edn::utils::merge(&V1_SYMBOLIC_SCHEMA, &right)
|
||||
.ok_or(ErrorKind::BadBootstrapDefinition("Unable to parse V2_SYMBOLIC_SCHEMA".into()))
|
||||
.map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into()))
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
@ -248,27 +226,27 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result<Vec<Value>>
|
|||
}
|
||||
|
||||
pub fn bootstrap_partition_map() -> PartitionMap {
|
||||
V2_PARTS[..].iter()
|
||||
V1_PARTS[..].iter()
|
||||
.map(|&(ref part, start, index)| (part.to_string(), Partition::new(start, index)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn bootstrap_ident_map() -> IdentMap {
|
||||
V2_IDENTS[..].iter()
|
||||
V1_IDENTS[..].iter()
|
||||
.map(|&(ref ident, entid)| (ident.clone(), entid))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn bootstrap_schema() -> Schema {
|
||||
let ident_map = bootstrap_ident_map();
|
||||
let bootstrap_triples = symbolic_schema_to_triples(&ident_map, &V2_SYMBOLIC_SCHEMA).unwrap();
|
||||
let bootstrap_triples = symbolic_schema_to_triples(&ident_map, &V1_SYMBOLIC_SCHEMA).unwrap();
|
||||
Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap()
|
||||
}
|
||||
|
||||
pub fn bootstrap_entities() -> Vec<Entity> {
|
||||
let bootstrap_assertions: Value = Value::Vector([
|
||||
symbolic_schema_to_assertions(&V2_SYMBOLIC_SCHEMA).unwrap(),
|
||||
idents_to_assertions(&V2_IDENTS[..]),
|
||||
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).unwrap(),
|
||||
idents_to_assertions(&V1_IDENTS[..]),
|
||||
].concat());
|
||||
|
||||
// Failure here is a coding error (since the inputs are fixed), not a runtime error.
|
||||
|
|
46
db/src/db.rs
46
db/src/db.rs
|
@ -26,17 +26,26 @@ use rusqlite::limits::Limit;
|
|||
|
||||
use ::{repeat_values, to_namespaced_keyword};
|
||||
use bootstrap;
|
||||
use edn::types::Value;
|
||||
|
||||
use edn::{
|
||||
DateTime,
|
||||
UTC,
|
||||
Uuid,
|
||||
Value,
|
||||
};
|
||||
|
||||
use entids;
|
||||
use mentat_core::{
|
||||
attribute,
|
||||
Attribute,
|
||||
AttributeBitFlags,
|
||||
Entid,
|
||||
FromMicros,
|
||||
IdentMap,
|
||||
Schema,
|
||||
SchemaMap,
|
||||
TypedValue,
|
||||
ToMicros,
|
||||
ValueType,
|
||||
};
|
||||
use errors::{ErrorKind, Result, ResultExt};
|
||||
|
@ -72,10 +81,8 @@ pub fn new_connection<T>(uri: T) -> rusqlite::Result<rusqlite::Connection> where
|
|||
|
||||
/// Version history:
|
||||
///
|
||||
/// 1: initial schema.
|
||||
/// 2: added :db.schema/version and /attribute in bootstrap; assigned idents 36 and 37, so we bump
|
||||
/// the part range here; tie bootstrapping to the SQLite user_version.
|
||||
pub const CURRENT_VERSION: i32 = 2;
|
||||
/// 1: initial Rust Mentat schema.
|
||||
pub const CURRENT_VERSION: i32 = 1;
|
||||
|
||||
/// MIN_SQLITE_VERSION should be changed when there's a new minimum version of sqlite required
|
||||
/// for the project to work.
|
||||
|
@ -93,9 +100,9 @@ fn to_bool_ref(x: bool) -> &'static bool {
|
|||
}
|
||||
|
||||
lazy_static! {
|
||||
/// SQL statements to be executed, in order, to create the Mentat SQL schema (version 2).
|
||||
/// SQL statements to be executed, in order, to create the Mentat SQL schema (version 1).
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
static ref V2_STATEMENTS: Vec<&'static str> = { vec![
|
||||
static ref V1_STATEMENTS: Vec<&'static str> = { vec![
|
||||
r#"CREATE TABLE datoms (e INTEGER NOT NULL, a SMALLINT NOT NULL, v BLOB NOT NULL, tx INTEGER NOT NULL,
|
||||
value_type_tag SMALLINT NOT NULL,
|
||||
index_avet TINYINT NOT NULL DEFAULT 0, index_vaet TINYINT NOT NULL DEFAULT 0,
|
||||
|
@ -203,7 +210,7 @@ fn get_user_version(conn: &rusqlite::Connection) -> Result<i32> {
|
|||
pub fn create_current_version(conn: &mut rusqlite::Connection) -> Result<DB> {
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
for statement in (&V2_STATEMENTS).iter() {
|
||||
for statement in (&V1_STATEMENTS).iter() {
|
||||
tx.execute(statement, &[])?;
|
||||
}
|
||||
|
||||
|
@ -347,11 +354,24 @@ impl TypedSQLValue for TypedValue {
|
|||
match (value_type_tag, value) {
|
||||
(0, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Ref(x)),
|
||||
(1, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Boolean(0 != x)),
|
||||
|
||||
// Negative integers are simply times before 1970.
|
||||
(4, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Instant(DateTime::<UTC>::from_micros(x))),
|
||||
|
||||
// SQLite distinguishes integral from decimal types, allowing long and double to
|
||||
// share a tag.
|
||||
(5, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Long(x)),
|
||||
(5, rusqlite::types::Value::Real(x)) => Ok(TypedValue::Double(x.into())),
|
||||
(10, rusqlite::types::Value::Text(x)) => Ok(TypedValue::String(Rc::new(x))),
|
||||
(11, rusqlite::types::Value::Blob(x)) => {
|
||||
let u = Uuid::from_bytes(x.as_slice());
|
||||
if u.is_err() {
|
||||
// Rather than exposing Uuid's ParseError…
|
||||
bail!(ErrorKind::BadSQLValuePair(rusqlite::types::Value::Blob(x),
|
||||
value_type_tag));
|
||||
}
|
||||
Ok(TypedValue::Uuid(u.unwrap()))
|
||||
},
|
||||
(13, rusqlite::types::Value::Text(x)) => {
|
||||
to_namespaced_keyword(&x).map(|k| TypedValue::Keyword(Rc::new(k)))
|
||||
},
|
||||
|
@ -369,7 +389,9 @@ impl TypedSQLValue for TypedValue {
|
|||
fn from_edn_value(value: &Value) -> Option<TypedValue> {
|
||||
match value {
|
||||
&Value::Boolean(x) => Some(TypedValue::Boolean(x)),
|
||||
&Value::Instant(x) => Some(TypedValue::Instant(x)),
|
||||
&Value::Integer(x) => Some(TypedValue::Long(x)),
|
||||
&Value::Uuid(x) => Some(TypedValue::Uuid(x)),
|
||||
&Value::Float(ref x) => Some(TypedValue::Double(x.clone())),
|
||||
&Value::Text(ref x) => Some(TypedValue::String(Rc::new(x.clone()))),
|
||||
&Value::NamespacedKeyword(ref x) => Some(TypedValue::Keyword(Rc::new(x.clone()))),
|
||||
|
@ -382,10 +404,12 @@ impl TypedSQLValue for TypedValue {
|
|||
match self {
|
||||
&TypedValue::Ref(x) => (rusqlite::types::Value::Integer(x).into(), 0),
|
||||
&TypedValue::Boolean(x) => (rusqlite::types::Value::Integer(if x { 1 } else { 0 }).into(), 1),
|
||||
&TypedValue::Instant(x) => (rusqlite::types::Value::Integer(x.to_micros()).into(), 4),
|
||||
// SQLite distinguishes integral from decimal types, allowing long and double to share a tag.
|
||||
&TypedValue::Long(x) => (rusqlite::types::Value::Integer(x).into(), 5),
|
||||
&TypedValue::Double(x) => (rusqlite::types::Value::Real(x.into_inner()).into(), 5),
|
||||
&TypedValue::String(ref x) => (rusqlite::types::ValueRef::Text(x.as_str()).into(), 10),
|
||||
&TypedValue::Uuid(ref u) => (rusqlite::types::Value::Blob(u.as_bytes().to_vec()).into(), 11),
|
||||
&TypedValue::Keyword(ref x) => (rusqlite::types::ValueRef::Text(&x.to_string()).into(), 13),
|
||||
}
|
||||
}
|
||||
|
@ -395,9 +419,11 @@ impl TypedSQLValue for TypedValue {
|
|||
match self {
|
||||
&TypedValue::Ref(x) => (Value::Integer(x), ValueType::Ref),
|
||||
&TypedValue::Boolean(x) => (Value::Boolean(x), ValueType::Boolean),
|
||||
&TypedValue::Instant(x) => (Value::Instant(x), ValueType::Instant),
|
||||
&TypedValue::Long(x) => (Value::Integer(x), ValueType::Long),
|
||||
&TypedValue::Double(x) => (Value::Float(x), ValueType::Double),
|
||||
&TypedValue::String(ref x) => (Value::Text(x.as_ref().clone()), ValueType::String),
|
||||
&TypedValue::Uuid(ref u) => (Value::Uuid(u.clone()), ValueType::Uuid),
|
||||
&TypedValue::Keyword(ref x) => (Value::NamespacedKeyword(x.as_ref().clone()), ValueType::Keyword),
|
||||
}
|
||||
}
|
||||
|
@ -1173,12 +1199,12 @@ mod tests {
|
|||
|
||||
// Does not include :db/txInstant.
|
||||
let datoms = debug::datoms_after(&conn, &db.schema, 0).unwrap();
|
||||
assert_eq!(datoms.0.len(), 74);
|
||||
assert_eq!(datoms.0.len(), 76);
|
||||
|
||||
// Includes :db/txInstant.
|
||||
let transactions = debug::transactions_after(&conn, &db.schema, 0).unwrap();
|
||||
assert_eq!(transactions.0.len(), 1);
|
||||
assert_eq!(transactions.0[0].0.len(), 75);
|
||||
assert_eq!(transactions.0[0].0.len(), 77);
|
||||
|
||||
let test_conn = TestConn {
|
||||
sqlite: conn,
|
||||
|
|
|
@ -44,18 +44,18 @@ pub const DB_TYPE_KEYWORD: Entid = 24;
|
|||
pub const DB_TYPE_LONG: Entid = 25;
|
||||
pub const DB_TYPE_DOUBLE: Entid = 26;
|
||||
pub const DB_TYPE_STRING: Entid = 27;
|
||||
pub const DB_TYPE_BOOLEAN: Entid = 28;
|
||||
pub const DB_TYPE_INSTANT: Entid = 29;
|
||||
pub const DB_TYPE_BYTES: Entid = 30;
|
||||
pub const DB_CARDINALITY_ONE: Entid = 31;
|
||||
pub const DB_CARDINALITY_MANY: Entid = 32;
|
||||
pub const DB_UNIQUE_VALUE: Entid = 33;
|
||||
pub const DB_UNIQUE_IDENTITY: Entid = 34;
|
||||
pub const DB_DOC: Entid = 35;
|
||||
|
||||
// Added in SQL schema v2.
|
||||
pub const DB_SCHEMA_VERSION: Entid = 36;
|
||||
pub const DB_SCHEMA_ATTRIBUTE: Entid = 37;
|
||||
pub const DB_TYPE_UUID: Entid = 28;
|
||||
pub const DB_TYPE_URI: Entid = 29;
|
||||
pub const DB_TYPE_BOOLEAN: Entid = 30;
|
||||
pub const DB_TYPE_INSTANT: Entid = 31;
|
||||
pub const DB_TYPE_BYTES: Entid = 32;
|
||||
pub const DB_CARDINALITY_ONE: Entid = 33;
|
||||
pub const DB_CARDINALITY_MANY: Entid = 34;
|
||||
pub const DB_UNIQUE_VALUE: Entid = 35;
|
||||
pub const DB_UNIQUE_IDENTITY: Entid = 36;
|
||||
pub const DB_DOC: Entid = 37;
|
||||
pub const DB_SCHEMA_VERSION: Entid = 38;
|
||||
pub const DB_SCHEMA_ATTRIBUTE: Entid = 39;
|
||||
|
||||
/// Return `false` if the given attribute will not change the metadata: recognized idents, schema,
|
||||
/// partitions in the partition map.
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
extern crate itertools;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate rusqlite;
|
||||
extern crate time;
|
||||
|
||||
extern crate tabwriter;
|
||||
extern crate time;
|
||||
|
||||
#[macro_use]
|
||||
extern crate edn;
|
||||
|
@ -24,8 +24,15 @@ extern crate mentat_core;
|
|||
extern crate mentat_tx;
|
||||
extern crate mentat_tx_parser;
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::iter::repeat;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use mentat_core::{
|
||||
DateTime,
|
||||
UTC,
|
||||
};
|
||||
|
||||
pub use errors::{Error, ErrorKind, ResultExt, Result};
|
||||
|
||||
pub mod db;
|
||||
|
@ -89,10 +96,7 @@ pub fn repeat_values(values_per_tuple: usize, tuples: usize) -> String {
|
|||
values
|
||||
}
|
||||
|
||||
/// Return the current time in milliseconds after the Unix epoch according to the local clock.
|
||||
///
|
||||
/// Compare `Date.now()` in JavaScript, `System.currentTimeMillis` in Java.
|
||||
pub fn now() -> i64 {
|
||||
let now = time::get_time();
|
||||
(now.sec as i64 * 1_000) + (now.nsec as i64 / (1_000_000))
|
||||
/// Return the current time as a UTC `DateTime` instance.
|
||||
pub fn now() -> DateTime<UTC> {
|
||||
UTC::now()
|
||||
}
|
||||
|
|
|
@ -115,12 +115,14 @@ pub fn update_schema_map_from_entid_triples<U>(schema_map: &mut SchemaMap, asser
|
|||
|
||||
entids::DB_VALUE_TYPE => {
|
||||
match *value {
|
||||
TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); },
|
||||
TypedValue::Ref(entids::DB_TYPE_BOOLEAN) => { builder.value_type(ValueType::Boolean); },
|
||||
TypedValue::Ref(entids::DB_TYPE_DOUBLE) => { builder.value_type(ValueType::Double); },
|
||||
TypedValue::Ref(entids::DB_TYPE_LONG) => { builder.value_type(ValueType::Long); },
|
||||
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
|
||||
TypedValue::Ref(entids::DB_TYPE_INSTANT) => { builder.value_type(ValueType::Instant); },
|
||||
TypedValue::Ref(entids::DB_TYPE_KEYWORD) => { builder.value_type(ValueType::Keyword); },
|
||||
TypedValue::Ref(entids::DB_TYPE_LONG) => { builder.value_type(ValueType::Long); },
|
||||
TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); },
|
||||
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
|
||||
TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); },
|
||||
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr)))
|
||||
}
|
||||
},
|
||||
|
|
|
@ -240,6 +240,7 @@ impl SchemaTypeChecking for Schema {
|
|||
(&ValueType::Long, tv @ TypedValue::Long(_)) => Ok(tv),
|
||||
(&ValueType::Double, tv @ TypedValue::Double(_)) => Ok(tv),
|
||||
(&ValueType::String, tv @ TypedValue::String(_)) => Ok(tv),
|
||||
(&ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv),
|
||||
(&ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
|
||||
// Ref coerces a little: we interpret some things depending on the schema as a Ref.
|
||||
(&ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),
|
||||
|
|
14
db/src/tx.rs
14
db/src/tx.rs
|
@ -70,10 +70,13 @@ use internal_types::{
|
|||
TermWithTempIds,
|
||||
TermWithoutTempIds,
|
||||
replace_lookup_ref};
|
||||
|
||||
use mentat_core::{
|
||||
DateTime,
|
||||
Schema,
|
||||
UTC,
|
||||
attribute,
|
||||
intern_set,
|
||||
Schema,
|
||||
};
|
||||
use mentat_tx::entities as entmod;
|
||||
use mentat_tx::entities::{
|
||||
|
@ -127,10 +130,7 @@ pub struct Tx<'conn, 'a> {
|
|||
tx_id: Entid,
|
||||
|
||||
/// The timestamp when the transaction began to be committed.
|
||||
///
|
||||
/// This is milliseconds after the Unix epoch according to the transactor's local clock.
|
||||
// TODO: :db.type/instant.
|
||||
tx_instant: i64,
|
||||
tx_instant: DateTime<UTC>,
|
||||
}
|
||||
|
||||
impl<'conn, 'a> Tx<'conn, 'a> {
|
||||
|
@ -140,7 +140,7 @@ impl<'conn, 'a> Tx<'conn, 'a> {
|
|||
schema_for_mutation: &'a Schema,
|
||||
schema: &'a Schema,
|
||||
tx_id: Entid,
|
||||
tx_instant: i64) -> Tx<'conn, 'a> {
|
||||
tx_instant: DateTime<UTC>) -> Tx<'conn, 'a> {
|
||||
Tx {
|
||||
store: store,
|
||||
partition_map: partition_map,
|
||||
|
@ -532,7 +532,7 @@ impl<'conn, 'a> Tx<'conn, 'a> {
|
|||
non_fts_one.push((self.tx_id,
|
||||
entids::DB_TX_INSTANT,
|
||||
self.schema.require_attribute_for_entid(entids::DB_TX_INSTANT).unwrap(),
|
||||
TypedValue::Long(self.tx_instant),
|
||||
TypedValue::Instant(self.tx_instant),
|
||||
true));
|
||||
|
||||
if !non_fts_one.is_empty() {
|
||||
|
|
|
@ -16,12 +16,14 @@ use std::collections::BTreeMap;
|
|||
extern crate mentat_core;
|
||||
|
||||
pub use self::mentat_core::{
|
||||
DateTime,
|
||||
Entid,
|
||||
ValueType,
|
||||
TypedValue,
|
||||
Attribute,
|
||||
AttributeBitFlags,
|
||||
Schema,
|
||||
UTC,
|
||||
};
|
||||
|
||||
/// Represents one partition of the entid space.
|
||||
|
@ -78,16 +80,13 @@ pub type AVMap<'a> = HashMap<&'a AVPair, Entid>;
|
|||
|
||||
/// A transaction report summarizes an applied transaction.
|
||||
// TODO: include map of resolved tempids.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct TxReport {
|
||||
/// The transaction ID of the transaction.
|
||||
pub tx_id: Entid,
|
||||
|
||||
/// The timestamp when the transaction began to be committed.
|
||||
///
|
||||
/// This is milliseconds after the Unix epoch according to the transactor's local clock.
|
||||
// TODO: :db.type/instant.
|
||||
pub tx_instant: i64,
|
||||
pub tx_instant: DateTime<UTC>,
|
||||
|
||||
/// A map from string literal tempid to resolved or allocated entid.
|
||||
///
|
||||
|
|
|
@ -11,10 +11,12 @@ build = "build.rs"
|
|||
readme = "./README.md"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.3"
|
||||
itertools = "0.5.9"
|
||||
num = "0.1.35"
|
||||
ordered-float = "0.4.0"
|
||||
pretty = "0.2.0"
|
||||
uuid = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
peg = "0.5.1"
|
||||
|
|
|
@ -14,8 +14,14 @@ use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
|||
use std::iter::FromIterator;
|
||||
use std::f64::{NAN, INFINITY, NEG_INFINITY};
|
||||
|
||||
use chrono::{
|
||||
DateTime,
|
||||
TimeZone,
|
||||
UTC
|
||||
};
|
||||
use num::BigInt;
|
||||
use ordered_float::OrderedFloat;
|
||||
use uuid::Uuid;
|
||||
|
||||
use types::{SpannedValue, Span, ValueAndSpan};
|
||||
|
||||
|
@ -142,6 +148,60 @@ pub text -> ValueAndSpan =
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// RFC 3339 timestamps. #inst "1985-04-12T23:20:50.52Z"
|
||||
// We accept an arbitrary depth of decimals.
|
||||
// Note that we discard the timezone information -- all times are translated to UTC.
|
||||
pub inst_string -> DateTime<UTC> =
|
||||
"#inst" whitespace+ "\"" d:$( [0-9]*<4> "-" [0-2][0-9] "-" [0-3][0-9]
|
||||
"T"
|
||||
[0-2][0-9] ":" [0-5][0-9] ":" [0-6][0-9]
|
||||
("." [0-9]+)?
|
||||
"Z" / (("+" / "-") [0-2][0-9] ":" [0-5][0-9])
|
||||
)
|
||||
"\"" {?
|
||||
DateTime::parse_from_rfc3339(d)
|
||||
.map(|t| t.with_timezone(&UTC))
|
||||
.map_err(|_| "invalid datetime") // Oh, rustpeg.
|
||||
}
|
||||
|
||||
pub inst_micros -> DateTime<UTC> =
|
||||
"#instmicros" whitespace+ d:$( digit+ ) {
|
||||
let micros = d.parse::<i64>().unwrap();
|
||||
let seconds: i64 = micros / 1000000;
|
||||
let nanos: u32 = ((micros % 1000000).abs() as u32) * 1000;
|
||||
UTC.timestamp(seconds, nanos)
|
||||
}
|
||||
|
||||
pub inst_millis -> DateTime<UTC> =
|
||||
"#instmillis" whitespace+ d:$( digit+ ) {
|
||||
let millis = d.parse::<i64>().unwrap();
|
||||
let seconds: i64 = millis / 1000;
|
||||
let nanos: u32 = ((millis % 1000).abs() as u32) * 1000000;
|
||||
UTC.timestamp(seconds, nanos)
|
||||
}
|
||||
|
||||
pub inst -> ValueAndSpan =
|
||||
start:#position t:(inst_millis / inst_micros / inst_string) end:#position {
|
||||
ValueAndSpan {
|
||||
inner: SpannedValue::Instant(t),
|
||||
span: Span::new(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
pub uuid_string -> Uuid =
|
||||
"\"" u:$( [a-f0-9]*<8> "-" [a-f0-9]*<4> "-" [a-f0-9]*<4> "-" [a-f0-9]*<4> "-" [a-f0-9]*<12> ) "\"" {
|
||||
Uuid::parse_str(u).expect("this is a valid UUID string")
|
||||
}
|
||||
|
||||
pub uuid -> ValueAndSpan =
|
||||
start:#position "#uuid" whitespace+ u:(uuid_string) end:#position {
|
||||
ValueAndSpan {
|
||||
inner: SpannedValue::Uuid(u),
|
||||
span: Span::new(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
namespace_divider = "."
|
||||
namespace_separator = "/"
|
||||
|
||||
|
@ -220,7 +280,7 @@ pub map -> ValueAndSpan =
|
|||
// It's important that float comes before integer or the parser assumes that
|
||||
// floats are integers and fails to parse
|
||||
pub value -> ValueAndSpan =
|
||||
__ v:(nil / nan / infinity / boolean / float / octalinteger / hexinteger / basedinteger / bigint / integer / text / keyword / symbol / list / vector / map / set) __ {
|
||||
__ v:(nil / nan / infinity / boolean / float / octalinteger / hexinteger / basedinteger / inst / uuid / bigint / integer / text / keyword / symbol / list / vector / map / set) __ {
|
||||
v
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
extern crate chrono;
|
||||
extern crate itertools;
|
||||
extern crate num;
|
||||
extern crate ordered_float;
|
||||
extern crate pretty;
|
||||
extern crate uuid;
|
||||
|
||||
pub mod symbols;
|
||||
pub mod types;
|
||||
|
@ -23,8 +25,20 @@ pub mod parse {
|
|||
include!(concat!(env!("OUT_DIR"), "/edn.rs"));
|
||||
}
|
||||
|
||||
// Re-export the types we use.
|
||||
pub use chrono::{DateTime, UTC};
|
||||
pub use num::BigInt;
|
||||
pub use ordered_float::OrderedFloat;
|
||||
pub use uuid::Uuid;
|
||||
|
||||
// Export from our modules.
|
||||
pub use parse::ParseError;
|
||||
pub use types::{Span, SpannedValue, Value, ValueAndSpan};
|
||||
pub use types::{
|
||||
FromMicros,
|
||||
Span,
|
||||
SpannedValue,
|
||||
ToMicros,
|
||||
Value,
|
||||
ValueAndSpan,
|
||||
};
|
||||
pub use symbols::{Keyword, NamespacedKeyword, PlainSymbol, NamespacedSymbol};
|
||||
|
|
|
@ -69,6 +69,7 @@ impl Value {
|
|||
Value::NamespacedKeyword(ref v) => pp.text(":").append(v.namespace.as_ref()).append("/").append(v.name.as_ref()),
|
||||
Value::Keyword(ref v) => pp.text(":").append(v.0.as_ref()),
|
||||
Value::Text(ref v) => pp.text("\"").append(v.as_ref()).append("\""),
|
||||
Value::Uuid(ref u) => pp.text("#uuid \"").append(u.hyphenated().to_string()).append("\""),
|
||||
_ => pp.text(self.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,16 @@ use std::cmp::{Ordering, Ord, PartialOrd};
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::f64;
|
||||
|
||||
use symbols;
|
||||
use chrono::{
|
||||
DateTime,
|
||||
TimeZone, // For UTC::timestamp. The compiler incorrectly complains that this is unused.
|
||||
UTC,
|
||||
};
|
||||
use num::BigInt;
|
||||
use ordered_float::OrderedFloat;
|
||||
use uuid::Uuid;
|
||||
|
||||
use symbols;
|
||||
|
||||
/// Value represents one of the allowed values in an EDN string.
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
|
@ -25,9 +32,11 @@ pub enum Value {
|
|||
Nil,
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Instant(DateTime<UTC>),
|
||||
BigInteger(BigInt),
|
||||
Float(OrderedFloat<f64>),
|
||||
Text(String),
|
||||
Uuid(Uuid),
|
||||
PlainSymbol(symbols::PlainSymbol),
|
||||
NamespacedSymbol(symbols::NamespacedSymbol),
|
||||
Keyword(symbols::Keyword),
|
||||
|
@ -52,9 +61,11 @@ pub enum SpannedValue {
|
|||
Nil,
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Instant(DateTime<UTC>),
|
||||
BigInteger(BigInt),
|
||||
Float(OrderedFloat<f64>),
|
||||
Text(String),
|
||||
Uuid(Uuid),
|
||||
PlainSymbol(symbols::PlainSymbol),
|
||||
NamespacedSymbol(symbols::NamespacedSymbol),
|
||||
Keyword(symbols::Keyword),
|
||||
|
@ -123,9 +134,11 @@ impl From<SpannedValue> for Value {
|
|||
SpannedValue::Nil => Value::Nil,
|
||||
SpannedValue::Boolean(v) => Value::Boolean(v),
|
||||
SpannedValue::Integer(v) => Value::Integer(v),
|
||||
SpannedValue::Instant(v) => Value::Instant(v),
|
||||
SpannedValue::BigInteger(v) => Value::BigInteger(v),
|
||||
SpannedValue::Float(v) => Value::Float(v),
|
||||
SpannedValue::Text(v) => Value::Text(v),
|
||||
SpannedValue::Uuid(v) => Value::Uuid(v),
|
||||
SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v),
|
||||
SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v),
|
||||
SpannedValue::Keyword(v) => Value::Keyword(v),
|
||||
|
@ -268,9 +281,11 @@ macro_rules! def_common_value_methods {
|
|||
def_is!(is_nil, $t::Nil);
|
||||
def_is!(is_boolean, $t::Boolean(_));
|
||||
def_is!(is_integer, $t::Integer(_));
|
||||
def_is!(is_instant, $t::Instant(_));
|
||||
def_is!(is_big_integer, $t::BigInteger(_));
|
||||
def_is!(is_float, $t::Float(_));
|
||||
def_is!(is_text, $t::Text(_));
|
||||
def_is!(is_uuid, $t::Uuid(_));
|
||||
def_is!(is_symbol, $t::PlainSymbol(_));
|
||||
def_is!(is_namespaced_symbol, $t::NamespacedSymbol(_));
|
||||
def_is!(is_keyword, $t::Keyword(_));
|
||||
|
@ -288,11 +303,13 @@ macro_rules! def_common_value_methods {
|
|||
|
||||
def_as!(as_boolean, $t::Boolean, bool,);
|
||||
def_as!(as_integer, $t::Integer, i64,);
|
||||
def_as!(as_instant, $t::Instant, DateTime<UTC>,);
|
||||
def_as!(as_float, $t::Float, f64, |v: OrderedFloat<f64>| v.into_inner());
|
||||
|
||||
def_as_ref!(as_big_integer, $t::BigInteger, BigInt);
|
||||
def_as_ref!(as_ordered_float, $t::Float, OrderedFloat<f64>);
|
||||
def_as_ref!(as_text, $t::Text, String);
|
||||
def_as_ref!(as_uuid, $t::Uuid, Uuid);
|
||||
def_as_ref!(as_symbol, $t::PlainSymbol, symbols::PlainSymbol);
|
||||
def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol);
|
||||
def_as_ref!(as_keyword, $t::Keyword, symbols::Keyword);
|
||||
|
@ -304,10 +321,12 @@ macro_rules! def_common_value_methods {
|
|||
|
||||
def_into!(into_boolean, $t::Boolean, bool,);
|
||||
def_into!(into_integer, $t::Integer, i64,);
|
||||
def_into!(into_instant, $t::Instant, DateTime<UTC>,);
|
||||
def_into!(into_big_integer, $t::BigInteger, BigInt,);
|
||||
def_into!(into_ordered_float, $t::Float, OrderedFloat<f64>,);
|
||||
def_into!(into_float, $t::Float, f64, |v: OrderedFloat<f64>| v.into_inner());
|
||||
def_into!(into_text, $t::Text, String,);
|
||||
def_into!(into_uuid, $t::Uuid, Uuid,);
|
||||
def_into!(into_symbol, $t::PlainSymbol, symbols::PlainSymbol,);
|
||||
def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,);
|
||||
def_into!(into_keyword, $t::Keyword, symbols::Keyword,);
|
||||
|
@ -336,15 +355,17 @@ macro_rules! def_common_value_methods {
|
|||
$t::Integer(_) => 2,
|
||||
$t::BigInteger(_) => 3,
|
||||
$t::Float(_) => 4,
|
||||
$t::Text(_) => 5,
|
||||
$t::PlainSymbol(_) => 6,
|
||||
$t::NamespacedSymbol(_) => 7,
|
||||
$t::Keyword(_) => 8,
|
||||
$t::NamespacedKeyword(_) => 9,
|
||||
$t::Vector(_) => 10,
|
||||
$t::List(_) => 11,
|
||||
$t::Set(_) => 12,
|
||||
$t::Map(_) => 13,
|
||||
$t::Instant(_) => 5,
|
||||
$t::Text(_) => 6,
|
||||
$t::Uuid(_) => 7,
|
||||
$t::PlainSymbol(_) => 8,
|
||||
$t::NamespacedSymbol(_) => 9,
|
||||
$t::Keyword(_) => 10,
|
||||
$t::NamespacedKeyword(_) => 11,
|
||||
$t::Vector(_) => 12,
|
||||
$t::List(_) => 13,
|
||||
$t::Set(_) => 14,
|
||||
$t::Map(_) => 15,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,9 +374,11 @@ macro_rules! def_common_value_methods {
|
|||
$t::Nil => false,
|
||||
$t::Boolean(_) => false,
|
||||
$t::Integer(_) => false,
|
||||
$t::Instant(_) => false,
|
||||
$t::BigInteger(_) => false,
|
||||
$t::Float(_) => false,
|
||||
$t::Text(_) => false,
|
||||
$t::Uuid(_) => false,
|
||||
$t::PlainSymbol(_) => false,
|
||||
$t::NamespacedSymbol(_) => false,
|
||||
$t::Keyword(_) => false,
|
||||
|
@ -389,9 +412,11 @@ macro_rules! def_common_value_ord {
|
|||
(&$t::Nil, &$t::Nil) => Ordering::Equal,
|
||||
(&$t::Boolean(a), &$t::Boolean(b)) => b.cmp(&a),
|
||||
(&$t::Integer(a), &$t::Integer(b)) => b.cmp(&a),
|
||||
(&$t::Instant(a), &$t::Instant(b)) => b.cmp(&a),
|
||||
(&$t::BigInteger(ref a), &$t::BigInteger(ref b)) => b.cmp(a),
|
||||
(&$t::Float(ref a), &$t::Float(ref b)) => b.cmp(a),
|
||||
(&$t::Text(ref a), &$t::Text(ref b)) => b.cmp(a),
|
||||
(&$t::Uuid(ref a), &$t::Uuid(ref b)) => b.cmp(a),
|
||||
(&$t::PlainSymbol(ref a), &$t::PlainSymbol(ref b)) => b.cmp(a),
|
||||
(&$t::NamespacedSymbol(ref a), &$t::NamespacedSymbol(ref b)) => b.cmp(a),
|
||||
(&$t::Keyword(ref a), &$t::Keyword(ref b)) => b.cmp(a),
|
||||
|
@ -414,6 +439,7 @@ macro_rules! def_common_value_display {
|
|||
$t::Nil => write!($f, "nil"),
|
||||
$t::Boolean(v) => write!($f, "{}", v),
|
||||
$t::Integer(v) => write!($f, "{}", v),
|
||||
$t::Instant(v) => write!($f, "{}", v),
|
||||
$t::BigInteger(ref v) => write!($f, "{}N", v),
|
||||
// TODO: make sure float syntax is correct.
|
||||
$t::Float(ref v) => {
|
||||
|
@ -429,6 +455,7 @@ macro_rules! def_common_value_display {
|
|||
}
|
||||
// TODO: EDN escaping.
|
||||
$t::Text(ref v) => write!($f, "\"{}\"", v),
|
||||
$t::Uuid(ref u) => write!($f, "#uuid \"{}\"", u.hyphenated().to_string()),
|
||||
$t::PlainSymbol(ref v) => v.fmt($f),
|
||||
$t::NamespacedSymbol(ref v) => v.fmt($f),
|
||||
$t::Keyword(ref v) => v.fmt($f),
|
||||
|
@ -518,8 +545,29 @@ impl Display for ValueAndSpan {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait FromMicros {
|
||||
fn from_micros(ts: i64) -> Self;
|
||||
}
|
||||
|
||||
impl FromMicros for DateTime<UTC> {
|
||||
fn from_micros(ts: i64) -> Self {
|
||||
UTC.timestamp(ts / 100_000, ((ts % 100_000).abs() as u32) * 1_000)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToMicros {
|
||||
fn to_micros(&self) -> i64;
|
||||
}
|
||||
|
||||
impl ToMicros for DateTime<UTC> {
|
||||
fn to_micros(&self) -> i64 {
|
||||
(self.timestamp() * 100_000) + (self.timestamp_subsec_micros() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate chrono;
|
||||
extern crate ordered_float;
|
||||
extern crate num;
|
||||
|
||||
|
@ -532,9 +580,21 @@ mod test {
|
|||
|
||||
use parse;
|
||||
|
||||
use chrono::{
|
||||
DateTime,
|
||||
TimeZone,
|
||||
UTC,
|
||||
};
|
||||
use num::BigInt;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
#[test]
|
||||
fn test_micros_roundtrip() {
|
||||
let ts_micros: i64 = 1493399581314000;
|
||||
let dt = DateTime::<UTC>::from_micros(ts_micros);
|
||||
assert_eq!(dt.to_micros(), ts_micros);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_from() {
|
||||
assert_eq!(Value::from_float(42f64), Value::Float(OrderedFloat::from(42f64)));
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
extern crate chrono;
|
||||
extern crate edn;
|
||||
extern crate num;
|
||||
extern crate ordered_float;
|
||||
extern crate uuid;
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
||||
use std::iter::FromIterator;
|
||||
|
@ -21,7 +23,16 @@ use num::traits::{Zero, One};
|
|||
use ordered_float::OrderedFloat;
|
||||
|
||||
use edn::parse::{self, ParseError};
|
||||
use edn::types::{Value, SpannedValue, Span, ValueAndSpan};
|
||||
use edn::types::{
|
||||
Value,
|
||||
ValueAndSpan,
|
||||
Span,
|
||||
SpannedValue,
|
||||
};
|
||||
use chrono::{
|
||||
TimeZone,
|
||||
UTC,
|
||||
};
|
||||
use edn::symbols;
|
||||
use edn::utils;
|
||||
|
||||
|
@ -55,7 +66,7 @@ macro_rules! fn_parse_into_value {
|
|||
}
|
||||
|
||||
// These look exactly like their `parse::foo` counterparts, but
|
||||
// automatically convert the returned result into Value. Use `parse:foo`
|
||||
// automatically convert the returned result into Value. Use `parse::foo`
|
||||
// if you want the original ValueAndSpan instance.
|
||||
fn_parse_into_value!(nil);
|
||||
fn_parse_into_value!(nan);
|
||||
|
@ -242,6 +253,24 @@ fn test_span_integer() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuid() {
|
||||
assert!(parse::uuid("#uuid\"550e8400-e29b-41d4-a716-446655440000\"").is_err()); // No whitespace.
|
||||
assert!(parse::uuid("#uuid \"z50e8400-e29b-41d4-a716-446655440000\"").is_err()); // Not hex.
|
||||
assert!(parse::uuid("\"z50e8400-e29b-41d4-a716-446655440000\"").is_err()); // No tag.
|
||||
assert!(parse::uuid("#uuid \"aaaaaaaae29b-41d4-a716-446655440000\"").is_err()); // Hyphens.
|
||||
assert!(parse::uuid("#uuid \"aaaaaaaa-e29b-41d4-a716-446655440\"").is_err()); // Truncated.
|
||||
assert!(parse::uuid("#uuid \"A50e8400-e29b-41d4-a716-446655440000\"").is_err()); // Capital.
|
||||
|
||||
let expected = uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")
|
||||
.expect("valid UUID");
|
||||
let actual = parse::uuid("#uuid \"550e8400-e29b-41d4-a716-446655440000\"")
|
||||
.expect("parse success")
|
||||
.inner
|
||||
.into();
|
||||
assert_eq!(self::Value::Uuid(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bigint() {
|
||||
use self::Value::*;
|
||||
|
@ -394,6 +423,12 @@ fn test_value() {
|
|||
assert_eq!(value("(1)").unwrap(), List(LinkedList::from_iter(vec![Integer(1)])));
|
||||
assert_eq!(value("#{1}").unwrap(), Set(BTreeSet::from_iter(vec![Integer(1)])));
|
||||
assert_eq!(value("{1 2}").unwrap(), Map(BTreeMap::from_iter(vec![(Integer(1), Integer(2))])));
|
||||
assert_eq!(value("#uuid \"e43c6f3e-3123-49b7-8098-9b47a7bc0fa4\"").unwrap(),
|
||||
Uuid(uuid::Uuid::parse_str("e43c6f3e-3123-49b7-8098-9b47a7bc0fa4").unwrap()));
|
||||
assert_eq!(value("#instmillis 1493410985187").unwrap(), Instant(UTC.timestamp(1493410985, 187000000)));
|
||||
assert_eq!(value("#instmicros 1493410985187123").unwrap(), Instant(UTC.timestamp(1493410985, 187123000)));
|
||||
assert_eq!(value("#inst \"2017-04-28T20:23:05.187Z\"").unwrap(),
|
||||
Instant(UTC.timestamp(1493410985, 187000000)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -568,7 +568,7 @@ impl ConjoiningClauses {
|
|||
fn constrain_to_tx(&mut self, tx: &PatternNonValuePlace) {
|
||||
match *tx {
|
||||
PatternNonValuePlace::Placeholder => (),
|
||||
_ => unimplemented!(), // TODO
|
||||
_ => unimplemented!(), // TODO: #440.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ impl ConjoiningClauses {
|
|||
// Sorry for the duplication; Rust makes it a pain to abstract this.
|
||||
|
||||
// The transaction part of a pattern must be an entid, variable, or placeholder.
|
||||
self.constrain_to_tx(&pattern.tx);
|
||||
self.constrain_to_tx(&pattern.tx); // See #440.
|
||||
self.constrain_to_ref(&pattern.entity);
|
||||
self.constrain_to_ref(&pattern.attribute);
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ impl ConjoiningClauses {
|
|||
SrcVar(_) |
|
||||
Constant(NonIntegerConstant::Boolean(_)) |
|
||||
Constant(NonIntegerConstant::Text(_)) |
|
||||
Constant(NonIntegerConstant::Uuid(_)) |
|
||||
Constant(NonIntegerConstant::Instant(_)) | // Instants are covered elsewhere.
|
||||
Constant(NonIntegerConstant::BigInteger(_)) => {
|
||||
self.mark_known_empty(EmptyBecause::NonNumericArgument);
|
||||
bail!(ErrorKind::NonNumericArgument(function.clone(), position));
|
||||
|
@ -80,6 +82,8 @@ impl ConjoiningClauses {
|
|||
Constant(NonIntegerConstant::Boolean(val)) => Ok(QueryValue::TypedValue(TypedValue::Boolean(val))),
|
||||
Constant(NonIntegerConstant::Float(f)) => Ok(QueryValue::TypedValue(TypedValue::Double(f))),
|
||||
Constant(NonIntegerConstant::Text(s)) => Ok(QueryValue::TypedValue(TypedValue::typed_string(s.as_str()))),
|
||||
Constant(NonIntegerConstant::Uuid(u)) => Ok(QueryValue::TypedValue(TypedValue::Uuid(u))),
|
||||
Constant(NonIntegerConstant::Instant(u)) => Ok(QueryValue::TypedValue(TypedValue::Instant(u))),
|
||||
Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
|
||||
SrcVar(_) => unimplemented!(),
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ matches = "0.1"
|
|||
[dependencies.edn]
|
||||
path = "../edn"
|
||||
|
||||
[dependencies.mentat_core]
|
||||
path = "../core"
|
||||
|
||||
[dependencies.mentat_parser_utils]
|
||||
path = "../parser-utils"
|
||||
|
||||
|
|
|
@ -9,9 +9,12 @@
|
|||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
extern crate edn;
|
||||
extern crate mentat_core;
|
||||
extern crate mentat_query;
|
||||
extern crate mentat_query_parser;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use edn::{
|
||||
NamespacedKeyword,
|
||||
PlainSymbol,
|
||||
|
@ -23,6 +26,7 @@ use mentat_query::{
|
|||
FindSpec,
|
||||
FnArg,
|
||||
Limit,
|
||||
NonIntegerConstant,
|
||||
Order,
|
||||
OrJoin,
|
||||
OrWhereClause,
|
||||
|
@ -267,3 +271,17 @@ fn can_parse_limit() {
|
|||
let variable_without_in = "[:find ?x :where [?x :foo/baz ?y] :limit ?limit]";
|
||||
assert!(parse_find_string(variable_without_in).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_uuid() {
|
||||
let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
|
||||
let s = "[:find ?x :where [?x :foo/baz #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]]";
|
||||
assert_eq!(parse_find_string(s).expect("parsed").where_clauses.pop().expect("a where clause"),
|
||||
WhereClause::Pattern(
|
||||
Pattern::new(None,
|
||||
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||
PatternNonValuePlace::Ident(Rc::new(NamespacedKeyword::new("foo", "baz"))),
|
||||
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
|
||||
PatternNonValuePlace::Placeholder)
|
||||
.expect("valid pattern")));
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use mentat_core::{
|
||||
SQLValueType,
|
||||
TypedValue,
|
||||
|
|
|
@ -76,8 +76,8 @@ fn prepopulated_schema() -> Schema {
|
|||
prepopulated_typed_schema(ValueType::String)
|
||||
}
|
||||
|
||||
fn make_arg(name: &'static str, value: &'static str) -> (String, Rc<String>) {
|
||||
(name.to_string(), Rc::new(value.to_string()))
|
||||
fn make_arg(name: &'static str, value: &'static str) -> (String, Rc<mentat_sql::Value>) {
|
||||
(name.to_string(), Rc::new(mentat_sql::Value::Text(value.to_string())))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -40,8 +40,18 @@ use std::collections::{
|
|||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use edn::{BigInt, OrderedFloat};
|
||||
pub use edn::{NamespacedKeyword, PlainSymbol};
|
||||
use edn::{
|
||||
BigInt,
|
||||
DateTime,
|
||||
OrderedFloat,
|
||||
Uuid,
|
||||
UTC,
|
||||
};
|
||||
|
||||
pub use edn::{
|
||||
NamespacedKeyword,
|
||||
PlainSymbol,
|
||||
};
|
||||
|
||||
use mentat_core::{
|
||||
TypedValue,
|
||||
|
@ -178,6 +188,8 @@ pub enum NonIntegerConstant {
|
|||
BigInteger(BigInt),
|
||||
Float(OrderedFloat<f64>),
|
||||
Text(Rc<String>),
|
||||
Instant(DateTime<UTC>),
|
||||
Uuid(Uuid),
|
||||
}
|
||||
|
||||
impl NonIntegerConstant {
|
||||
|
@ -187,6 +199,8 @@ impl NonIntegerConstant {
|
|||
NonIntegerConstant::Boolean(v) => TypedValue::Boolean(v),
|
||||
NonIntegerConstant::Float(v) => TypedValue::Double(v),
|
||||
NonIntegerConstant::Text(v) => TypedValue::String(v),
|
||||
NonIntegerConstant::Instant(v) => TypedValue::Instant(v),
|
||||
NonIntegerConstant::Uuid(v) => TypedValue::Uuid(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -310,10 +324,22 @@ impl FromValue<PatternValuePlace> for PatternValuePlace {
|
|||
Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))),
|
||||
edn::SpannedValue::BigInteger(ref x) =>
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))),
|
||||
edn::SpannedValue::Instant(x) =>
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))),
|
||||
edn::SpannedValue::Text(ref x) =>
|
||||
// TODO: intern strings. #398.
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Text(Rc::new(x.clone())))),
|
||||
_ => None,
|
||||
edn::SpannedValue::Uuid(ref u) =>
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Uuid(u.clone()))),
|
||||
|
||||
// These don't appear in queries.
|
||||
edn::SpannedValue::Nil => None,
|
||||
edn::SpannedValue::NamespacedSymbol(_) => None,
|
||||
edn::SpannedValue::Keyword(_) => None,
|
||||
edn::SpannedValue::Map(_) => None,
|
||||
edn::SpannedValue::List(_) => None,
|
||||
edn::SpannedValue::Set(_) => None,
|
||||
edn::SpannedValue::Vector(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,5 +7,10 @@ workspace = ".."
|
|||
error-chain = "0.8.1"
|
||||
ordered-float = "0.4.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.10.1"
|
||||
# System sqlite might be very old.
|
||||
features = ["bundled", "limits"]
|
||||
|
||||
[dependencies.mentat_core]
|
||||
path = "../core"
|
||||
|
|
|
@ -11,13 +11,20 @@
|
|||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
extern crate ordered_float;
|
||||
extern crate rusqlite;
|
||||
|
||||
extern crate mentat_core;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use mentat_core::TypedValue;
|
||||
use mentat_core::{
|
||||
ToMicros,
|
||||
TypedValue,
|
||||
};
|
||||
|
||||
pub use rusqlite::types::Value;
|
||||
|
||||
error_chain! {
|
||||
types {
|
||||
|
@ -47,7 +54,7 @@ pub struct SQLQuery {
|
|||
pub sql: String,
|
||||
|
||||
/// These will eventually perhaps be rusqlite `ToSql` instances.
|
||||
pub args: Vec<(String, Rc<String>)>,
|
||||
pub args: Vec<(String, Rc<rusqlite::types::Value>)>,
|
||||
}
|
||||
|
||||
/// Gratefully based on Diesel's QueryBuilder trait:
|
||||
|
@ -88,7 +95,7 @@ pub struct SQLiteQueryBuilder {
|
|||
|
||||
arg_prefix: String,
|
||||
arg_counter: i64,
|
||||
args: Vec<(String, Rc<String>)>,
|
||||
args: Vec<(String, Rc<rusqlite::types::Value>)>,
|
||||
}
|
||||
|
||||
impl SQLiteQueryBuilder {
|
||||
|
@ -105,7 +112,7 @@ impl SQLiteQueryBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn push_static_arg(&mut self, val: Rc<String>) {
|
||||
fn push_static_arg(&mut self, val: Rc<rusqlite::types::Value>) {
|
||||
let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
|
||||
self.arg_counter = self.arg_counter + 1;
|
||||
self.push_named_arg(arg.as_str());
|
||||
|
@ -136,11 +143,25 @@ impl QueryBuilder for SQLiteQueryBuilder {
|
|||
&Boolean(v) => self.push_sql(if v { "1" } else { "0" }),
|
||||
&Long(v) => self.push_sql(v.to_string().as_str()),
|
||||
&Double(OrderedFloat(v)) => self.push_sql(v.to_string().as_str()),
|
||||
|
||||
// These are both `Rc`. We can just clone an `Rc<String>`, but we
|
||||
// must make a new single `String`, wrapped in an `Rc`, for keywords.
|
||||
&String(ref s) => self.push_static_arg(s.clone()),
|
||||
&Keyword(ref s) => self.push_static_arg(Rc::new(s.as_ref().to_string())),
|
||||
&Instant(dt) => {
|
||||
self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
|
||||
},
|
||||
&Uuid(ref u) => {
|
||||
// Get a byte array.
|
||||
let bytes = u.as_bytes().clone();
|
||||
let v = Rc::new(rusqlite::types::Value::Blob(bytes.to_vec()));
|
||||
self.push_static_arg(v);
|
||||
},
|
||||
// These are both `Rc`. Unfortunately, we can't use that fact when
|
||||
// turning these into rusqlite Values.
|
||||
&String(ref s) => {
|
||||
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().clone()));
|
||||
self.push_static_arg(v);
|
||||
},
|
||||
&Keyword(ref s) => {
|
||||
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
|
||||
self.push_static_arg(v);
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -180,6 +201,10 @@ impl QueryBuilder for SQLiteQueryBuilder {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn string_arg(s: &str) -> Rc<rusqlite::types::Value> {
|
||||
Rc::new(rusqlite::types::Value::Text(s.to_string()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql() {
|
||||
let mut s = SQLiteQueryBuilder::new();
|
||||
|
@ -188,14 +213,14 @@ mod tests {
|
|||
s.push_sql(" WHERE ");
|
||||
s.push_identifier("bar").unwrap();
|
||||
s.push_sql(" = ");
|
||||
s.push_static_arg(Rc::new("frobnicate".to_string()));
|
||||
s.push_static_arg(string_arg("frobnicate"));
|
||||
s.push_sql(" OR ");
|
||||
s.push_static_arg(Rc::new("swoogle".to_string()));
|
||||
s.push_static_arg(string_arg("swoogle"));
|
||||
let q = s.finish();
|
||||
|
||||
assert_eq!(q.sql.as_str(), "SELECT `foo` WHERE `bar` = $v0 OR $v1");
|
||||
assert_eq!(q.args,
|
||||
vec![("$v0".to_string(), Rc::new("frobnicate".to_string())),
|
||||
("$v1".to_string(), Rc::new("swoogle".to_string()))]);
|
||||
vec![("$v0".to_string(), string_arg("frobnicate")),
|
||||
("$v1".to_string(), string_arg("swoogle"))]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,15 +8,22 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
extern crate chrono;
|
||||
extern crate time;
|
||||
|
||||
extern crate mentat;
|
||||
extern crate mentat_core;
|
||||
extern crate mentat_db;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::FixedOffset;
|
||||
|
||||
use mentat_core::{
|
||||
TypedValue,
|
||||
ValueType,
|
||||
UTC,
|
||||
Uuid,
|
||||
};
|
||||
|
||||
use mentat::{
|
||||
|
@ -28,6 +35,8 @@ use mentat::{
|
|||
q_once,
|
||||
};
|
||||
|
||||
use mentat::conn::Conn;
|
||||
|
||||
use mentat::errors::{
|
||||
Error,
|
||||
ErrorKind,
|
||||
|
@ -46,7 +55,7 @@ fn test_rel() {
|
|||
let end = time::PreciseTime::now();
|
||||
|
||||
// This will need to change each time we add a default ident.
|
||||
assert_eq!(37, results.len());
|
||||
assert_eq!(39, results.len());
|
||||
|
||||
// Every row is a pair of a Ref and a Keyword.
|
||||
if let QueryResults::Rel(ref rel) = results {
|
||||
|
@ -154,7 +163,7 @@ fn test_coll() {
|
|||
.expect("Query failed");
|
||||
let end = time::PreciseTime::now();
|
||||
|
||||
assert_eq!(37, results.len());
|
||||
assert_eq!(39, results.len());
|
||||
|
||||
if let QueryResults::Coll(ref coll) = results {
|
||||
assert!(coll.iter().all(|item| item.matches_type(ValueType::Ref)));
|
||||
|
@ -204,3 +213,44 @@ fn test_unbound_inputs() {
|
|||
_ => panic!("Expected unbound variables."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instants_and_uuids() {
|
||||
// We assume, perhaps foolishly, that the clocks on test machines won't lose more than an
|
||||
// hour while this test is running.
|
||||
let start = UTC::now() + FixedOffset::west(60 * 60);
|
||||
|
||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
||||
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
|
||||
conn.transact(&mut c, r#"[
|
||||
[:db/add "s" :db/ident :foo/uuid]
|
||||
[:db/add "s" :db/valueType :db.type/uuid]
|
||||
[:db/add "s" :db/cardinality :db.cardinality/one]
|
||||
]"#).unwrap();
|
||||
conn.transact(&mut c, r#"[
|
||||
[:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"]
|
||||
]"#).unwrap();
|
||||
|
||||
// We don't yet support getting the tx from a pattern (#440), so run wild.
|
||||
let r = conn.q_once(&mut c,
|
||||
r#"[:find [?x ?u ?when]
|
||||
:where [?x :foo/uuid ?u]
|
||||
[?tx :db/txInstant ?when]]"#, None);
|
||||
match r {
|
||||
Result::Ok(QueryResults::Tuple(Some(vals))) => {
|
||||
let mut vals = vals.into_iter();
|
||||
match (vals.next(), vals.next(), vals.next(), vals.next()) {
|
||||
(Some(TypedValue::Ref(e)),
|
||||
Some(TypedValue::Uuid(u)),
|
||||
Some(TypedValue::Instant(t)),
|
||||
None) => {
|
||||
assert!(e > 39); // There are at least this many entities in the store.
|
||||
assert_eq!(Ok(u), Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4"));
|
||||
assert!(t > start);
|
||||
},
|
||||
_ => panic!("Unexpected results."),
|
||||
}
|
||||
},
|
||||
_ => panic!("Expected query to work."),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,33 @@ use mentat_tx::entities::{
|
|||
};
|
||||
use mentat_tx_parser::Tx;
|
||||
|
||||
#[test]
|
||||
fn test_float_and_uuid() {
|
||||
let expected_uuid = edn::Uuid::parse_str("267bab92-ee39-4ca2-b7f0-1163a85af1fb").expect("valid uuid");
|
||||
let input = r#"
|
||||
[[:db/add 101 :test/a #uuid "267bab92-ee39-4ca2-b7f0-1163a85af1fb"]
|
||||
[:db/add 102 :test/b #f NaN]]
|
||||
"#;
|
||||
let edn = parse::value(input).expect("to parse test input");
|
||||
|
||||
let result = Tx::parse(edn);
|
||||
assert_eq!(result.unwrap(),
|
||||
vec![
|
||||
Entity::AddOrRetract {
|
||||
op: OpType::Add,
|
||||
e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(101)),
|
||||
a: Entid::Ident(NamespacedKeyword::new("test", "a")),
|
||||
v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Uuid(expected_uuid), edn::Span(23, 67))),
|
||||
},
|
||||
Entity::AddOrRetract {
|
||||
op: OpType::Add,
|
||||
e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(102)),
|
||||
a: Entid::Ident(NamespacedKeyword::new("test", "b")),
|
||||
v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Float(edn::OrderedFloat(std::f64::NAN)), edn::Span(91, 97))),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entities() {
|
||||
let input = r#"
|
||||
|
|
Loading…
Reference in a new issue