UUIDs and instants. Fixes #44, #45, #426, #427. (#438) r=nalexander

* 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:
Richard Newman 2017-04-28 20:11:55 -07:00 committed by GitHub
parent bd389d2f0d
commit daca8def57
28 changed files with 495 additions and 126 deletions

View file

@ -17,6 +17,7 @@ members = []
rustc_version = "0.1.7" rustc_version = "0.1.7"
[dependencies] [dependencies]
chrono = "0.3"
clap = "2.19.3" clap = "2.19.3"
error-chain = "0.8.1" error-chain = "0.8.1"
nickel = "0.9.0" nickel = "0.9.0"

View file

@ -25,7 +25,20 @@ use std::rc::Rc;
use enum_set::EnumSet; use enum_set::EnumSet;
use self::ordered_float::OrderedFloat; 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. /// Core types defining a Mentat knowledge base.
@ -48,6 +61,7 @@ pub enum ValueType {
Double, Double,
String, String,
Keyword, Keyword,
Uuid,
} }
impl ValueType { impl ValueType {
@ -61,6 +75,7 @@ impl ValueType {
s.insert(ValueType::Double); s.insert(ValueType::Double);
s.insert(ValueType::String); s.insert(ValueType::String);
s.insert(ValueType::Keyword); s.insert(ValueType::Keyword);
s.insert(ValueType::Uuid);
s s
} }
} }
@ -86,6 +101,7 @@ impl ValueType {
ValueType::Double => values::DB_TYPE_DOUBLE.clone(), ValueType::Double => values::DB_TYPE_DOUBLE.clone(),
ValueType::String => values::DB_TYPE_STRING.clone(), ValueType::String => values::DB_TYPE_STRING.clone(),
ValueType::Keyword => values::DB_TYPE_KEYWORD.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::Double => ":db.type/double",
ValueType::String => ":db.type/string", ValueType::String => ":db.type/string",
ValueType::Keyword => ":db.type/keyword", ValueType::Keyword => ":db.type/keyword",
ValueType::Uuid => ":db.type/uuid",
}) })
} }
} }
@ -113,9 +130,11 @@ pub enum TypedValue {
Boolean(bool), Boolean(bool),
Long(i64), Long(i64),
Double(OrderedFloat<f64>), Double(OrderedFloat<f64>),
Instant(DateTime<UTC>),
// TODO: &str throughout? // TODO: &str throughout?
String(Rc<String>), String(Rc<String>),
Keyword(Rc<NamespacedKeyword>), Keyword(Rc<NamespacedKeyword>),
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
} }
impl TypedValue { impl TypedValue {
@ -136,9 +155,11 @@ impl TypedValue {
&TypedValue::Ref(_) => ValueType::Ref, &TypedValue::Ref(_) => ValueType::Ref,
&TypedValue::Boolean(_) => ValueType::Boolean, &TypedValue::Boolean(_) => ValueType::Boolean,
&TypedValue::Long(_) => ValueType::Long, &TypedValue::Long(_) => ValueType::Long,
&TypedValue::Instant(_) => ValueType::Instant,
&TypedValue::Double(_) => ValueType::Double, &TypedValue::Double(_) => ValueType::Double,
&TypedValue::String(_) => ValueType::String, &TypedValue::String(_) => ValueType::String,
&TypedValue::Keyword(_) => ValueType::Keyword, &TypedValue::Keyword(_) => ValueType::Keyword,
&TypedValue::Uuid(_) => ValueType::Uuid,
} }
} }
@ -155,6 +176,10 @@ impl TypedValue {
pub fn typed_string(s: &str) -> TypedValue { pub fn typed_string(s: &str) -> TypedValue {
TypedValue::String(Rc::new(s.to_string())) 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. // 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::Long => 5,
ValueType::Double => 5, ValueType::Double => 5,
ValueType::String => 10, ValueType::String => 10,
ValueType::Uuid => 11,
ValueType::Keyword => 13, ValueType::Keyword => 13,
} }
} }
@ -182,6 +208,8 @@ impl SQLValueType for ValueType {
/// ///
/// ``` /// ```
/// use mentat_core::{ValueType, SQLValueType}; /// 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(-1)); /// assert!(!ValueType::Boolean.accommodates_integer(-1));
/// assert!(!ValueType::Boolean.accommodates_integer(10)); /// assert!(!ValueType::Boolean.accommodates_integer(10));
@ -190,11 +218,13 @@ impl SQLValueType for ValueType {
fn accommodates_integer(&self, int: i64) -> bool { fn accommodates_integer(&self, int: i64) -> bool {
use ValueType::*; use ValueType::*;
match *self { match *self {
Instant | Long | Double => true, Instant => false, // Always use #inst.
Long | Double => true,
Ref => int >= 0, Ref => int >= 0,
Boolean => (int == 0) || (int == 1), Boolean => (int == 0) || (int == 1),
ValueType::String => false, ValueType::String => false,
Keyword => false, Keyword => false,
Uuid => false,
} }
} }
} }

View file

