Implement parsing of query predicates. (#380) r=nalexander

This commit is contained in:
Richard Newman 2017-03-16 14:44:56 +00:00
parent 1c4e30a906
commit d83c8620cd
4 changed files with 184 additions and 56 deletions

View file

@ -41,16 +41,20 @@ use std::collections::BTreeMap;
use self::mentat_query::{ use self::mentat_query::{
FindQuery, FindQuery,
FnArg,
FromValue, FromValue,
Predicate,
PredicateFn,
SrcVar, SrcVar,
Variable, Variable,
}; };
use self::mentat_parser_utils::ValueParseError; use self::mentat_parser_utils::ValueParseError;
use super::parse::{ use super::parse::{
Result,
ErrorKind, ErrorKind,
QueryParseResult, QueryParseResult,
Result,
clause_seq_to_patterns, clause_seq_to_patterns,
}; };
@ -106,8 +110,8 @@ fn parse_find_parts(find: &[edn::Value],
find_spec: spec, find_spec: spec,
default_source: source, default_source: source,
with: with_vars, with: with_vars,
in_vars: vec!(), // TODO in_vars: vec![], // TODO
in_sources: vec!(), // TODO in_sources: vec![], // TODO
where_clauses: where_clauses, where_clauses: where_clauses,
} }
}) })
@ -230,6 +234,26 @@ mod test_parse {
value: PatternValuePlace::Variable(Variable(PlainSymbol::new("?y"))), value: PatternValuePlace::Variable(Variable(PlainSymbol::new("?y"))),
tx: PatternNonValuePlace::Placeholder, 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)],
}),
]);
} }
} }

View file

