* 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"
|
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"
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
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 ::{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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
14
db/src/tx.rs
14
db/src/tx.rs
|
@ -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() {
|
||||||
|
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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")));
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"))]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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#"
|
||||||
|
|
Loading…
Reference in a new issue