diff --git a/core/src/types.rs b/core/src/types.rs index 1ead80b3..716f943f 100644 --- a/core/src/types.rs +++ b/core/src/types.rs @@ -131,6 +131,24 @@ impl ValueType { }) } + pub fn from_keyword(keyword: &Keyword) -> Option { + 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 { TypedValue::typed_ns_keyword("db.type", match self { ValueType::Ref => "ref", diff --git a/query-algebrizer/src/clauses/predicate.rs b/query-algebrizer/src/clauses/predicate.rs index f099b7b8..add9eb8c 100644 --- a/query-algebrizer/src/clauses/predicate.rs +++ b/query-algebrizer/src/clauses/predicate.rs @@ -16,6 +16,7 @@ use mentat_core::{ use mentat_query::{ FnArg, + PlainSymbol, Predicate, TypeAnnotation, }; @@ -66,7 +67,10 @@ impl ConjoiningClauses { /// Apply a type annotation, which is a construct like a predicate that constrains the argument /// to be a specific ValueType. 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(()) } diff --git a/query-algebrizer/tests/type_reqs.rs b/query-algebrizer/tests/type_reqs.rs index 6cec8e27..a8f5179b 100644 --- a/query-algebrizer/tests/type_reqs.rs +++ b/query-algebrizer/tests/type_reqs.rs @@ -43,22 +43,12 @@ fn prepopulated_schema() -> Schema { #[test] fn test_empty_known() { - let type_names = [ - "boolean", - "long", - "double", - "string", - "keyword", - "uuid", - "instant", - "ref", - ]; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - for known_type in type_names.iter() { - for required in type_names.iter() { - let q = format!("[:find ?e :where [?e :test/{} ?v] [({} ?v)]]", - known_type, required); + for known_type in ValueType::all_enums().iter() { + for required in ValueType::all_enums().iter() { + let q = format!("[:find ?e :where [?e :test/{} ?v] [(type ?v {})]]", + known_type.into_keyword().name(), required); println!("Query: {}", q); let cc = alg(known, &q); // It should only be empty if the known type and our requirement differ. @@ -72,7 +62,7 @@ fn test_empty_known() { fn test_multiple() { let schema = prepopulated_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); assert!(cc.empty_because.is_some()); } @@ -81,5 +71,5 @@ fn test_multiple() { fn test_unbound() { let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - bails(known, "[:find ?e :where [(string ?e)]]"); + bails(known, "[:find ?e :where [(type ?e :db.type/string)]]"); } diff --git a/query-parser/Cargo.toml b/query-parser/Cargo.toml index b6670281..2a91dbc4 100644 --- a/query-parser/Cargo.toml +++ b/query-parser/Cargo.toml @@ -7,14 +7,10 @@ workspace = ".." combine = "2.3.2" error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" } maplit = "0.1" -matches = "0.1" [dependencies.edn] path = "../edn" -[dependencies.mentat_core] - path = "../core" - [dependencies.mentat_parser_utils] path = "../parser-utils" diff --git a/query-parser/src/lib.rs b/query-parser/src/lib.rs index c6939efc..e5deff3a 100644 --- a/query-parser/src/lib.rs +++ b/query-parser/src/lib.rs @@ -16,9 +16,6 @@ extern crate maplit; #[macro_use] extern crate error_chain; -#[macro_use] -extern crate matches; - extern crate edn; #[macro_use] diff --git a/query-parser/src/parse.rs b/query-parser/src/parse.rs index 545ed38b..0b11d3bf 100644 --- a/query-parser/src/parse.rs +++ b/query-parser/src/parse.rs @@ -12,7 +12,6 @@ extern crate combine; extern crate edn; extern crate mentat_parser_utils; extern crate mentat_query; -extern crate mentat_core; 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::mentat_core::ValueType; - use errors::{ Error, ErrorKind, @@ -100,6 +97,10 @@ def_parser!(Query, variable, Variable, { 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, { 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, type_symbol, "type"); + def_parser!(Where, rule_vars, BTreeSet, { seq() .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. def_parser!(Where, type_annotation, WhereClause, { // 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() .of_exactly(seq() - .of_exactly((Query::type_anno_type(), Query::variable()) - .map(|(ty, var)| { + .of_exactly((Where::type_symbol(), Query::variable(), Query::keyword()) + .map(|(_, var, ty)| { WhereClause::TypeAnnotation( TypeAnnotation { value_type: ty, @@ -759,7 +740,10 @@ mod test { let input = input.with_spans(); let mut par = Where::pattern(); 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] @@ -1137,15 +1121,16 @@ mod test { #[test] fn test_type_anno() { assert_edn_parses_to!(Where::type_annotation, - "[(string ?x)]", + "[(type ?x :db.type/string)]", WhereClause::TypeAnnotation(TypeAnnotation { - value_type: ValueType::String, + value_type: edn::Keyword::namespaced("db.type", "string"), 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, - "[[long ?foo]]", + "[[type ?foo :db_type_long]]", WhereClause::TypeAnnotation(TypeAnnotation { - value_type: ValueType::Long, + value_type: edn::Keyword::plain("db_type_long"), variable: Variable::from_valid_name("?foo"), })); diff --git a/query-parser/tests/find_tests.rs b/query-parser/tests/find_tests.rs index e274e6ff..95e5bc66 100644 --- a/query-parser/tests/find_tests.rs +++ b/query-parser/tests/find_tests.rs @@ -12,7 +12,6 @@ extern crate maplit; extern crate edn; -extern crate mentat_core; extern crate mentat_query; extern crate mentat_query_parser; diff --git a/query-translator/tests/translate.rs b/query-translator/tests/translate.rs index 0e17e4a0..3fa6ffe6 100644 --- a/query-translator/tests/translate.rs +++ b/query-translator/tests/translate.rs @@ -348,7 +348,7 @@ fn test_unknown_ident() { fn test_type_required_long() { 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); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ @@ -363,7 +363,7 @@ fn test_type_required_long() { fn test_type_required_double() { 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); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ @@ -378,7 +378,7 @@ fn test_type_required_double() { fn test_type_required_boolean() { 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); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ @@ -392,7 +392,7 @@ fn test_type_required_boolean() { fn test_type_required_string() { 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); // Note: strings should use `all_datoms` and not `datoms`. diff --git a/query/src/lib.rs b/query/src/lib.rs index 7c692949..139b7330 100644 --- a/query/src/lib.rs +++ b/query/src/lib.rs @@ -58,7 +58,6 @@ use mentat_core::{ FromRc, TypedValue, ValueRc, - ValueType, }; pub type SrcVarName = String; // Do not include the required syntactic '$'. @@ -967,7 +966,7 @@ pub struct NotJoin { #[derive(Clone, Debug, Eq, PartialEq)] pub struct TypeAnnotation { - pub value_type: ValueType, + pub value_type: Keyword, pub variable: Variable, } diff --git a/tests/query.rs b/tests/query.rs index edca7983..b0422f23 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -602,7 +602,7 @@ fn test_aggregates_type_handling() { // You can't sum instants. let r = store.q_once(r#"[:find (sum ?v) . - :where [_ _ ?v] [(instant ?v)]]"#, + :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, None); match r { Result::Err( @@ -623,7 +623,7 @@ fn test_aggregates_type_handling() { // But you can count them. let r = store.q_once(r#"[:find (count ?v) . - :where [_ _ ?v] [(instant ?v)]]"#, + :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, None) .into_scalar_result() .expect("results") @@ -634,7 +634,7 @@ fn test_aggregates_type_handling() { // And you can min them, which returns an instant. let r = store.q_once(r#"[:find (min ?v) . - :where [_ _ ?v] [(instant ?v)]]"#, + :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, None) .into_scalar_result() .expect("results") @@ -644,7 +644,7 @@ fn test_aggregates_type_handling() { assert_eq!(Binding::Scalar(TypedValue::Instant(earliest)), r); let r = store.q_once(r#"[:find (sum ?v) . - :where [_ _ ?v] [(long ?v)]]"#, + :where [_ _ ?v] [(type ?v :db.type/long)]]"#, None) .into_scalar_result() .expect("results") @@ -655,7 +655,7 @@ fn test_aggregates_type_handling() { assert_eq!(Binding::Scalar(TypedValue::Long(total)), r); let r = store.q_once(r#"[:find (avg ?v) . - :where [_ _ ?v] [(double ?v)]]"#, + :where [_ _ ?v] [(type ?v :db.type/double)]]"#, None) .into_scalar_result() .expect("results") @@ -710,19 +710,8 @@ fn test_type_reqs() { } }; - let type_names = &[ - "boolean", - "long", - "double", - "string", - "keyword", - "uuid", - "instant", - "ref", - ]; - - for name in type_names { - let q = format!("[:find [?v ...] :in ?e :where [?e _ ?v] [({} ?v)]]", name); + for value_type in ValueType::all_enums().iter() { + let q = format!("[:find [?v ...] :in ?e :where [?e _ ?v] [(type ?v {})]]", value_type.into_keyword()); let results = conn.q_once(&mut c, &q, QueryInputs::with_value_sequence(vec![ (Variable::from_valid_name("?e"), TypedValue::Ref(entid)), ])) @@ -746,7 +735,7 @@ fn test_type_reqs() { let longs_query = r#"[:find [?v ...] :order (asc ?v) :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![ (Variable::from_valid_name("?e"), TypedValue::Ref(entid)),