@ -13,7 +13,7 @@ extern crate edn;
extern crate mentat_parser_utils; extern crate mentat_parser_utils;
extern crate mentat_query; 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::combine::combinator::{choice, try};
use self::mentat_parser_utils::{ use self::mentat_parser_utils::{
@ -25,10 +25,13 @@ use self::mentat_query::{
Element, Element,
FindQuery, FindQuery,
FindSpec, FindSpec,
FnArg,
FromValue, FromValue,
Pattern, Pattern,
PatternNonValuePlace, PatternNonValuePlace,
PatternValuePlace, PatternValuePlace,
Predicate,
PredicateFn,
SrcVar, SrcVar,
Variable, Variable,
WhereClause, WhereClause,
@ -93,34 +96,75 @@ impl<I> Query<I>
def_value_satisfy_parser_fn!(Query, variable, Variable, Variable::from_value); 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, 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<I>(::std::marker::PhantomData<fn(I) -> I>); pub struct Where<I>(::std::marker::PhantomData<fn(I) -> I>);
def_value_satisfy_parser_fn!(Where, pattern_value_place, PatternValuePlace, PatternValuePlace::from_value); def_value_satisfy_parser_fn!(Where,
def_value_satisfy_parser_fn!(Where, pattern_non_value_place, PatternNonValuePlace, PatternNonValuePlace::from_value); 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<Vec<edn::Value>> {
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<WhereClause, _> = 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| { satisfy_map(|x: edn::Value| {
if let edn::Value::Vector(y) = x { if let edn::Value::Vector(y) = x {
// While *technically* Datomic allows you to have a query like: // While *technically* Datomic allows you to have a query like:
// [:find … :where [[?x]]] // [:find … :where [[?x]]]
// We don't -- we require at list e, a. // We don't -- we require at least e, a.
let mut p = let mut p = (optional(Query::source_var()), // src
(optional(Query::source_var()), // src Where::pattern_non_value_place(), // e
Where::pattern_non_value_place(), // e Where::pattern_non_value_place(), // a
Where::pattern_non_value_place(), // a optional(Where::pattern_value_place()), // v
optional(Where::pattern_value_place()), // v optional(Where::pattern_non_value_place()), // tx
optional(Where::pattern_non_value_place()), // tx eof())
eof()) .map(|(src, e, a, v, tx, _)| {
.map(|(src, e, a, v, tx, _)| { let v = v.unwrap_or(PatternValuePlace::Placeholder);
let v = v.unwrap_or(PatternValuePlace::Placeholder); let tx = tx.unwrap_or(PatternNonValuePlace::Placeholder);
let tx = tx.unwrap_or(PatternNonValuePlace::Placeholder);
// Pattern::new takes care of reversal of reversed // Pattern::new takes care of reversal of reversed
// attributes: [?x :foo/_bar ?y] turns into // attributes: [?x :foo/_bar ?y] turns into
// [?y :foo/bar ?x]. // [?y :foo/bar ?x].
Pattern::new(src, e, a, v, tx) Pattern::new(src, e, a, v, tx).map(WhereClause::Pattern)
}); });
// This is a bit messy: the inner conversion to a Pattern can // This is a bit messy: the inner conversion to a Pattern can
// fail if the input is something like // 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. // is nonsense. That leaves us with nested optionals; we unwrap them here.
let r: ParseResult<Option<Pattern>, _> = p.parse_lazy(&y[..]).into(); let r: ParseResult<Option<WhereClause>, _> = p.parse_lazy(&y[..]).into();
let v: Option<Option<Pattern>> = r.ok().map(|x| x.0); let v: Option<Option<WhereClause>> = Query::to_parsed_value(r);
v.unwrap_or(None) v.unwrap_or(None)
} else { } else {
None None
@ -145,12 +189,16 @@ def_value_parser_fn!(Where, pattern, Pattern, input, {
}).parse_stream(input) }).parse_stream(input)
}); });
def_value_parser_fn!(Query, arguments, Vec<FnArg>, input, {
(many::<Vec<FnArg>, _>(Query::fn_arg()), eof())
.map(|(args, _)| { args })
.parse_stream(input)
});
def_value_parser_fn!(Where, clauses, Vec<WhereClause>, input, { def_value_parser_fn!(Where, clauses, Vec<WhereClause>, input, {
// Right now we only support patterns. See #239 for more. // Right now we only support patterns and predicates. See #239 for more.
(many1::<Vec<Pattern>, _>(Where::pattern()), eof()) (many1::<Vec<WhereClause>, _>(choice([Where::pattern(), Where::pred()])), eof())
.map(|(patterns, _)| { .map(|(patterns, _)| { patterns })
patterns.into_iter().map(WhereClause::Pattern).collect()
})
.parse_stream(input) .parse_stream(input)
}); });
@ -272,18 +320,17 @@ mod test {
let a = edn::NamespacedKeyword::new("foo", "bar"); let a = edn::NamespacedKeyword::new("foo", "bar");
let v = OrderedFloat(99.9); let v = OrderedFloat(99.9);
let tx = edn::PlainSymbol::new("?tx"); let tx = edn::PlainSymbol::new("?tx");
let input = [edn::Value::Vector( let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()),
vec!(edn::Value::PlainSymbol(e.clone()),
edn::Value::NamespacedKeyword(a.clone()), edn::Value::NamespacedKeyword(a.clone()),
edn::Value::Float(v.clone()), edn::Value::Float(v.clone()),
edn::Value::PlainSymbol(tx.clone())))]; edn::Value::PlainSymbol(tx.clone())))];
assert_parses_to!(Where::pattern, input, Pattern { assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Placeholder, entity: PatternNonValuePlace::Placeholder,
attribute: PatternNonValuePlace::Ident(a), attribute: PatternNonValuePlace::Ident(a),
value: PatternValuePlace::Constant(NonIntegerConstant::Float(v)), value: PatternValuePlace::Constant(NonIntegerConstant::Float(v)),
tx: PatternNonValuePlace::Variable(Variable(tx)), tx: PatternNonValuePlace::Variable(Variable(tx)),
}); }));
} }
#[test] #[test]
@ -293,19 +340,18 @@ mod test {
let a = edn::PlainSymbol::new("?a"); let a = edn::PlainSymbol::new("?a");
let v = edn::PlainSymbol::new("?v"); let v = edn::PlainSymbol::new("?v");
let tx = edn::PlainSymbol::new("?tx"); let tx = edn::PlainSymbol::new("?tx");
let input = [edn::Value::Vector( let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(s.clone()),
vec!(edn::Value::PlainSymbol(s.clone()),
edn::Value::PlainSymbol(e.clone()), edn::Value::PlainSymbol(e.clone()),
edn::Value::PlainSymbol(a.clone()), edn::Value::PlainSymbol(a.clone()),
edn::Value::PlainSymbol(v.clone()), edn::Value::PlainSymbol(v.clone()),
edn::Value::PlainSymbol(tx.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())), source: Some(SrcVar::NamedSrc("x".to_string())),
entity: PatternNonValuePlace::Variable(Variable(e)), entity: PatternNonValuePlace::Variable(Variable(e)),
attribute: PatternNonValuePlace::Variable(Variable(a)), attribute: PatternNonValuePlace::Variable(Variable(a)),
value: PatternValuePlace::Variable(Variable(v)), value: PatternValuePlace::Variable(Variable(v)),
tx: PatternNonValuePlace::Variable(Variable(tx)), tx: PatternNonValuePlace::Variable(Variable(tx)),
}); }));
} }
#[test] #[test]
@ -314,8 +360,7 @@ mod test {
let a = edn::NamespacedKeyword::new("foo", "_bar"); let a = edn::NamespacedKeyword::new("foo", "_bar");
let v = OrderedFloat(99.9); let v = OrderedFloat(99.9);
let tx = edn::PlainSymbol::new("?tx"); let tx = edn::PlainSymbol::new("?tx");
let input = [edn::Value::Vector( let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()),
vec!(edn::Value::PlainSymbol(e.clone()),
edn::Value::NamespacedKeyword(a.clone()), edn::Value::NamespacedKeyword(a.clone()),
edn::Value::Float(v.clone()), edn::Value::Float(v.clone()),
edn::Value::PlainSymbol(tx.clone())))]; edn::Value::PlainSymbol(tx.clone())))];
@ -331,21 +376,20 @@ mod test {
let a = edn::NamespacedKeyword::new("foo", "_bar"); let a = edn::NamespacedKeyword::new("foo", "_bar");
let v = edn::PlainSymbol::new("?v"); let v = edn::PlainSymbol::new("?v");
let tx = edn::PlainSymbol::new("?tx"); let tx = edn::PlainSymbol::new("?tx");
let input = [edn::Value::Vector( let input = [edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()),
vec!(edn::Value::PlainSymbol(e.clone()),
edn::Value::NamespacedKeyword(a.clone()), edn::Value::NamespacedKeyword(a.clone()),
edn::Value::PlainSymbol(v.clone()), edn::Value::PlainSymbol(v.clone()),
edn::Value::PlainSymbol(tx.clone())))]; edn::Value::PlainSymbol(tx.clone())))];
// Note that the attribute is no longer reversed, and the entity and value have // Note that the attribute is no longer reversed, and the entity and value have
// switched places. // switched places.
assert_parses_to!(Where::pattern, input, Pattern { assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern {
source: None, source: None,
entity: PatternNonValuePlace::Variable(Variable(v)), entity: PatternNonValuePlace::Variable(Variable(v)),
attribute: PatternNonValuePlace::Ident(edn::NamespacedKeyword::new("foo", "bar")), attribute: PatternNonValuePlace::Ident(edn::NamespacedKeyword::new("foo", "bar")),
value: PatternValuePlace::Placeholder, value: PatternValuePlace::Placeholder,
tx: PatternNonValuePlace::Variable(Variable(tx)), tx: PatternNonValuePlace::Variable(Variable(tx)),
}); }));
} }
#[test] #[test]

