Part 1: parse #inst in EDN and throughout query engine.
This commit is contained in:
parent
f21a285afb
commit
2b53abbd85
9 changed files with 153 additions and 14 deletions
|
@ -33,6 +33,13 @@ pub use edn::{
|
|||
Uuid,
|
||||
};
|
||||
|
||||
pub use edn::{
|
||||
DateTime,
|
||||
FromMicros,
|
||||
ToMicros,
|
||||
UTC,
|
||||
};
|
||||
|
||||
/// Core types defining a Mentat knowledge base.
|
||||
|
||||
/// Represents one entid in the entid space.
|
||||
|
@ -123,6 +130,7 @@ pub enum TypedValue {
|
|||
Boolean(bool),
|
||||
Long(i64),
|
||||
Double(OrderedFloat<f64>),
|
||||
Instant(DateTime<UTC>),
|
||||
// TODO: &str throughout?
|
||||
String(Rc<String>),
|
||||
Keyword(Rc<NamespacedKeyword>),
|
||||
|
@ -147,6 +155,7 @@ impl TypedValue {
|
|||
&TypedValue::Ref(_) => ValueType::Ref,
|
||||
&TypedValue::Boolean(_) => ValueType::Boolean,
|
||||
&TypedValue::Long(_) => ValueType::Long,
|
||||
&TypedValue::Instant(_) => ValueType::Instant,
|
||||
&TypedValue::Double(_) => ValueType::Double,
|
||||
&TypedValue::String(_) => ValueType::String,
|
||||
&TypedValue::Keyword(_) => ValueType::Keyword,
|
||||
|
@ -167,6 +176,10 @@ impl TypedValue {
|
|||
pub fn typed_string(s: &str) -> TypedValue {
|
||||
TypedValue::String(Rc::new(s.to_string()))
|
||||
}
|
||||
|
||||
pub fn current_instant() -> TypedValue {
|
||||
TypedValue::Instant(UTC::now())
|
||||
}
|
||||
}
|
||||
|
||||
// Put this here rather than in `db` simply because it's widely needed.
|
||||
|
|
|
@ -11,6 +11,7 @@ build = "build.rs"
|
|||
readme = "./README.md"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.3"
|
||||
itertools = "0.5.9"
|
||||
num = "0.1.35"
|
||||
ordered-float = "0.4.0"
|
||||
|
|
|
@ -14,6 +14,11 @@ use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
|||
use std::iter::FromIterator;
|
||||
use std::f64::{NAN, INFINITY, NEG_INFINITY};
|
||||
|
||||
use chrono::{
|
||||
DateTime,
|
||||
TimeZone,
|
||||
UTC
|
||||
};
|
||||
use num::BigInt;
|
||||
use ordered_float::OrderedFloat;
|
||||
use uuid::Uuid;
|
||||
|
@ -143,6 +148,39 @@ 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> =
|
||||
"\"" 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_millis -> DateTime<UTC> =
|
||||
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 "#inst" whitespace+ t:(inst_millis / 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")
|
||||
|
@ -234,7 +272,7 @@ pub map -> ValueAndSpan =
|
|||
// It's important that float comes before integer or the parser assumes that
|
||||
// floats are integers and fails to parse
|
||||
pub value -> ValueAndSpan =
|
||||
__ v:(nil / nan / infinity / boolean / float / octalinteger / hexinteger / basedinteger / uuid / bigint / integer / text / keyword / symbol / list / vector / map / set) __ {
|
||||
__ v:(nil / nan / infinity / boolean / float / octalinteger / hexinteger / basedinteger / inst / uuid / bigint / integer / text / keyword / symbol / list / vector / map / set) __ {
|
||||
v
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
extern crate chrono;
|
||||
extern crate itertools;
|
||||
extern crate num;
|
||||
extern crate ordered_float;
|
||||
|
@ -25,11 +26,19 @@ pub mod parse {
|
|||
}
|
||||
|
||||
// Re-export the types we use.
|
||||
pub use chrono::{DateTime, UTC};
|
||||
pub use num::BigInt;
|
||||
pub use ordered_float::OrderedFloat;
|
||||
pub use uuid::Uuid;
|
||||
|
||||
// Export from our modules.
|
||||
pub use parse::ParseError;
|
||||
pub use types::{Span, SpannedValue, Value, ValueAndSpan};
|
||||
pub use types::{
|
||||
FromMicros,
|
||||
Span,
|
||||
SpannedValue,
|
||||
ToMicros,
|
||||
Value,
|
||||
ValueAndSpan,
|
||||
};
|
||||
pub use symbols::{Keyword, NamespacedKeyword, PlainSymbol, NamespacedSymbol};
|
||||
|
|
|
@ -15,6 +15,11 @@ use std::cmp::{Ordering, Ord, PartialOrd};
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::f64;
|
||||
|
||||
use chrono::{
|
||||
DateTime,
|
||||
TimeZone, // For UTC::timestamp. The compiler incorrectly complains that this is unused.
|
||||
UTC,
|
||||
};
|
||||
use num::BigInt;
|
||||
use ordered_float::OrderedFloat;
|
||||
use uuid::Uuid;
|
||||
|
@ -27,6 +32,7 @@ pub enum Value {
|
|||
Nil,
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Instant(DateTime<UTC>),
|
||||
BigInteger(BigInt),
|
||||
Float(OrderedFloat<f64>),
|
||||
Text(String),
|
||||
|
@ -55,6 +61,7 @@ pub enum SpannedValue {
|
|||
Nil,
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Instant(DateTime<UTC>),
|
||||
BigInteger(BigInt),
|
||||
Float(OrderedFloat<f64>),
|
||||
Text(String),
|
||||
|
@ -127,6 +134,7 @@ impl From<SpannedValue> for Value {
|
|||
SpannedValue::Nil => Value::Nil,
|
||||
SpannedValue::Boolean(v) => Value::Boolean(v),
|
||||
SpannedValue::Integer(v) => Value::Integer(v),
|
||||
SpannedValue::Instant(v) => Value::Instant(v),
|
||||
SpannedValue::BigInteger(v) => Value::BigInteger(v),
|
||||
SpannedValue::Float(v) => Value::Float(v),
|
||||
SpannedValue::Text(v) => Value::Text(v),
|
||||
|
@ -273,6 +281,7 @@ macro_rules! def_common_value_methods {
|
|||
def_is!(is_nil, $t::Nil);
|
||||
def_is!(is_boolean, $t::Boolean(_));
|
||||
def_is!(is_integer, $t::Integer(_));
|
||||
def_is!(is_instant, $t::Instant(_));
|
||||
def_is!(is_big_integer, $t::BigInteger(_));
|
||||
def_is!(is_float, $t::Float(_));
|
||||
def_is!(is_text, $t::Text(_));
|
||||
|
@ -294,6 +303,7 @@ macro_rules! def_common_value_methods {
|
|||
|
||||
def_as!(as_boolean, $t::Boolean, bool,);
|
||||
def_as!(as_integer, $t::Integer, i64,);
|
||||
def_as!(as_instant, $t::Instant, DateTime<UTC>,);
|
||||
def_as!(as_float, $t::Float, f64, |v: OrderedFloat<f64>| v.into_inner());
|
||||
|
||||
def_as_ref!(as_big_integer, $t::BigInteger, BigInt);
|
||||
|
@ -311,6 +321,7 @@ macro_rules! def_common_value_methods {
|
|||
|
||||
def_into!(into_boolean, $t::Boolean, bool,);
|
||||
def_into!(into_integer, $t::Integer, i64,);
|
||||
def_into!(into_instant, $t::Instant, DateTime<UTC>,);
|
||||
def_into!(into_big_integer, $t::BigInteger, BigInt,);
|
||||
def_into!(into_ordered_float, $t::Float, OrderedFloat<f64>,);
|
||||
def_into!(into_float, $t::Float, f64, |v: OrderedFloat<f64>| v.into_inner());
|
||||
|
@ -344,16 +355,17 @@ macro_rules! def_common_value_methods {
|
|||
$t::Integer(_) => 2,
|
||||
$t::BigInteger(_) => 3,
|
||||
$t::Float(_) => 4,
|
||||
$t::Text(_) => 5,
|
||||
$t::Uuid(_) => 6,
|
||||
$t::PlainSymbol(_) => 7,
|
||||
$t::NamespacedSymbol(_) => 8,
|
||||
$t::Keyword(_) => 9,
|
||||
$t::NamespacedKeyword(_) => 10,
|
||||
$t::Vector(_) => 11,
|
||||
$t::List(_) => 12,
|
||||
$t::Set(_) => 13,
|
||||
$t::Map(_) => 14,
|
||||
$t::Instant(_) => 5,
|
||||
$t::Text(_) => 6,
|
||||
$t::Uuid(_) => 7,
|
||||
$t::PlainSymbol(_) => 8,
|
||||
$t::NamespacedSymbol(_) => 9,
|
||||
$t::Keyword(_) => 10,
|
||||
$t::NamespacedKeyword(_) => 11,
|
||||
$t::Vector(_) => 12,
|
||||
$t::List(_) => 13,
|
||||
$t::Set(_) => 14,
|
||||
$t::Map(_) => 15,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,6 +374,7 @@ macro_rules! def_common_value_methods {
|
|||
$t::Nil => false,
|
||||
$t::Boolean(_) => false,
|
||||
$t::Integer(_) => false,
|
||||
$t::Instant(_) => false,
|
||||
$t::BigInteger(_) => false,
|
||||
$t::Float(_) => false,
|
||||
$t::Text(_) => false,
|
||||
|
@ -399,6 +412,7 @@ macro_rules! def_common_value_ord {
|
|||
(&$t::Nil, &$t::Nil) => Ordering::Equal,
|
||||
(&$t::Boolean(a), &$t::Boolean(b)) => b.cmp(&a),
|
||||
(&$t::Integer(a), &$t::Integer(b)) => b.cmp(&a),
|
||||
(&$t::Instant(a), &$t::Instant(b)) => b.cmp(&a),
|
||||
(&$t::BigInteger(ref a), &$t::BigInteger(ref b)) => b.cmp(a),
|
||||
(&$t::Float(ref a), &$t::Float(ref b)) => b.cmp(a),
|
||||
(&$t::Text(ref a), &$t::Text(ref b)) => b.cmp(a),
|
||||
|
@ -425,6 +439,7 @@ macro_rules! def_common_value_display {
|
|||
$t::Nil => write!($f, "nil"),
|
||||
$t::Boolean(v) => write!($f, "{}", v),
|
||||
$t::Integer(v) => write!($f, "{}", v),
|
||||
$t::Instant(v) => write!($f, "{}", v),
|
||||
$t::BigInteger(ref v) => write!($f, "{}N", v),
|
||||
// TODO: make sure float syntax is correct.
|
||||
$t::Float(ref v) => {
|
||||
|
@ -530,8 +545,29 @@ impl Display for ValueAndSpan {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait FromMicros {
|
||||
fn from_micros(ts: i64) -> Self;
|
||||
}
|
||||
|
||||
impl FromMicros for DateTime<UTC> {
|
||||
fn from_micros(ts: i64) -> Self {
|
||||
UTC.timestamp(ts / 100_000, ((ts % 100_000).abs() as u32) * 1_000)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToMicros {
|
||||
fn to_micros(&self) -> i64;
|
||||
}
|
||||
|
||||
impl ToMicros for DateTime<UTC> {
|
||||
fn to_micros(&self) -> i64 {
|
||||
(self.timestamp() * 100_000) + (self.timestamp_subsec_micros() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate chrono;
|
||||
extern crate ordered_float;
|
||||
extern crate num;
|
||||
|
||||
|
@ -544,9 +580,21 @@ mod test {
|
|||
|
||||
use parse;
|
||||
|
||||
use chrono::{
|
||||
DateTime,
|
||||
TimeZone,
|
||||
UTC,
|
||||
};
|
||||
use num::BigInt;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
#[test]
|
||||
fn test_micros_roundtrip() {
|
||||
let ts_micros: i64 = 1493399581314000;
|
||||
let dt = DateTime::<UTC>::from_micros(ts_micros);
|
||||
assert_eq!(dt.to_micros(), ts_micros);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_from() {
|
||||
assert_eq!(Value::from_float(42f64), Value::Float(OrderedFloat::from(42f64)));
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
extern crate chrono;
|
||||
extern crate edn;
|
||||
extern crate num;
|
||||
extern crate ordered_float;
|
||||
|
@ -22,7 +23,17 @@ use num::traits::{Zero, One};
|
|||
use ordered_float::OrderedFloat;
|
||||
|
||||
use edn::parse::{self, ParseError};
|
||||
use edn::types::{Value, SpannedValue, Span, ValueAndSpan};
|
||||
use edn::types::{
|
||||
Value,
|
||||
ValueAndSpan,
|
||||
Span,
|
||||
SpannedValue,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use chrono::{
|
||||
TimeZone,
|
||||
UTC,
|
||||
};
|
||||
use edn::symbols;
|
||||
use edn::utils;
|
||||
|
||||
|
@ -412,6 +423,11 @@ fn test_value() {
|
|||
assert_eq!(value("(1)").unwrap(), List(LinkedList::from_iter(vec![Integer(1)])));
|
||||
assert_eq!(value("#{1}").unwrap(), Set(BTreeSet::from_iter(vec![Integer(1)])));
|
||||
assert_eq!(value("{1 2}").unwrap(), Map(BTreeMap::from_iter(vec![(Integer(1), Integer(2))])));
|
||||
assert_eq!(value("#uuid \"e43c6f3e-3123-49b7-8098-9b47a7bc0fa4\"").unwrap(),
|
||||
Uuid(uuid::Uuid::parse_str("e43c6f3e-3123-49b7-8098-9b47a7bc0fa4").unwrap()));
|
||||
assert_eq!(value("#inst 1493410985187").unwrap(), Instant(UTC.timestamp(1493410985, 187000000)));
|
||||
assert_eq!(value("#inst \"2017-04-28T20:23:05.187Z\"").unwrap(),
|
||||
Instant(UTC.timestamp(1493410985, 187000000)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -55,6 +55,7 @@ impl ConjoiningClauses {
|
|||
Constant(NonIntegerConstant::Boolean(_)) |
|
||||
Constant(NonIntegerConstant::Text(_)) |
|
||||
Constant(NonIntegerConstant::Uuid(_)) |
|
||||
Constant(NonIntegerConstant::Instant(_)) | // Instants are covered elsewhere.
|
||||
Constant(NonIntegerConstant::BigInteger(_)) => {
|
||||
self.mark_known_empty(EmptyBecause::NonNumericArgument);
|
||||
bail!(ErrorKind::NonNumericArgument(function.clone(), position));
|
||||
|
@ -82,6 +83,7 @@ impl ConjoiningClauses {
|
|||
Constant(NonIntegerConstant::Float(f)) => Ok(QueryValue::TypedValue(TypedValue::Double(f))),
|
||||
Constant(NonIntegerConstant::Text(s)) => Ok(QueryValue::TypedValue(TypedValue::typed_string(s.as_str()))),
|
||||
Constant(NonIntegerConstant::Uuid(u)) => Ok(QueryValue::TypedValue(TypedValue::Uuid(u))),
|
||||
Constant(NonIntegerConstant::Instant(u)) => Ok(QueryValue::TypedValue(TypedValue::Instant(u))),
|
||||
Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
|
||||
SrcVar(_) => unimplemented!(),
|
||||
}
|
||||
|
|
|
@ -42,8 +42,10 @@ use std::rc::Rc;
|
|||
|
||||
use edn::{
|
||||
BigInt,
|
||||
DateTime,
|
||||
OrderedFloat,
|
||||
Uuid,
|
||||
UTC,
|
||||
};
|
||||
|
||||
pub use edn::{
|
||||
|
@ -186,6 +188,7 @@ pub enum NonIntegerConstant {
|
|||
BigInteger(BigInt),
|
||||
Float(OrderedFloat<f64>),
|
||||
Text(Rc<String>),
|
||||
Instant(DateTime<UTC>),
|
||||
Uuid(Uuid),
|
||||
}
|
||||
|
||||
|
@ -196,6 +199,7 @@ impl NonIntegerConstant {
|
|||
NonIntegerConstant::Boolean(v) => TypedValue::Boolean(v),
|
||||
NonIntegerConstant::Float(v) => TypedValue::Double(v),
|
||||
NonIntegerConstant::Text(v) => TypedValue::String(v),
|
||||
NonIntegerConstant::Instant(v) => TypedValue::Instant(v),
|
||||
NonIntegerConstant::Uuid(v) => TypedValue::Uuid(v),
|
||||
}
|
||||
}
|
||||
|
@ -320,6 +324,8 @@ impl FromValue<PatternValuePlace> for PatternValuePlace {
|
|||
Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))),
|
||||
edn::SpannedValue::BigInteger(ref x) =>
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))),
|
||||
edn::SpannedValue::Instant(x) =>
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))),
|
||||
edn::SpannedValue::Text(ref x) =>
|
||||
// TODO: intern strings. #398.
|
||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Text(Rc::new(x.clone())))),
|
||||
|
|
|
@ -19,7 +19,10 @@ use std::rc::Rc;
|
|||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use mentat_core::TypedValue;
|
||||
use mentat_core::{
|
||||
ToMicros,
|
||||
TypedValue,
|
||||
};
|
||||
|
||||
pub use rusqlite::types::Value;
|
||||
|
||||
|
@ -140,6 +143,9 @@ impl QueryBuilder for SQLiteQueryBuilder {
|
|||
&Boolean(v) => self.push_sql(if v { "1" } else { "0" }),
|
||||
&Long(v) => self.push_sql(v.to_string().as_str()),
|
||||
&Double(OrderedFloat(v)) => self.push_sql(v.to_string().as_str()),
|
||||
&Instant(dt) => {
|
||||
self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead?
|
||||
},
|
||||
&Uuid(ref u) => {
|
||||
// Get a byte array.
|
||||
let bytes = u.as_bytes().clone();
|
||||
|
|
Loading…
Reference in a new issue