Implement parsing of query predicates. (#380) r=nalexander
This commit is contained in:
parent
1c4e30a906
commit
d83c8620cd
4 changed files with 184 additions and 56 deletions
|
@ -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)],
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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),
|
||||
]}),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue