From d83c8620cd3efae9b527b1dc82a31c3ee68addf0 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Thu, 16 Mar 2017 14:44:56 +0000 Subject: [PATCH] Implement parsing of query predicates. (#380) r=nalexander --- query-parser/src/find.rs | 30 +++++++- query-parser/src/parse.rs | 126 +++++++++++++++++++++---------- query-parser/tests/find_tests.rs | 43 ++++++++--- query/src/lib.rs | 41 +++++++++- 4 files changed, 184 insertions(+), 56 deletions(-) diff --git a/query-parser/src/find.rs b/query-parser/src/find.rs index fdf9a4e6..56523d91 100644 --- a/query-parser/src/find.rs +++ b/query-parser/src/find.rs @@ -41,16 +41,20 @@ use std::collections::BTreeMap; use self::mentat_query::{ FindQuery, + FnArg, FromValue, + Predicate, + PredicateFn, SrcVar, Variable, }; + use self::mentat_parser_utils::ValueParseError; use super::parse::{ - Result, ErrorKind, QueryParseResult, + Result, clause_seq_to_patterns, }; @@ -106,8 +110,8 @@ fn parse_find_parts(find: &[edn::Value], find_spec: spec, default_source: source, with: with_vars, - in_vars: vec!(), // TODO - in_sources: vec!(), // TODO + in_vars: vec![], // TODO + in_sources: vec![], // TODO where_clauses: where_clauses, } }) @@ -230,6 +234,26 @@ mod test_parse { value: PatternValuePlace::Variable(Variable(PlainSymbol::new("?y"))), tx: PatternNonValuePlace::Placeholder, })]); + } + #[test] + fn test_parse_predicate() { + let input = "[:find ?x :where [?x :foo/bar ?y] [[< ?y 10]]]"; + let parsed = parse_find_string(input).unwrap(); + assert_eq!(parsed.where_clauses, + vec![ + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable(PlainSymbol::new("?x"))), + attribute: PatternNonValuePlace::Ident(NamespacedKeyword::new("foo", "bar")), + value: PatternValuePlace::Variable(Variable(PlainSymbol::new("?y"))), + tx: PatternNonValuePlace::Placeholder, + }), + WhereClause::Pred(Predicate { + operator: PlainSymbol::new("<"), + args: vec![FnArg::Variable(Variable(PlainSymbol::new("?y"))), + FnArg::EntidOrInteger(10)], + }), + ]); } } diff --git a/query-parser/src/parse.rs b/query-parser/src/parse.rs index 34d0621d..e7660535 100644 --- a/query-parser/src/parse.rs +++ b/query-parser/src/parse.rs @@ -13,7 +13,7 @@ extern crate edn; extern crate mentat_parser_utils; extern crate mentat_query; -use self::combine::{eof, many1, optional, parser, satisfy_map, Parser, ParseResult, Stream}; +use self::combine::{eof, many, many1, optional, parser, satisfy_map, Parser, ParseResult, Stream}; use self::combine::combinator::{choice, try}; use self::mentat_parser_utils::{ @@ -25,10 +25,13 @@ use self::mentat_query::{ Element, FindQuery, FindSpec, + FnArg, FromValue, Pattern, PatternNonValuePlace, PatternValuePlace, + Predicate, + PredicateFn, SrcVar, Variable, WhereClause, @@ -93,34 +96,75 @@ impl Query def_value_satisfy_parser_fn!(Query, variable, Variable, Variable::from_value); def_value_satisfy_parser_fn!(Query, source_var, SrcVar, SrcVar::from_value); +def_value_satisfy_parser_fn!(Query, predicate_fn, PredicateFn, PredicateFn::from_value); +def_value_satisfy_parser_fn!(Query, fn_arg, FnArg, FnArg::from_value); pub struct Where(::std::marker::PhantomData I>); -def_value_satisfy_parser_fn!(Where, pattern_value_place, PatternValuePlace, PatternValuePlace::from_value); -def_value_satisfy_parser_fn!(Where, pattern_non_value_place, PatternNonValuePlace, PatternNonValuePlace::from_value); +def_value_satisfy_parser_fn!(Where, + pattern_value_place, + PatternValuePlace, + PatternValuePlace::from_value); +def_value_satisfy_parser_fn!(Where, + pattern_non_value_place, + PatternNonValuePlace, + PatternNonValuePlace::from_value); -def_value_parser_fn!(Where, pattern, Pattern, input, { +/// Take a vector Value containing one vector Value, and return the `Vec` inside the inner vector. +/// Also accepts an inner list, returning it as a `Vec`. +fn unwrap_nested(x: edn::Value) -> Option> { + match x { + edn::Value::Vector(mut v) => { + match v.pop() { + Some(edn::Value::List(items)) => Some(items.into_iter().collect()), + Some(edn::Value::Vector(items)) => Some(items), + _ => None, + } + } + _ => None, + } +} + +/// A vector containing just a parenthesized filter expression. +def_value_parser_fn!(Where, pred, WhereClause, input, { + satisfy_map(|x: edn::Value| { + // Accept either a list or a vector here: + // `[(foo ?x ?y)]` or `[[foo ?x ?y]]` + unwrap_nested(x).and_then(|items| { + let mut p = (Query::predicate_fn(), Query::arguments(), eof()).map(|(f, args, _)| { + WhereClause::Pred( + Predicate { + operator: f.0, + args: args, + }) + }); + let r: ParseResult = p.parse_lazy(&items[..]).into(); + Query::to_parsed_value(r) + }) + }).parse_stream(input) +}); + +def_value_parser_fn!(Where, pattern, WhereClause, input, { satisfy_map(|x: edn::Value| { if let edn::Value::Vector(y) = x { // While *technically* Datomic allows you to have a query like: // [:find … :where [[?x]]] - // We don't -- we require at list e, a. - let mut p = - (optional(Query::source_var()), // src - Where::pattern_non_value_place(), // e - Where::pattern_non_value_place(), // a - optional(Where::pattern_value_place()), // v - optional(Where::pattern_non_value_place()), // tx - eof()) - .map(|(src, e, a, v, tx, _)| { - let v = v.unwrap_or(PatternValuePlace::Placeholder); - let tx = tx.unwrap_or(PatternNonValuePlace::Placeholder); + // We don't -- we require at least e, a. + let mut p = (optional(Query::source_var()), // src + Where::pattern_non_value_place(), // e + Where::pattern_non_value_place(), // a + optional(Where::pattern_value_place()), // v + optional(Where::pattern_non_value_place()), // tx + eof()) + .map(|(src, e, a, v, tx, _)| { + let v = v.unwrap_or(PatternValuePlace::Placeholder); + let tx = tx.unwrap_or(PatternNonValuePlace::Placeholder); - // Pattern::new takes care of reversal of reversed - // attributes: [?x :foo/_bar ?y] turns into - // [?y :foo/bar ?x]. - Pattern::new(src, e, a, v, tx) - }); + // Pattern::new takes care of reversal of reversed + // attributes: [?x :foo/_bar ?y] turns into + // [?y :foo/bar ?x]. + Pattern::new(src, e, a, v, tx).map(WhereClause::Pattern) + }); // This is a bit messy: the inner conversion to a Pattern can // fail if the input is something like @@ -136,8 +180,8 @@ def_value_parser_fn!(Where, pattern, Pattern, input, { // ``` // // is nonsense. That leaves us with nested optionals; we unwrap them here. - let r: ParseResult, _> = p.parse_lazy(&y[..]).into(); - let v: Option> = r.ok().map(|x| x.0); + let r: ParseResult, _> = p.parse_lazy(&y[..]).into(); + let v: Option> = Query::to_parsed_value(r); v.unwrap_or(None) } else { None @@ -145,12 +189,16 @@ def_value_parser_fn!(Where, pattern, Pattern, input, { }).parse_stream(input) }); +def_value_parser_fn!(Query, arguments, Vec, input, { + (many::, _>(Query::fn_arg()), eof()) + .map(|(args, _)| { args }) + .parse_stream(input) +}); + def_value_parser_fn!(Where, clauses, Vec, input, { - // Right now we only support patterns. See #239 for more. - (many1::, _>(Where::pattern()), eof()) - .map(|(patterns, _)| { - patterns.into_iter().map(WhereClause::Pattern).collect() - }) + // Right now we only support patterns and predicates. See #239 for more. + (many1::, _>(choice([Where::pattern(), Where::pred()])), eof()) + .map(|(patterns, _)| { patterns }) .parse_stream(input) }); @@ -272,18 +320,17 @@ mod test { let a = edn::NamespacedKeyword::new("foo", "bar"); let v = OrderedFloat(99.9); let tx = edn::PlainSymbol::new("?tx"); - let input = [edn::Value::Vector( - vec!(edn::Value::PlainSymbol(e.clone()), + let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()), edn::Value::NamespacedKeyword(a.clone()), edn::Value::Float(v.clone()), edn::Value::PlainSymbol(tx.clone())))]; - assert_parses_to!(Where::pattern, input, Pattern { + assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Placeholder, attribute: PatternNonValuePlace::Ident(a), value: PatternValuePlace::Constant(NonIntegerConstant::Float(v)), tx: PatternNonValuePlace::Variable(Variable(tx)), - }); + })); } #[test] @@ -293,19 +340,18 @@ mod test { let a = edn::PlainSymbol::new("?a"); let v = edn::PlainSymbol::new("?v"); let tx = edn::PlainSymbol::new("?tx"); - let input = [edn::Value::Vector( - vec!(edn::Value::PlainSymbol(s.clone()), + let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(s.clone()), edn::Value::PlainSymbol(e.clone()), edn::Value::PlainSymbol(a.clone()), edn::Value::PlainSymbol(v.clone()), edn::Value::PlainSymbol(tx.clone())))]; - assert_parses_to!(Where::pattern, input, Pattern { + assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern { source: Some(SrcVar::NamedSrc("x".to_string())), entity: PatternNonValuePlace::Variable(Variable(e)), attribute: PatternNonValuePlace::Variable(Variable(a)), value: PatternValuePlace::Variable(Variable(v)), tx: PatternNonValuePlace::Variable(Variable(tx)), - }); + })); } #[test] @@ -314,8 +360,7 @@ mod test { let a = edn::NamespacedKeyword::new("foo", "_bar"); let v = OrderedFloat(99.9); let tx = edn::PlainSymbol::new("?tx"); - let input = [edn::Value::Vector( - vec!(edn::Value::PlainSymbol(e.clone()), + let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()), edn::Value::NamespacedKeyword(a.clone()), edn::Value::Float(v.clone()), edn::Value::PlainSymbol(tx.clone())))]; @@ -331,21 +376,20 @@ mod test { let a = edn::NamespacedKeyword::new("foo", "_bar"); let v = edn::PlainSymbol::new("?v"); let tx = edn::PlainSymbol::new("?tx"); - let input = [edn::Value::Vector( - vec!(edn::Value::PlainSymbol(e.clone()), + let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()), edn::Value::NamespacedKeyword(a.clone()), edn::Value::PlainSymbol(v.clone()), edn::Value::PlainSymbol(tx.clone())))]; // Note that the attribute is no longer reversed, and the entity and value have // switched places. - assert_parses_to!(Where::pattern, input, Pattern { + assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable(v)), attribute: PatternNonValuePlace::Ident(edn::NamespacedKeyword::new("foo", "bar")), value: PatternValuePlace::Placeholder, tx: PatternNonValuePlace::Variable(Variable(tx)), - }); + })); } #[test] diff --git a/query-parser/tests/find_tests.rs b/query-parser/tests/find_tests.rs index b34c277d..f5ba7763 100644 --- a/query-parser/tests/find_tests.rs +++ b/query-parser/tests/find_tests.rs @@ -12,24 +12,45 @@ extern crate mentat_query_parser; extern crate mentat_query; extern crate edn; -use mentat_query::FindSpec::FindScalar; -use mentat_query::Element; -use mentat_query::Variable; use edn::PlainSymbol; +use mentat_query::{ + Element, + FindSpec, + FnArg, + Pattern, + PatternNonValuePlace, + PatternValuePlace, + Predicate, + Variable, + WhereClause, +}; + +use mentat_query_parser::parse_find_string; + ///! N.B., parsing a query can be done without reference to a DB. ///! Processing the parsed query into something we can work with ///! for planning involves interrogating the schema and idents in ///! the store. ///! See for more. - #[test] -fn can_parse_trivial_find() { - let find = FindScalar(Element::Variable(Variable(PlainSymbol("?foo".to_string())))); +fn can_parse_predicates() { + let s = "[:find [?x ...] :where [?x _ ?y] [(< ?y 10)]]"; + let p = parse_find_string(s).unwrap(); - if let FindScalar(Element::Variable(Variable(PlainSymbol(name)))) = find { - assert_eq!("?foo", name); - } else { - panic!() - } + assert_eq!(p.find_spec, + FindSpec::FindColl(Element::Variable(Variable(PlainSymbol::new("?x"))))); + assert_eq!(p.where_clauses, + vec![ + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable(PlainSymbol::new("?x"))), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::Variable(Variable(PlainSymbol::new("?y"))), + tx: PatternNonValuePlace::Placeholder, + }), + WhereClause::Pred(Predicate { operator: PlainSymbol::new("<"), args: vec![ + FnArg::Variable(Variable(PlainSymbol::new("?y"))), FnArg::EntidOrInteger(10), + ]}), + ]); } diff --git a/query/src/lib.rs b/query/src/lib.rs index ecfa3529..4d1ea999 100644 --- a/query/src/lib.rs +++ b/query/src/lib.rs @@ -75,6 +75,26 @@ impl fmt::Debug for Variable { } } +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PredicateFn(pub PlainSymbol); + +impl FromValue for PredicateFn { + fn from_value(v: &edn::Value) -> Option { + if let edn::Value::PlainSymbol(ref s) = *v { + PredicateFn::from_symbol(s) + } else { + None + } + } +} + +impl PredicateFn { + pub fn from_symbol(sym: &PlainSymbol) -> Option { + // TODO: validate the acceptable set of function names. + Some(PredicateFn(sym.clone())) + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum SrcVar { DefaultSrc, @@ -121,6 +141,7 @@ impl NonIntegerConstant { } } +#[derive(Clone, Debug, Eq, PartialEq)] pub enum FnArg { Variable(Variable), SrcVar(SrcVar), @@ -129,6 +150,19 @@ pub enum FnArg { Constant(NonIntegerConstant), } +impl FromValue for FnArg { + fn from_value(v: &edn::Value) -> Option { + // TODO: support SrcVars. + Variable::from_value(v) + .and_then(|v| Some(FnArg::Variable(v))) + .or_else(|| + match v { + &edn::Value::Integer(i) => Some(FnArg::EntidOrInteger(i)), + _ => unimplemented!(), + }) + } +} + /// e, a, tx can't be values -- no strings, no floats -- and so /// they can only be variables, entity IDs, ident keywords, or /// placeholders. @@ -433,6 +467,11 @@ impl Pattern { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Predicate { + pub operator: PlainSymbol, + pub args: Vec, +} #[allow(dead_code)] #[derive(Clone, Debug, Eq, PartialEq)] @@ -441,7 +480,7 @@ pub enum WhereClause { NotJoin, Or, OrJoin, - Pred, + Pred(Predicate), WhereFn, RuleExpr, Pattern(Pattern),