@ -61,6 +61,8 @@ lazy_static! {
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG), (ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG),
(ns_keyword!("db.type", "double"), entids::DB_TYPE_DOUBLE), (ns_keyword!("db.type", "double"), entids::DB_TYPE_DOUBLE),
(ns_keyword!("db.type", "string"), entids::DB_TYPE_STRING), (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", "boolean"), entids::DB_TYPE_BOOLEAN),
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT), (ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT),
(ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES), (ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES),
@ -69,16 +71,11 @@ lazy_static! {
(ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE), (ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE),
(ns_keyword!("db.unique", "identity"), entids::DB_UNIQUE_IDENTITY), (ns_keyword!("db.unique", "identity"), entids::DB_UNIQUE_IDENTITY),
(ns_keyword!("db", "doc"), entids::DB_DOC), (ns_keyword!("db", "doc"), entids::DB_DOC),
(ns_keyword!("db.schema", "version"), entids::DB_SCHEMA_VERSION),
(ns_keyword!("db.schema", "attribute"), entids::DB_SCHEMA_ATTRIBUTE),
] ]
}; };
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", "attribute"), entids::DB_SCHEMA_ATTRIBUTE),
]].concat()
};
static ref V1_PARTS: Vec<(symbols::NamespacedKeyword, i64, i64)> = { static ref V1_PARTS: Vec<(symbols::NamespacedKeyword, i64, i64)> = {
vec![(ns_keyword!("db.part", "db"), 0, (1 + V1_IDENTS.len()) as i64), vec![(ns_keyword!("db.part", "db"), 0, (1 + V1_IDENTS.len()) as i64),
(ns_keyword!("db.part", "user"), 0x10000, 0x10000), (ns_keyword!("db.part", "user"), 0x10000, 0x10000),
@ -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 = { static ref V1_SYMBOLIC_SCHEMA: Value = {
let s = r#" let s = r#"
{:db/ident {:db/valueType :db.type/keyword {:db/ident {:db/valueType :db.type/keyword
@ -108,7 +98,7 @@ lazy_static! {
;; TODO: support user-specified functions in the future. ;; TODO: support user-specified functions in the future.
;; :db.install/function {:db/valueType :db.type/ref ;; :db.install/function {:db/valueType :db.type/ref
;; :db/cardinality :db.cardinality/many} ;; :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/cardinality :db.cardinality/one
:db/index true} :db/index true}
:db/valueType {:db/valueType :db.type/ref :db/valueType {:db/valueType :db.type/ref
@ -126,16 +116,8 @@ lazy_static! {
:db/fulltext {:db/valueType :db.type/boolean :db/fulltext {:db/valueType :db.type/boolean
:db/cardinality :db.cardinality/one} :db/cardinality :db.cardinality/one}
:db/noHistory {:db/valueType :db.type/boolean :db/noHistory {:db/valueType :db.type/boolean
:db/cardinality :db.cardinality/one}}"#; :db/cardinality :db.cardinality/one}
edn::parse::value(s) :db.alter/attribute {:db/valueType :db.type/ref
.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/many} :db/cardinality :db.cardinality/many}
:db.schema/version {:db/valueType :db.type/long :db.schema/version {:db/valueType :db.type/long
:db/cardinality :db.cardinality/one} :db/cardinality :db.cardinality/one}
@ -146,13 +128,9 @@ lazy_static! {
:db/index true :db/index true
:db/unique :db.unique/value :db/unique :db.unique/value
:db/cardinality :db.cardinality/many}}"#; :db/cardinality :db.cardinality/many}}"#;
let right = edn::parse::value(s) edn::parse::value(s)
.map(|v| v.without_spans()) .map(|v| v.without_spans())
.map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V2_SYMBOLIC_SCHEMA".into())) .map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into()))
.unwrap();
edn::utils::merge(&V1_SYMBOLIC_SCHEMA, &right)
.ok_or(ErrorKind::BadBootstrapDefinition("Unable to parse V2_SYMBOLIC_SCHEMA".into()))
.unwrap() .unwrap()
}; };
} }
@ -248,27 +226,27 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result<Vec<Value>>
} }
pub fn bootstrap_partition_map() -> PartitionMap { pub fn bootstrap_partition_map() -> PartitionMap {
V2_PARTS[..].iter() V1_PARTS[..].iter()
.map(|&(ref part, start, index)| (part.to_string(), Partition::new(start, index))) .map(|&(ref part, start, index)| (part.to_string(), Partition::new(start, index)))
.collect() .collect()
} }
pub fn bootstrap_ident_map() -> IdentMap { pub fn bootstrap_ident_map() -> IdentMap {
V2_IDENTS[..].iter() V1_IDENTS[..].iter()
.map(|&(ref ident, entid)| (ident.clone(), entid)) .map(|&(ref ident, entid)| (ident.clone(), entid))
.collect() .collect()
} }
pub fn bootstrap_schema() -> Schema { pub fn bootstrap_schema() -> Schema {
let ident_map = bootstrap_ident_map(); 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() Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap()
} }
pub fn bootstrap_entities() -> Vec<Entity> { pub fn bootstrap_entities() -> Vec<Entity> {
let bootstrap_assertions: Value = Value::Vector([ let bootstrap_assertions: Value = Value::Vector([
symbolic_schema_to_assertions(&V2_SYMBOLIC_SCHEMA).unwrap(), symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).unwrap(),
idents_to_assertions(&V2_IDENTS[..]), idents_to_assertions(&V1_IDENTS[..]),
].concat()); ].concat());
// Failure here is a coding error (since the inputs are fixed), not a runtime error. // Failure here is a coding error (since the inputs are fixed), not a runtime error.

View file

@ -26,17 +26,26 @@ use rusqlite::limits::Limit;
use ::{repeat_values, to_namespaced_keyword}; use ::{repeat_values, to_namespaced_keyword};
use bootstrap; use bootstrap;
use edn::types::Value;
use edn::{
DateTime,
UTC,
Uuid,
Value,
};
use entids; use entids;
use mentat_core::{ use mentat_core::{
attribute, attribute,
Attribute, Attribute,
AttributeBitFlags, AttributeBitFlags,
Entid, Entid,
FromMicros,
IdentMap, IdentMap,
Schema, Schema,
SchemaMap, SchemaMap,
TypedValue, TypedValue,
ToMicros,
ValueType, ValueType,
}; };
use errors::{ErrorKind, Result, ResultExt}; use errors::{ErrorKind, Result, ResultExt};
@ -72,10 +81,8 @@ pub fn new_connection<T>(uri: T) -> rusqlite::Result<rusqlite::Connection> where
/// Version history: /// Version history:
/// ///
/// 1: initial schema. /// 1: initial Rust Mentat schema.
/// 2: added :db.schema/version and /attribute in bootstrap; assigned idents 36 and 37, so we bump pub const CURRENT_VERSION: i32 = 1;
/// the part range here; tie bootstrapping to the SQLite user_version.
pub const CURRENT_VERSION: i32 = 2;
/// MIN_SQLITE_VERSION should be changed when there's a new minimum version of sqlite required /// MIN_SQLITE_VERSION should be changed when there's a new minimum version of sqlite required
/// for the project to work. /// for the project to work.
@ -93,9 +100,9 @@ fn to_bool_ref(x: bool) -> &'static bool {
} }
lazy_static! { 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)] #[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, 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, value_type_tag SMALLINT NOT NULL,
index_avet TINYINT NOT NULL DEFAULT 0, index_vaet TINYINT NOT NULL DEFAULT 0, 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> { pub fn create_current_version(conn: &mut rusqlite::Connection) -> Result<DB> {
let tx = conn.transaction()?; let tx = conn.transaction()?;
for statement in (&V2_STATEMENTS).iter() { for statement in (&V1_STATEMENTS).iter() {
tx.execute(statement, &[])?; tx.execute(statement, &[])?;
} }
@ -347,11 +354,24 @@ impl TypedSQLValue for TypedValue {
match (value_type_tag, value) { match (value_type_tag, value) {
(0, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Ref(x)), (0, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Ref(x)),
(1, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Boolean(0 != 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 // SQLite distinguishes integral from decimal types, allowing long and double to
// share a tag. // share a tag.
(5, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Long(x)), (5, rusqlite::types::Value::Integer(x)) => Ok(TypedValue::Long(x)),
(5, rusqlite::types::Value::Real(x)) => Ok(TypedValue::Double(x.into())), (5, rusqlite::types::Value::Real(x)) => Ok(TypedValue::Double(x.into())),
(10, rusqlite::types::Value::Text(x)) => Ok(TypedValue::String(Rc::new(x))), (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)) => { (13, rusqlite::types::Value::Text(x)) => {
to_namespaced_keyword(&x).map(|k| TypedValue::Keyword(Rc::new(k))) 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> { fn from_edn_value(value: &Value) -> Option<TypedValue> {
match value { match value {
&Value::Boolean(x) => Some(TypedValue::Boolean(x)), &Value::Boolean(x) => Some(TypedValue::Boolean(x)),
&Value::Instant(x) => Some(TypedValue::Instant(x)),
&Value::Integer(x) => Some(TypedValue::Long(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::Float(ref x) => Some(TypedValue::Double(x.clone())),
&Value::Text(ref x) => Some(TypedValue::String(Rc::new(x.clone()))), &Value::Text(ref x) => Some(TypedValue::String(Rc::new(x.clone()))),
&Value::NamespacedKeyword(ref x) => Some(TypedValue::Keyword(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 { match self {
&TypedValue::Ref(x) => (rusqlite::types::Value::Integer(x).into(), 0), &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::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. // 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::Long(x) => (rusqlite::types::Value::Integer(x).into(), 5),
&TypedValue::Double(x) => (rusqlite::types::Value::Real(x.into_inner()).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::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), &TypedValue::Keyword(ref x) => (rusqlite::types::ValueRef::Text(&x.to_string()).into(), 13),
} }
} }
@ -395,9 +419,11 @@ impl TypedSQLValue for TypedValue {
match self { match self {
&TypedValue::Ref(x) => (Value::Integer(x), ValueType::Ref), &TypedValue::Ref(x) => (Value::Integer(x), ValueType::Ref),
&TypedValue::Boolean(x) => (Value::Boolean(x), ValueType::Boolean), &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::Long(x) => (Value::Integer(x), ValueType::Long),
&TypedValue::Double(x) => (Value::Float(x), ValueType::Double), &TypedValue::Double(x) => (Value::Float(x), ValueType::Double),
&TypedValue::String(ref x) => (Value::Text(x.as_ref().clone()), ValueType::String), &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), &TypedValue::Keyword(ref x) => (Value::NamespacedKeyword(x.as_ref().clone()), ValueType::Keyword),
} }
} }
@ -1173,12 +1199,12 @@ mod tests {
// Does not include :db/txInstant. // Does not include :db/txInstant.
let datoms = debug::datoms_after(&conn, &db.schema, 0).unwrap(); 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. // Includes :db/txInstant.
let transactions = debug::transactions_after(&conn, &db.schema, 0).unwrap(); let transactions = debug::transactions_after(&conn, &db.schema, 0).unwrap();
assert_eq!(transactions.0.len(), 1); 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 { let test_conn = TestConn {
sqlite: conn, sqlite: conn,

View file

@ -44,18 +44,18 @@ pub const DB_TYPE_KEYWORD: Entid = 24;
pub const DB_TYPE_LONG: Entid = 25; pub const DB_TYPE_LONG: Entid = 25;
pub const DB_TYPE_DOUBLE: Entid = 26; pub const DB_TYPE_DOUBLE: Entid = 26;
pub const DB_TYPE_STRING: Entid = 27; pub const DB_TYPE_STRING: Entid = 27;
pub const DB_TYPE_BOOLEAN: Entid = 28; pub const DB_TYPE_UUID: Entid = 28;
pub const DB_TYPE_INSTANT: Entid = 29; pub const DB_TYPE_URI: Entid = 29;
pub const DB_TYPE_BYTES: Entid = 30; pub const DB_TYPE_BOOLEAN: Entid = 30;
pub const DB_CARDINALITY_ONE: Entid = 31; pub const DB_TYPE_INSTANT: Entid = 31;
pub const DB_CARDINALITY_MANY: Entid = 32; pub const DB_TYPE_BYTES: Entid = 32;
pub const DB_UNIQUE_VALUE: Entid = 33; pub const DB_CARDINALITY_ONE: Entid = 33;
pub const DB_UNIQUE_IDENTITY: Entid = 34; pub const DB_CARDINALITY_MANY: Entid = 34;
pub const DB_DOC: Entid = 35; pub const DB_UNIQUE_VALUE: Entid = 35;
pub const DB_UNIQUE_IDENTITY: Entid = 36;
// Added in SQL schema v2. pub const DB_DOC: Entid = 37;
pub const DB_SCHEMA_VERSION: Entid = 36; pub const DB_SCHEMA_VERSION: Entid = 38;
pub const DB_SCHEMA_ATTRIBUTE: Entid = 37; pub const DB_SCHEMA_ATTRIBUTE: Entid = 39;
/// Return `false` if the given attribute will not change the metadata: recognized idents, schema, /// Return `false` if the given attribute will not change the metadata: recognized idents, schema,
/// partitions in the partition map. /// partitions in the partition map.

View file

@ -11,12 +11,12 @@
#[macro_use] #[macro_use]
extern crate error_chain; extern crate error_chain;
extern crate itertools; extern crate itertools;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate rusqlite; extern crate rusqlite;
extern crate time;
extern crate tabwriter; extern crate tabwriter;
extern crate time;
#[macro_use] #[macro_use]
extern crate edn; extern crate edn;
@ -24,8 +24,15 @@ extern crate mentat_core;
extern crate mentat_tx; extern crate mentat_tx;
extern crate mentat_tx_parser; extern crate mentat_tx_parser;
use itertools::Itertools;
use std::iter::repeat; use std::iter::repeat;
use itertools::Itertools;
use mentat_core::{
DateTime,
UTC,
};
pub use errors::{Error, ErrorKind, ResultExt, Result}; pub use errors::{Error, ErrorKind, ResultExt, Result};
pub mod db; pub mod db;
@ -89,10 +96,7 @@ pub fn repeat_values(values_per_tuple: usize, tuples: usize) -> String {
values values
} }
/// Return the current time in milliseconds after the Unix epoch according to the local clock. /// Return the current time as a UTC `DateTime` instance.
/// pub fn now() -> DateTime<UTC> {
/// Compare `Date.now()` in JavaScript, `System.currentTimeMillis` in Java. UTC::now()
pub fn now() -> i64 {
let now = time::get_time();
(now.sec as i64 * 1_000) + (now.nsec as i64 / (1_000_000))
} }

View file

@ -115,12 +115,14 @@ pub fn update_schema_map_from_entid_triples<U>(schema_map: &mut SchemaMap, asser
entids::DB_VALUE_TYPE => { entids::DB_VALUE_TYPE => {
match *value { 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_BOOLEAN) => { builder.value_type(ValueType::Boolean); },
TypedValue::Ref(entids::DB_TYPE_DOUBLE) => { builder.value_type(ValueType::Double); }, 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_INSTANT) => { builder.value_type(ValueType::Instant); },
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
TypedValue::Ref(entids::DB_TYPE_KEYWORD) => { builder.value_type(ValueType::Keyword); }, 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))) _ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr)))
} }
}, },

View file

@ -240,6 +240,7 @@ impl SchemaTypeChecking for Schema {
(&ValueType::Long, tv @ TypedValue::Long(_)) => Ok(tv), (&ValueType::Long, tv @ TypedValue::Long(_)) => Ok(tv),
(&ValueType::Double, tv @ TypedValue::Double(_)) => Ok(tv), (&ValueType::Double, tv @ TypedValue::Double(_)) => Ok(tv),
(&ValueType::String, tv @ TypedValue::String(_)) => Ok(tv), (&ValueType::String, tv @ TypedValue::String(_)) => Ok(tv),
(&ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv),
(&ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv), (&ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
// Ref coerces a little: we interpret some things depending on the schema as a Ref. // Ref coerces a little: we interpret some things depending on the schema as a Ref.
(&ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)), (&ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),

View file

@ -70,10 +70,13 @@ use internal_types::{
TermWithTempIds, TermWithTempIds,
TermWithoutTempIds, TermWithoutTempIds,
replace_lookup_ref}; replace_lookup_ref};
use mentat_core::{ use mentat_core::{
DateTime,
Schema,
UTC,
attribute, attribute,
intern_set, intern_set,
Schema,
}; };
use mentat_tx::entities as entmod; use mentat_tx::entities as entmod;
use mentat_tx::entities::{ use mentat_tx::entities::{
@ -127,10 +130,7 @@ pub struct Tx<'conn, 'a> {
tx_id: Entid, tx_id: Entid,
/// The timestamp when the transaction began to be committed. /// The timestamp when the transaction began to be committed.
/// tx_instant: DateTime<UTC>,
/// This is milliseconds after the Unix epoch according to the transactor's local clock.
// TODO: :db.type/instant.
tx_instant: i64,
} }
impl<'conn, 'a> Tx<'conn, 'a> { impl<'conn, 'a> Tx<'conn, 'a> {
@ -140,7 +140,7 @@ impl<'conn, 'a> Tx<'conn, 'a> {
schema_for_mutation: &'a Schema, schema_for_mutation: &'a Schema,
schema: &'a Schema, schema: &'a Schema,
tx_id: Entid, tx_id: Entid,
tx_instant: i64) -> Tx<'conn, 'a> { tx_instant: DateTime<UTC>) -> Tx<'conn, 'a> {
Tx { Tx {
store: store, store: store,
partition_map: partition_map, partition_map: partition_map,
@ -532,7 +532,7 @@ impl<'conn, 'a> Tx<'conn, 'a> {
non_fts_one.push((self.tx_id, non_fts_one.push((self.tx_id,
entids::DB_TX_INSTANT, entids::DB_TX_INSTANT,
self.schema.require_attribute_for_entid(entids::DB_TX_INSTANT).unwrap(), self.schema.require_attribute_for_entid(entids::DB_TX_INSTANT).unwrap(),
TypedValue::Long(self.tx_instant), TypedValue::Instant(self.tx_instant),
true)); true));
if !non_fts_one.is_empty() { if !non_fts_one.is_empty() {

View file

@ -16,12 +16,14 @@ use std::collections::BTreeMap;
extern crate mentat_core; extern crate mentat_core;
pub use self::mentat_core::{ pub use self::mentat_core::{
DateTime,
Entid, Entid,
ValueType, ValueType,
TypedValue, TypedValue,
Attribute, Attribute,
AttributeBitFlags, AttributeBitFlags,
Schema, Schema,
UTC,
}; };
/// Represents one partition of the entid space. /// 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. /// A transaction report summarizes an applied transaction.
// TODO: include map of resolved tempids. // 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 { pub struct TxReport {
/// The transaction ID of the transaction. /// The transaction ID of the transaction.
pub tx_id: Entid, pub tx_id: Entid,
/// The timestamp when the transaction began to be committed. /// The timestamp when the transaction began to be committed.
/// pub tx_instant: DateTime<UTC>,
/// This is milliseconds after the Unix epoch according to the transactor's local clock.
// TODO: :db.type/instant.
pub tx_instant: i64,
/// A map from string literal tempid to resolved or allocated entid. /// A map from string literal tempid to resolved or allocated entid.
/// ///

View file

@ -11,10 +11,12 @@ build = "build.rs"
readme = "./README.md" readme = "./README.md"
[dependencies] [dependencies]
chrono = "0.3"
itertools = "0.5.9" itertools = "0.5.9"
num = "0.1.35" num = "0.1.35"
ordered-float = "0.4.0" ordered-float = "0.4.0"
pretty = "0.2.0" pretty = "0.2.0"
uuid = "0.5.0"
[build-dependencies] [build-dependencies]
peg = "0.5.1" peg = "0.5.1"

View file

@ -14,8 +14,14 @@ use std::collections::{BTreeSet, BTreeMap, LinkedList};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::f64::{NAN, INFINITY, NEG_INFINITY}; use std::f64::{NAN, INFINITY, NEG_INFINITY};
use chrono::{
DateTime,
TimeZone,
UTC
};
use num::BigInt; use num::BigInt;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use uuid::Uuid;
use types::{SpannedValue, Span, ValueAndSpan}; 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_divider = "."
namespace_separator = "/" namespace_separator = "/"
@ -220,7 +280,7 @@ pub map -> ValueAndSpan =
// It's important that float comes before integer or the parser assumes that // It's important that float comes before integer or the parser assumes that
// floats are integers and fails to parse // floats are integers and fails to parse
pub value -> ValueAndSpan = 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 v
} }

View file

@ -8,10 +8,12 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate chrono;
extern crate itertools; extern crate itertools;
extern crate num; extern crate num;
extern crate ordered_float; extern crate ordered_float;
extern crate pretty; extern crate pretty;
extern crate uuid;
pub mod symbols; pub mod symbols;
pub mod types; pub mod types;
@ -23,8 +25,20 @@ pub mod parse {
include!(concat!(env!("OUT_DIR"), "/edn.rs")); include!(concat!(env!("OUT_DIR"), "/edn.rs"));
} }
// Re-export the types we use.
pub use chrono::{DateTime, UTC};
pub use num::BigInt; pub use num::BigInt;
pub use ordered_float::OrderedFloat; pub use ordered_float::OrderedFloat;
pub use uuid::Uuid;
// Export from our modules.
pub use parse::ParseError; 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}; pub use symbols::{Keyword, NamespacedKeyword, PlainSymbol, NamespacedSymbol};

View file

@ -69,6 +69,7 @@ impl Value {
Value::NamespacedKeyword(ref v) => pp.text(":").append(v.namespace.as_ref()).append("/").append(v.name.as_ref()), 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::Keyword(ref v) => pp.text(":").append(v.0.as_ref()),
Value::Text(ref v) => pp.text("\"").append(v.as_ref()).append("\""), 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()) _ => pp.text(self.to_string())
} }
} }

View file

@ -15,9 +15,16 @@ use std::cmp::{Ordering, Ord, PartialOrd};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::f64; 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 num::BigInt;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use uuid::Uuid;
use symbols;
/// Value represents one of the allowed values in an EDN string. /// Value represents one of the allowed values in an EDN string.
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
@ -25,9 +32,11 @@ pub enum Value {
Nil, Nil,
Boolean(bool), Boolean(bool),
Integer(i64), Integer(i64),
Instant(DateTime<UTC>),
BigInteger(BigInt), BigInteger(BigInt),
Float(OrderedFloat<f64>), Float(OrderedFloat<f64>),
Text(String), Text(String),
Uuid(Uuid),
PlainSymbol(symbols::PlainSymbol), PlainSymbol(symbols::PlainSymbol),
NamespacedSymbol(symbols::NamespacedSymbol), NamespacedSymbol(symbols::NamespacedSymbol),
Keyword(symbols::Keyword), Keyword(symbols::Keyword),
@ -52,9 +61,11 @@ pub enum SpannedValue {
Nil, Nil,
Boolean(bool), Boolean(bool),
Integer(i64), Integer(i64),
Instant(DateTime<UTC>),
BigInteger(BigInt), BigInteger(BigInt),
Float(OrderedFloat<f64>), Float(OrderedFloat<f64>),
Text(String), Text(String),
Uuid(Uuid),
PlainSymbol(symbols::PlainSymbol), PlainSymbol(symbols::PlainSymbol),
NamespacedSymbol(symbols::NamespacedSymbol), NamespacedSymbol(symbols::NamespacedSymbol),
Keyword(symbols::Keyword), Keyword(symbols::Keyword),
@ -123,9 +134,11 @@ impl From<SpannedValue> for Value {
SpannedValue::Nil => Value::Nil, SpannedValue::Nil => Value::Nil,
SpannedValue::Boolean(v) => Value::Boolean(v), SpannedValue::Boolean(v) => Value::Boolean(v),
SpannedValue::Integer(v) => Value::Integer(v), SpannedValue::Integer(v) => Value::Integer(v),
SpannedValue::Instant(v) => Value::Instant(v),
SpannedValue::BigInteger(v) => Value::BigInteger(v), SpannedValue::BigInteger(v) => Value::BigInteger(v),
SpannedValue::Float(v) => Value::Float(v), SpannedValue::Float(v) => Value::Float(v),
SpannedValue::Text(v) => Value::Text(v), SpannedValue::Text(v) => Value::Text(v),
SpannedValue::Uuid(v) => Value::Uuid(v),
SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v), SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v),
SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v), SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v),
SpannedValue::Keyword(v) => Value::Keyword(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_nil, $t::Nil);
def_is!(is_boolean, $t::Boolean(_)); def_is!(is_boolean, $t::Boolean(_));
def_is!(is_integer, $t::Integer(_)); def_is!(is_integer, $t::Integer(_));
def_is!(is_instant, $t::Instant(_));
def_is!(is_big_integer, $t::BigInteger(_)); def_is!(is_big_integer, $t::BigInteger(_));
def_is!(is_float, $t::Float(_)); def_is!(is_float, $t::Float(_));
def_is!(is_text, $t::Text(_)); def_is!(is_text, $t::Text(_));
def_is!(is_uuid, $t::Uuid(_));
def_is!(is_symbol, $t::PlainSymbol(_)); def_is!(is_symbol, $t::PlainSymbol(_));
def_is!(is_namespaced_symbol, $t::NamespacedSymbol(_)); def_is!(is_namespaced_symbol, $t::NamespacedSymbol(_));
def_is!(is_keyword, $t::Keyword(_)); 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_boolean, $t::Boolean, bool,);
def_as!(as_integer, $t::Integer, i64,); 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!(as_float, $t::Float, f64, |v: OrderedFloat<f64>| v.into_inner());
def_as_ref!(as_big_integer, $t::BigInteger, BigInt); def_as_ref!(as_big_integer, $t::BigInteger, BigInt);
def_as_ref!(as_ordered_float, $t::Float, OrderedFloat<f64>); def_as_ref!(as_ordered_float, $t::Float, OrderedFloat<f64>);
def_as_ref!(as_text, $t::Text, String); 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_symbol, $t::PlainSymbol, symbols::PlainSymbol);
def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol); def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol);
def_as_ref!(as_keyword, $t::Keyword, symbols::Keyword); 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_boolean, $t::Boolean, bool,);
def_into!(into_integer, $t::Integer, i64,); 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_big_integer, $t::BigInteger, BigInt,);
def_into!(into_ordered_float, $t::Float, OrderedFloat<f64>,); 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_float, $t::Float, f64, |v: OrderedFloat<f64>| v.into_inner());
def_into!(into_text, $t::Text, String,); 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_symbol, $t::PlainSymbol, symbols::PlainSymbol,);
def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,); def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,);
def_into!(into_keyword, $t::Keyword, symbols::Keyword,); def_into!(into_keyword, $t::Keyword, symbols::Keyword,);
@ -336,15 +355,17 @@ macro_rules! def_common_value_methods {
$t::Integer(_) => 2, $t::Integer(_) => 2,
$t::BigInteger(_) => 3, $t::BigInteger(_) => 3,
$t::Float(_) => 4, $t::Float(_) => 4,
$t::Text(_) => 5, $t::Instant(_) => 5,
$t::PlainSymbol(_) => 6, $t::Text(_) => 6,
$t::NamespacedSymbol(_) => 7, $t::Uuid(_) => 7,
$t::Keyword(_) => 8, $t::PlainSymbol(_) => 8,
$t::NamespacedKeyword(_) => 9, $t::NamespacedSymbol(_) => 9,
$t::Vector(_) => 10, $t::Keyword(_) => 10,
$t::List(_) => 11, $t::NamespacedKeyword(_) => 11,
$t::Set(_) => 12, $t::Vector(_) => 12,
$t::Map(_) => 13, $t::List(_) => 13,
$t::Set(_) => 14,
$t::Map(_) => 15,
} }
} }
@ -353,9 +374,11 @@ macro_rules! def_common_value_methods {
$t::Nil => false, $t::Nil => false,
$t::Boolean(_) => false, $t::Boolean(_) => false,
$t::Integer(_) => false, $t::Integer(_) => false,
$t::Instant(_) => false,
$t::BigInteger(_) => false, $t::BigInteger(_) => false,
$t::Float(_) => false, $t::Float(_) => false,
$t::Text(_) => false, $t::Text(_) => false,
$t::Uuid(_) => false,
$t::PlainSymbol(_) => false, $t::PlainSymbol(_) => false,
$t::NamespacedSymbol(_) => false, $t::NamespacedSymbol(_) => false,
$t::Keyword(_) => false, $t::Keyword(_) => false,
@ -389,9 +412,11 @@ macro_rules! def_common_value_ord {
(&$t::Nil, &$t::Nil) => Ordering::Equal, (&$t::Nil, &$t::Nil) => Ordering::Equal,
(&$t::Boolean(a), &$t::Boolean(b)) => b.cmp(&a), (&$t::Boolean(a), &$t::Boolean(b)) => b.cmp(&a),
(&$t::Integer(a), &$t::Integer(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::BigInteger(ref a), &$t::BigInteger(ref b)) => b.cmp(a),
(&$t::Float(ref a), &$t::Float(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::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::PlainSymbol(ref a), &$t::PlainSymbol(ref b)) => b.cmp(a),
(&$t::NamespacedSymbol(ref a), &$t::NamespacedSymbol(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), (&$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::Nil => write!($f, "nil"),
$t::Boolean(v) => write!($f, "{}", v), $t::Boolean(v) => write!($f, "{}", v),
$t::Integer(v) => write!($f, "{}", v), $t::Integer(v) => write!($f, "{}", v),
$t::Instant(v) => write!($f, "{}", v),
$t::BigInteger(ref v) => write!($f, "{}N", v), $t::BigInteger(ref v) => write!($f, "{}N", v),
// TODO: make sure float syntax is correct. // TODO: make sure float syntax is correct.
$t::Float(ref v) => { $t::Float(ref v) => {
@ -429,6 +455,7 @@ macro_rules! def_common_value_display {
} }
// TODO: EDN escaping. // TODO: EDN escaping.
$t::Text(ref v) => write!($f, "\"{}\"", v), $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::PlainSymbol(ref v) => v.fmt($f),
$t::NamespacedSymbol(ref v) => v.fmt($f), $t::NamespacedSymbol(ref v) => v.fmt($f),
$t::Keyword(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)] #[cfg(test)]
mod test { mod test {
extern crate chrono;
extern crate ordered_float; extern crate ordered_float;
extern crate num; extern crate num;
@ -532,9 +580,21 @@ mod test {
use parse; use parse;
use chrono::{
DateTime,
TimeZone,
UTC,
};
use num::BigInt; use num::BigInt;
use ordered_float::OrderedFloat; 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] #[test]
fn test_value_from() { fn test_value_from() {
assert_eq!(Value::from_float(42f64), Value::Float(OrderedFloat::from(42f64))); assert_eq!(Value::from_float(42f64), Value::Float(OrderedFloat::from(42f64)));

View file

@ -8,9 +8,11 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate chrono;
extern crate edn; extern crate edn;
extern crate num; extern crate num;
extern crate ordered_float; extern crate ordered_float;
extern crate uuid;
use std::collections::{BTreeSet, BTreeMap, LinkedList}; use std::collections::{BTreeSet, BTreeMap, LinkedList};
use std::iter::FromIterator; use std::iter::FromIterator;
@ -21,7 +23,16 @@ use num::traits::{Zero, One};
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use edn::parse::{self, ParseError}; 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::symbols;
use edn::utils; use edn::utils;
@ -55,7 +66,7 @@ macro_rules! fn_parse_into_value {
} }
// These look exactly like their `parse::foo` counterparts, but // 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. // if you want the original ValueAndSpan instance.
fn_parse_into_value!(nil); fn_parse_into_value!(nil);
fn_parse_into_value!(nan); 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] #[test]
fn test_bigint() { fn test_bigint() {
use self::Value::*; 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(), List(LinkedList::from_iter(vec![Integer(1)])));
assert_eq!(value("#{1}").unwrap(), Set(BTreeSet::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("{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] #[test]

View file

@ -568,7 +568,7 @@ impl ConjoiningClauses {
fn constrain_to_tx(&mut self, tx: &PatternNonValuePlace) { fn constrain_to_tx(&mut self, tx: &PatternNonValuePlace) {
match *tx { match *tx {
PatternNonValuePlace::Placeholder => (), PatternNonValuePlace::Placeholder => (),
_ => unimplemented!(), // TODO _ => unimplemented!(), // TODO: #440.
} }
} }
@ -874,4 +874,4 @@ mod tests {
assert!(alias_zero != alias_one); assert!(alias_zero != alias_one);
assert!(alias_one != alias_two); assert!(alias_one != alias_two);
} }
} }

View file

@ -81,7 +81,7 @@ impl ConjoiningClauses {
// Sorry for the duplication; Rust makes it a pain to abstract this. // 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. // 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.entity);
self.constrain_to_ref(&pattern.attribute); self.constrain_to_ref(&pattern.attribute);

View file

@ -54,6 +54,8 @@ impl ConjoiningClauses {
SrcVar(_) | SrcVar(_) |
Constant(NonIntegerConstant::Boolean(_)) | Constant(NonIntegerConstant::Boolean(_)) |
Constant(NonIntegerConstant::Text(_)) | Constant(NonIntegerConstant::Text(_)) |
Constant(NonIntegerConstant::Uuid(_)) |
Constant(NonIntegerConstant::Instant(_)) | // Instants are covered elsewhere.
Constant(NonIntegerConstant::BigInteger(_)) => { Constant(NonIntegerConstant::BigInteger(_)) => {
self.mark_known_empty(EmptyBecause::NonNumericArgument); self.mark_known_empty(EmptyBecause::NonNumericArgument);
bail!(ErrorKind::NonNumericArgument(function.clone(), position)); bail!(ErrorKind::NonNumericArgument(function.clone(), position));
@ -80,6 +82,8 @@ impl ConjoiningClauses {
Constant(NonIntegerConstant::Boolean(val)) => Ok(QueryValue::TypedValue(TypedValue::Boolean(val))), Constant(NonIntegerConstant::Boolean(val)) => Ok(QueryValue::TypedValue(TypedValue::Boolean(val))),
Constant(NonIntegerConstant::Float(f)) => Ok(QueryValue::TypedValue(TypedValue::Double(f))), 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::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!(), Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
SrcVar(_) => unimplemented!(), SrcVar(_) => unimplemented!(),
} }

View file

@ -11,6 +11,9 @@ matches = "0.1"
[dependencies.edn] [dependencies.edn]
path = "../edn" path = "../edn"
[dependencies.mentat_core]
path = "../core"
[dependencies.mentat_parser_utils] [dependencies.mentat_parser_utils]
path = "../parser-utils" path = "../parser-utils"

View file

@ -9,9 +9,12 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate edn; extern crate edn;
extern crate mentat_core;
extern crate mentat_query; extern crate mentat_query;
extern crate mentat_query_parser; extern crate mentat_query_parser;
use std::rc::Rc;
use edn::{ use edn::{
NamespacedKeyword, NamespacedKeyword,
PlainSymbol, PlainSymbol,
@ -23,6 +26,7 @@ use mentat_query::{
FindSpec, FindSpec,
FnArg, FnArg,
Limit, Limit,
NonIntegerConstant,
Order, Order,
OrJoin, OrJoin,
OrWhereClause, OrWhereClause,
@ -267,3 +271,17 @@ fn can_parse_limit() {
let variable_without_in = "[:find ?x :where [?x :foo/baz ?y] :limit ?limit]"; let variable_without_in = "[:find ?x :where [?x :foo/baz ?y] :limit ?limit]";
assert!(parse_find_string(variable_without_in).is_err()); 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")));
}

View file

@ -8,8 +8,6 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use std::rc::Rc;
use mentat_core::{ use mentat_core::{
SQLValueType, SQLValueType,
TypedValue, TypedValue,

View file

@ -76,8 +76,8 @@ fn prepopulated_schema() -> Schema {
prepopulated_typed_schema(ValueType::String) prepopulated_typed_schema(ValueType::String)
} }
fn make_arg(name: &'static str, value: &'static str) -> (String, Rc<String>) { fn make_arg(name: &'static str, value: &'static str) -> (String, Rc<mentat_sql::Value>) {
(name.to_string(), Rc::new(value.to_string())) (name.to_string(), Rc::new(mentat_sql::Value::Text(value.to_string())))
} }
#[test] #[test]
@ -550,4 +550,4 @@ fn test_complex_nested_or_join_type_projection() {
AS `c00` \ AS `c00` \
LIMIT 1"); LIMIT 1");
assert_eq!(args, vec![]); assert_eq!(args, vec![]);
} }

View file

@ -40,8 +40,18 @@ use std::collections::{
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use edn::{BigInt, OrderedFloat}; use edn::{
pub use edn::{NamespacedKeyword, PlainSymbol}; BigInt,
DateTime,
OrderedFloat,
Uuid,
UTC,
};
pub use edn::{
NamespacedKeyword,
PlainSymbol,
};
use mentat_core::{ use mentat_core::{
TypedValue, TypedValue,
@ -178,6 +188,8 @@ pub enum NonIntegerConstant {
BigInteger(BigInt), BigInteger(BigInt),
Float(OrderedFloat<f64>), Float(OrderedFloat<f64>),
Text(Rc<String>), Text(Rc<String>),
Instant(DateTime<UTC>),
Uuid(Uuid),
} }
impl NonIntegerConstant { impl NonIntegerConstant {
@ -187,6 +199,8 @@ impl NonIntegerConstant {
NonIntegerConstant::Boolean(v) => TypedValue::Boolean(v), NonIntegerConstant::Boolean(v) => TypedValue::Boolean(v),
NonIntegerConstant::Float(v) => TypedValue::Double(v), NonIntegerConstant::Float(v) => TypedValue::Double(v),
NonIntegerConstant::Text(v) => TypedValue::String(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))), Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))),
edn::SpannedValue::BigInteger(ref x) => edn::SpannedValue::BigInteger(ref x) =>
Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))), Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))),
edn::SpannedValue::Instant(x) =>
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))),
edn::SpannedValue::Text(ref x) => edn::SpannedValue::Text(ref x) =>
// TODO: intern strings. #398. // TODO: intern strings. #398.
Some(PatternValuePlace::Constant(NonIntegerConstant::Text(Rc::new(x.clone())))), 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,
} }
} }
} }
@ -761,4 +787,4 @@ impl ContainsVariables for Pattern {
acc_ref(acc, v) acc_ref(acc, v)
} }
} }
} }

