Part 2: Turn (type-function ?var) into (type ?var type-keyword).

This is more general (the parser doesn't encode the set of known
types), and avoids a dependency on `ValueType`.
This commit is contained in:
Nick Alexander 2018-05-28 14:34:42 -07:00
parent ad9a1394a3
commit 1d8d94f887
10 changed files with 60 additions and 83 deletions

View file

@ -131,6 +131,24 @@ impl ValueType {
}) })
} }
pub fn from_keyword(keyword: &Keyword) -> Option<Self> {
if keyword.namespace() != Some("db.type") {
return None;
}
return match keyword.name() {
"ref" => Some(ValueType::Ref),
"boolean" => Some(ValueType::Boolean),
"instant" => Some(ValueType::Instant),
"long" => Some(ValueType::Long),
"double" => Some(ValueType::Double),
"string" => Some(ValueType::String),
"keyword" => Some(ValueType::Keyword),
"uuid" => Some(ValueType::Uuid),
_ => None,
}
}
pub fn into_typed_value(self) -> TypedValue { pub fn into_typed_value(self) -> TypedValue {
TypedValue::typed_ns_keyword("db.type", match self { TypedValue::typed_ns_keyword("db.type", match self {
ValueType::Ref => "ref", ValueType::Ref => "ref",

View file

@ -16,6 +16,7 @@ use mentat_core::{
use mentat_query::{ use mentat_query::{
FnArg, FnArg,
PlainSymbol,
Predicate, Predicate,
TypeAnnotation, TypeAnnotation,
}; };
@ -66,7 +67,10 @@ impl ConjoiningClauses {
/// Apply a type annotation, which is a construct like a predicate that constrains the argument /// Apply a type annotation, which is a construct like a predicate that constrains the argument
/// to be a specific ValueType. /// to be a specific ValueType.
pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> { pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> {
self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(anno.value_type)); match ValueType::from_keyword(&anno.value_type) {
Some(value_type) => self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type)),
None => bail!(ErrorKind::InvalidArgumentType(PlainSymbol::plain("type"), ValueTypeSet::any(), 2)),
}
Ok(()) Ok(())
} }

View file

@ -43,22 +43,12 @@ fn prepopulated_schema() -> Schema {
#[test] #[test]
fn test_empty_known() { fn test_empty_known() {
let type_names = [
"boolean",
"long",
"double",
"string",
"keyword",
"uuid",
"instant",
"ref",
];
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
for known_type in type_names.iter() { for known_type in ValueType::all_enums().iter() {
for required in type_names.iter() { for required in ValueType::all_enums().iter() {
let q = format!("[:find ?e :where [?e :test/{} ?v] [({} ?v)]]", let q = format!("[:find ?e :where [?e :test/{} ?v] [(type ?v {})]]",
known_type, required); known_type.into_keyword().name(), required);
println!("Query: {}", q); println!("Query: {}", q);
let cc = alg(known, &q); let cc = alg(known, &q);
// It should only be empty if the known type and our requirement differ. // It should only be empty if the known type and our requirement differ.
@ -72,7 +62,7 @@ fn test_empty_known() {
fn test_multiple() { fn test_multiple() {
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
let q = "[:find ?e :where [?e _ ?v] [(long ?v)] [(double ?v)]]"; let q = "[:find ?e :where [?e _ ?v] [(type ?v :db.type/long)] [(type ?v :db.type/double)]]";
let cc = alg(known, &q); let cc = alg(known, &q);
assert!(cc.empty_because.is_some()); assert!(cc.empty_because.is_some());
} }
@ -81,5 +71,5 @@ fn test_multiple() {
fn test_unbound() { fn test_unbound() {
let schema = prepopulated_schema(); let schema = prepopulated_schema();
let known = Known::for_schema(&schema); let known = Known::for_schema(&schema);
bails(known, "[:find ?e :where [(string ?e)]]"); bails(known, "[:find ?e :where [(type ?e :db.type/string)]]");
} }

View file

@ -7,14 +7,10 @@ workspace = ".."
combine = "2.3.2" combine = "2.3.2"
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" } error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
maplit = "0.1" maplit = "0.1"
matches = "0.1"
[dependencies.edn] [dependencies.edn]
path = "../edn" path = "../edn"
[dependencies.mentat_core]
path = "../core"
[dependencies.mentat_parser_utils] [dependencies.mentat_parser_utils]
path = "../parser-utils" path = "../parser-utils"

View file

@ -16,9 +16,6 @@ extern crate maplit;
#[macro_use] #[macro_use]
extern crate error_chain; extern crate error_chain;
#[macro_use]
extern crate matches;
extern crate edn; extern crate edn;
#[macro_use] #[macro_use]

View file

@ -12,7 +12,6 @@ extern crate combine;
extern crate edn; extern crate edn;
extern crate mentat_parser_utils; extern crate mentat_parser_utils;
extern crate mentat_query; extern crate mentat_query;
extern crate mentat_core;
use std; // To refer to std::result::Result. use std; // To refer to std::result::Result.
@ -34,8 +33,6 @@ use self::combine::{
use self::combine::combinator::{any, choice, or, try}; use self::combine::combinator::{any, choice, or, try};
use self::mentat_core::ValueType;
use errors::{ use errors::{
Error, Error,
ErrorKind, ErrorKind,
@ -100,6 +97,10 @@ def_parser!(Query, variable, Variable, {
satisfy_map(Variable::from_value) satisfy_map(Variable::from_value)
}); });
def_parser!(Query, keyword, edn::Keyword, {
satisfy_map(|v: &edn::ValueAndSpan| v.inner.as_keyword().cloned())
});
def_parser!(Query, source_var, SrcVar, { def_parser!(Query, source_var, SrcVar, {
satisfy_map(SrcVar::from_value) satisfy_map(SrcVar::from_value)
}); });
@ -178,6 +179,8 @@ def_matches_plain_symbol!(Where, not, "not");
def_matches_plain_symbol!(Where, not_join, "not-join"); def_matches_plain_symbol!(Where, not_join, "not-join");
def_matches_plain_symbol!(Where, type_symbol, "type");
def_parser!(Where, rule_vars, BTreeSet<Variable>, { def_parser!(Where, rule_vars, BTreeSet<Variable>, {
seq() seq()
.of_exactly(many1(Query::variable()).and_then(unique_vars)) .of_exactly(many1(Query::variable()).and_then(unique_vars))
@ -339,36 +342,14 @@ def_parser!(Where, pred, WhereClause, {
}))) })))
}); });
def_parser!(Query, type_anno_type, ValueType, {
satisfy_map(|v: &edn::ValueAndSpan| {
match v.inner {
edn::SpannedValue::PlainSymbol(ref s) => {
let name = s.0.as_str();
match name {
"ref" => Some(ValueType::Ref),
"boolean" => Some(ValueType::Boolean),
"instant" => Some(ValueType::Instant),
"long" => Some(ValueType::Long),
"double" => Some(ValueType::Double),
"string" => Some(ValueType::String),
"keyword" => Some(ValueType::Keyword),
"uuid" => Some(ValueType::Uuid),
_ => None
}
},
_ => None,
}
})
});
/// A type annotation. /// A type annotation.
def_parser!(Where, type_annotation, WhereClause, { def_parser!(Where, type_annotation, WhereClause, {
// Accept either a nested list or a nested vector here: // Accept either a nested list or a nested vector here:
// `[(string ?x)]` or `[[string ?x]]` // `[(type ?x :db.type/string)]` or `[[type ?x :db.type/long]]`
vector() vector()
.of_exactly(seq() .of_exactly(seq()
.of_exactly((Query::type_anno_type(), Query::variable()) .of_exactly((Where::type_symbol(), Query::variable(), Query::keyword())
.map(|(ty, var)| { .map(|(_, var, ty)| {
WhereClause::TypeAnnotation( WhereClause::TypeAnnotation(
TypeAnnotation { TypeAnnotation {
value_type: ty, value_type: ty,
@ -759,7 +740,10 @@ mod test {
let input = input.with_spans(); let input = input.with_spans();
let mut par = Where::pattern(); let mut par = Where::pattern();
let result = par.parse(input.atom_stream()); let result = par.parse(input.atom_stream());
assert!(matches!(result, Err(_)), "Expected a parse error."); match result {
Err(_) => (),
_ => assert!(false, "Expected a parse error"),
}
} }
#[test] #[test]
@ -1137,15 +1121,16 @@ mod test {
#[test] #[test]
fn test_type_anno() { fn test_type_anno() {
assert_edn_parses_to!(Where::type_annotation, assert_edn_parses_to!(Where::type_annotation,
"[(string ?x)]", "[(type ?x :db.type/string)]",
WhereClause::TypeAnnotation(TypeAnnotation { WhereClause::TypeAnnotation(TypeAnnotation {
value_type: ValueType::String, value_type: edn::Keyword::namespaced("db.type", "string"),
variable: Variable::from_valid_name("?x"), variable: Variable::from_valid_name("?x"),
})); }));
// We don't check for valid types, or even that the type is namespaced.
assert_edn_parses_to!(Where::clause, assert_edn_parses_to!(Where::clause,
"[[long ?foo]]", "[[type ?foo :db_type_long]]",
WhereClause::TypeAnnotation(TypeAnnotation { WhereClause::TypeAnnotation(TypeAnnotation {
value_type: ValueType::Long, value_type: edn::Keyword::plain("db_type_long"),
variable: Variable::from_valid_name("?foo"), variable: Variable::from_valid_name("?foo"),
})); }));

View file

@ -12,7 +12,6 @@
extern crate maplit; extern crate maplit;
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;

View file

@ -348,7 +348,7 @@ fn test_unknown_ident() {
fn test_type_required_long() { fn test_type_required_long() {
let schema = Schema::default(); let schema = Schema::default();
let query = r#"[:find ?x :where [?x _ ?e] [(long ?e)]]"#; let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/long)]]"#;
let SQLQuery { sql, args } = translate(&schema, query); let SQLQuery { sql, args } = translate(&schema, query);
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \
@ -363,7 +363,7 @@ fn test_type_required_long() {
fn test_type_required_double() { fn test_type_required_double() {
let schema = Schema::default(); let schema = Schema::default();
let query = r#"[:find ?x :where [?x _ ?e] [(double ?e)]]"#; let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/double)]]"#;
let SQLQuery { sql, args } = translate(&schema, query); let SQLQuery { sql, args } = translate(&schema, query);
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \
@ -378,7 +378,7 @@ fn test_type_required_double() {
fn test_type_required_boolean() { fn test_type_required_boolean() {
let schema = Schema::default(); let schema = Schema::default();
let query = r#"[:find ?x :where [?x _ ?e] [(boolean ?e)]]"#; let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/boolean)]]"#;
let SQLQuery { sql, args } = translate(&schema, query); let SQLQuery { sql, args } = translate(&schema, query);
assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \
@ -392,7 +392,7 @@ fn test_type_required_boolean() {
fn test_type_required_string() { fn test_type_required_string() {
let schema = Schema::default(); let schema = Schema::default();
let query = r#"[:find ?x :where [?x _ ?e] [(string ?e)]]"#; let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/string)]]"#;
let SQLQuery { sql, args } = translate(&schema, query); let SQLQuery { sql, args } = translate(&schema, query);
// Note: strings should use `all_datoms` and not `datoms`. // Note: strings should use `all_datoms` and not `datoms`.

View file

@ -58,7 +58,6 @@ use mentat_core::{
FromRc, FromRc,
TypedValue, TypedValue,
ValueRc, ValueRc,
ValueType,
}; };
pub type SrcVarName = String; // Do not include the required syntactic '$'. pub type SrcVarName = String; // Do not include the required syntactic '$'.
@ -967,7 +966,7 @@ pub struct NotJoin {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct TypeAnnotation { pub struct TypeAnnotation {
pub value_type: ValueType, pub value_type: Keyword,
pub variable: Variable, pub variable: Variable,
} }

View file

@ -602,7 +602,7 @@ fn test_aggregates_type_handling() {
// You can't sum instants. // You can't sum instants.
let r = store.q_once(r#"[:find (sum ?v) . let r = store.q_once(r#"[:find (sum ?v) .
:where [_ _ ?v] [(instant ?v)]]"#, :where [_ _ ?v] [(type ?v :db.type/instant)]]"#,
None); None);
match r { match r {
Result::Err( Result::Err(
@ -623,7 +623,7 @@ fn test_aggregates_type_handling() {
// But you can count them. // But you can count them.
let r = store.q_once(r#"[:find (count ?v) . let r = store.q_once(r#"[:find (count ?v) .
:where [_ _ ?v] [(instant ?v)]]"#, :where [_ _ ?v] [(type ?v :db.type/instant)]]"#,
None) None)
.into_scalar_result() .into_scalar_result()
.expect("results") .expect("results")
@ -634,7 +634,7 @@ fn test_aggregates_type_handling() {
// And you can min them, which returns an instant. // And you can min them, which returns an instant.
let r = store.q_once(r#"[:find (min ?v) . let r = store.q_once(r#"[:find (min ?v) .
:where [_ _ ?v] [(instant ?v)]]"#, :where [_ _ ?v] [(type ?v :db.type/instant)]]"#,
None) None)
.into_scalar_result() .into_scalar_result()
.expect("results") .expect("results")
@ -644,7 +644,7 @@ fn test_aggregates_type_handling() {
assert_eq!(Binding::Scalar(TypedValue::Instant(earliest)), r); assert_eq!(Binding::Scalar(TypedValue::Instant(earliest)), r);
let r = store.q_once(r#"[:find (sum ?v) . let r = store.q_once(r#"[:find (sum ?v) .
:where [_ _ ?v] [(long ?v)]]"#, :where [_ _ ?v] [(type ?v :db.type/long)]]"#,
None) None)
.into_scalar_result() .into_scalar_result()
.expect("results") .expect("results")
@ -655,7 +655,7 @@ fn test_aggregates_type_handling() {
assert_eq!(Binding::Scalar(TypedValue::Long(total)), r); assert_eq!(Binding::Scalar(TypedValue::Long(total)), r);
let r = store.q_once(r#"[:find (avg ?v) . let r = store.q_once(r#"[:find (avg ?v) .
:where [_ _ ?v] [(double ?v)]]"#, :where [_ _ ?v] [(type ?v :db.type/double)]]"#,
None) None)
.into_scalar_result() .into_scalar_result()
.expect("results") .expect("results")
@ -710,19 +710,8 @@ fn test_type_reqs() {
} }
}; };
let type_names = &[ for value_type in ValueType::all_enums().iter() {
"boolean", let q = format!("[:find [?v ...] :in ?e :where [?e _ ?v] [(type ?v {})]]", value_type.into_keyword());
"long",
"double",
"string",
"keyword",
"uuid",
"instant",
"ref",
];
for name in type_names {
let q = format!("[:find [?v ...] :in ?e :where [?e _ ?v] [({} ?v)]]", name);
let results = conn.q_once(&mut c, &q, QueryInputs::with_value_sequence(vec![ let results = conn.q_once(&mut c, &q, QueryInputs::with_value_sequence(vec![
(Variable::from_valid_name("?e"), TypedValue::Ref(entid)), (Variable::from_valid_name("?e"), TypedValue::Ref(entid)),
])) ]))
@ -746,7 +735,7 @@ fn test_type_reqs() {
let longs_query = r#"[:find [?v ...] let longs_query = r#"[:find [?v ...]
:order (asc ?v) :order (asc ?v)
:in ?e :in ?e
:where [?e _ ?v] [(long ?v)]]"#; :where [?e _ ?v] [(type ?v :db.type/long)]]"#;
let res = conn.q_once(&mut c, longs_query, QueryInputs::with_value_sequence(vec![ let res = conn.q_once(&mut c, longs_query, QueryInputs::with_value_sequence(vec![
(Variable::from_valid_name("?e"), TypedValue::Ref(entid)), (Variable::from_valid_name("?e"), TypedValue::Ref(entid)),