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::{
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)],
}),
]);
}
}

View file

@ -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<I> Query<I>
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<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, 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<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| {
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<Option<Pattern>, _> = p.parse_lazy(&y[..]).into();
let v: Option<Option<Pattern>> = r.ok().map(|x| x.0);
let r: ParseResult<Option<WhereClause>, _> = p.parse_lazy(&y[..]).into();
let v: Option<Option<WhereClause>> = 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<FnArg>, input, {
(many::<Vec<FnArg>, _>(Query::fn_arg()), eof())
.map(|(args, _)| { args })
.parse_stream(input)
});
def_value_parser_fn!(Where, clauses, Vec<WhereClause>, input, {
// Right now we only support patterns. See #239 for more.
(many1::<Vec<Pattern>, _>(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::<Vec<WhereClause>, _>(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]

View file

@ -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 <https://github.com/mozilla/mentat/wiki/Querying> 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),
]}),
]);
}

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)]
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<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
/// 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<FnArg>,
}
#[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),