View file

@ -7,5 +7,10 @@ workspace = ".."
error-chain = "0.8.1" error-chain = "0.8.1"
ordered-float = "0.4.0" ordered-float = "0.4.0"
[dependencies.rusqlite]
version = "0.10.1"
# System sqlite might be very old.
features = ["bundled", "limits"]
[dependencies.mentat_core] [dependencies.mentat_core]
path = "../core" path = "../core"

View file

@ -11,13 +11,20 @@
#[macro_use] #[macro_use]
extern crate error_chain; extern crate error_chain;
extern crate ordered_float; extern crate ordered_float;
extern crate rusqlite;
extern crate mentat_core; extern crate mentat_core;
use std::rc::Rc; use std::rc::Rc;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use mentat_core::TypedValue; use mentat_core::{
ToMicros,
TypedValue,
};
pub use rusqlite::types::Value;
error_chain! { error_chain! {
types { types {
@ -47,7 +54,7 @@ pub struct SQLQuery {
pub sql: String, pub sql: String,
/// These will eventually perhaps be rusqlite `ToSql` instances. /// 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: /// Gratefully based on Diesel's QueryBuilder trait:
@ -88,7 +95,7 @@ pub struct SQLiteQueryBuilder {
arg_prefix: String, arg_prefix: String,
arg_counter: i64, arg_counter: i64,
args: Vec<(String, Rc<String>)>, args: Vec<(String, Rc<rusqlite::types::Value>)>,
} }
impl SQLiteQueryBuilder { 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); let arg = format!("{}{}", self.arg_prefix, self.arg_counter);
self.arg_counter = self.arg_counter + 1; self.arg_counter = self.arg_counter + 1;
self.push_named_arg(arg.as_str()); 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" }), &Boolean(v) => self.push_sql(if v { "1" } else { "0" }),
&Long(v) => self.push_sql(v.to_string().as_str()), &Long(v) => self.push_sql(v.to_string().as_str()),
&Double(OrderedFloat(v)) => self.push_sql(v.to_string().as_str()), &Double(OrderedFloat(v)) => self.push_sql(v.to_string().as_str()),
&Instant(dt) => {
// These are both `Rc`. We can just clone an `Rc<String>`, but we self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
// must make a new single `String`, wrapped in an `Rc`, for keywords. },
&String(ref s) => self.push_static_arg(s.clone()), &Uuid(ref u) => {
&Keyword(ref s) => self.push_static_arg(Rc::new(s.as_ref().to_string())), // 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(()) Ok(())
} }
@ -180,6 +201,10 @@ impl QueryBuilder for SQLiteQueryBuilder {
mod tests { mod tests {
use super::*; use super::*;
fn string_arg(s: &str) -> Rc<rusqlite::types::Value> {
Rc::new(rusqlite::types::Value::Text(s.to_string()))
}
#[test] #[test]
fn test_sql() { fn test_sql() {
let mut s = SQLiteQueryBuilder::new(); let mut s = SQLiteQueryBuilder::new();
@ -188,14 +213,14 @@ mod tests {
s.push_sql(" WHERE "); s.push_sql(" WHERE ");
s.push_identifier("bar").unwrap(); s.push_identifier("bar").unwrap();
s.push_sql(" = "); 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_sql(" OR ");
s.push_static_arg(Rc::new("swoogle".to_string())); s.push_static_arg(string_arg("swoogle"));
let q = s.finish(); let q = s.finish();
assert_eq!(q.sql.as_str(), "SELECT `foo` WHERE `bar` = $v0 OR $v1"); assert_eq!(q.sql.as_str(), "SELECT `foo` WHERE `bar` = $v0 OR $v1");
assert_eq!(q.args, assert_eq!(q.args,
vec![("$v0".to_string(), Rc::new("frobnicate".to_string())), vec![("$v0".to_string(), string_arg("frobnicate")),
("$v1".to_string(), Rc::new("swoogle".to_string()))]); ("$v1".to_string(), string_arg("swoogle"))]);
} }
} }

View file

@ -8,15 +8,22 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate chrono;
extern crate time; extern crate time;
extern crate mentat; extern crate mentat;
extern crate mentat_core; extern crate mentat_core;
extern crate mentat_db; extern crate mentat_db;
use std::str::FromStr;
use chrono::FixedOffset;
use mentat_core::{ use mentat_core::{
TypedValue, TypedValue,
ValueType, ValueType,
UTC,
Uuid,
}; };
use mentat::{ use mentat::{
@ -28,6 +35,8 @@ use mentat::{
q_once, q_once,
}; };
use mentat::conn::Conn;
use mentat::errors::{ use mentat::errors::{
Error, Error,
ErrorKind, ErrorKind,
@ -46,7 +55,7 @@ fn test_rel() {
let end = time::PreciseTime::now(); let end = time::PreciseTime::now();
// This will need to change each time we add a default ident. // 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. // Every row is a pair of a Ref and a Keyword.
if let QueryResults::Rel(ref rel) = results { if let QueryResults::Rel(ref rel) = results {
@ -154,7 +163,7 @@ fn test_coll() {
.expect("Query failed"); .expect("Query failed");
let end = time::PreciseTime::now(); let end = time::PreciseTime::now();
assert_eq!(37, results.len()); assert_eq!(39, results.len());
if let QueryResults::Coll(ref coll) = results { if let QueryResults::Coll(ref coll) = results {
assert!(coll.iter().all(|item| item.matches_type(ValueType::Ref))); assert!(coll.iter().all(|item| item.matches_type(ValueType::Ref)));
@ -204,3 +213,44 @@ fn test_unbound_inputs() {
_ => panic!("Expected unbound variables."), _ => 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."),
}
}

View file

@ -25,6 +25,33 @@ use mentat_tx::entities::{
}; };
use mentat_tx_parser::Tx; 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] #[test]
fn test_entities() { fn test_entities() {
let input = r#" let input = r#"