View file

@ -12,24 +12,45 @@ extern crate mentat_query_parser;
extern crate mentat_query; extern crate mentat_query;
extern crate edn; extern crate edn;
use mentat_query::FindSpec::FindScalar;
use mentat_query::Element;
use mentat_query::Variable;
use edn::PlainSymbol; 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. ///! N.B., parsing a query can be done without reference to a DB.
///! Processing the parsed query into something we can work with ///! Processing the parsed query into something we can work with
///! for planning involves interrogating the schema and idents in ///! for planning involves interrogating the schema and idents in
///! the store. ///! the store.
///! See <https://github.com/mozilla/mentat/wiki/Querying> for more. ///! See <https://github.com/mozilla/mentat/wiki/Querying> for more.
#[test] #[test]
fn can_parse_trivial_find() { fn can_parse_predicates() {
let find = FindScalar(Element::Variable(Variable(PlainSymbol("?foo".to_string())))); 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!(p.find_spec,
assert_eq!("?foo", name); FindSpec::FindColl(Element::Variable(Variable(PlainSymbol::new("?x")))));
} else { assert_eq!(p.where_clauses,
panic!() 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),
]}),
]);
} }

View file

@ -75,6 +75,26 @@ impl fmt::Debug for Variable {
} }
} }
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PredicateFn(pub PlainSymbol);
impl FromValue<PredicateFn> for PredicateFn {
fn from_value(v: &edn::Value) -> Option<PredicateFn> {
if let edn::Value::PlainSymbol(ref s) = *v {
PredicateFn::from_symbol(s)
} else {
None
}
}
}
impl PredicateFn {
pub fn from_symbol(sym: &PlainSymbol) -> Option<PredicateFn> {
// TODO: validate the acceptable set of function names.
Some(PredicateFn(sym.clone()))
}
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum SrcVar { pub enum SrcVar {
DefaultSrc, DefaultSrc,
@ -121,6 +141,7 @@ impl NonIntegerConstant {
} }
} }
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FnArg { pub enum FnArg {
Variable(Variable), Variable(Variable),
SrcVar(SrcVar), SrcVar(SrcVar),
@ -129,6 +150,19 @@ pub enum FnArg {
Constant(NonIntegerConstant), Constant(NonIntegerConstant),
} }
impl FromValue<FnArg> for FnArg {
fn from_value(v: &edn::Value) -> Option<FnArg> {
// 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 /// e, a, tx can't be values -- no strings, no floats -- and so
/// they can only be variables, entity IDs, ident keywords, or /// they can only be variables, entity IDs, ident keywords, or
/// placeholders. /// placeholders.
@ -433,6 +467,11 @@ impl Pattern {
} }
} }
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Predicate {
pub operator: PlainSymbol,
pub args: Vec<FnArg>,
}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -441,7 +480,7 @@ pub enum WhereClause {
NotJoin, NotJoin,
Or, Or,
OrJoin, OrJoin,
Pred, Pred(Predicate),
WhereFn, WhereFn,
RuleExpr, RuleExpr,
Pattern(Pattern), Pattern(Pattern),