From 5b770a54cdd0c9c7234a190347d8b8bd0687cc65 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Thu, 2 Feb 2017 18:32:00 -0800 Subject: [PATCH] Parse basic :find and :where clauses. (#211) r=nalexander * Make Variable::from_symbol public. * Implement basic parsing of queries. * Use pinned dependencies the hard way to fix Travis. * Bump ordered-float dependency to 0.4.0. * Error coercions to use ?, and finishing the find interface. --- db/Cargo.toml | 2 +- edn/Cargo.toml | 2 +- query-parser/Cargo.toml | 2 + query-parser/src/error.rs | 41 --- query-parser/src/find.rs | 107 +++++++- query-parser/src/lib.rs | 6 +- query-parser/src/parse.rs | 435 +++++++++++++++++++++----------- query-parser/src/parser_util.rs | 2 + query-parser/src/util.rs | 24 -- query/Cargo.toml | 4 +- query/src/lib.rs | 187 ++++++++++++-- 11 files changed, 569 insertions(+), 243 deletions(-) delete mode 100644 query-parser/src/error.rs diff --git a/db/Cargo.toml b/db/Cargo.toml index f6d1b0d9..96b977eb 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -7,7 +7,7 @@ error-chain = "0.8.0" lazy_static = "0.2.2" # TODO: don't depend on num and ordered-float; expose helpers in edn abstracting necessary constructors. num = "0.1.35" -ordered-float = "0.3.0" +ordered-float = "0.4.0" [dependencies.rusqlite] version = "0.9.3" diff --git a/edn/Cargo.toml b/edn/Cargo.toml index c5629952..8275aced 100644 --- a/edn/Cargo.toml +++ b/edn/Cargo.toml @@ -11,7 +11,7 @@ readme = "./README.md" [dependencies] num = "0.1.35" -ordered-float = "0.3.0" +ordered-float = "0.4.0" [build-dependencies] peg = "0.4" diff --git a/query-parser/Cargo.toml b/query-parser/Cargo.toml index ecbf1de1..3abf2ce7 100644 --- a/query-parser/Cargo.toml +++ b/query-parser/Cargo.toml @@ -4,6 +4,8 @@ version = "0.0.1" [dependencies] combine = "2.1.1" +matches = "0.1" +ordered-float = "0.4.0" [dependencies.edn] path = "../edn" diff --git a/query-parser/src/error.rs b/query-parser/src/error.rs deleted file mode 100644 index 8e264e96..00000000 --- a/query-parser/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2016 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -extern crate combine; -extern crate edn; -extern crate mentat_query; - -use self::mentat_query::{FindSpec, FindQuery}; - -#[derive(Clone,Debug,Eq,PartialEq)] -pub struct NotAVariableError(pub edn::Value); - -#[derive(Clone,Debug,Eq,PartialEq)] -pub enum FindParseError { - Err, -} - -#[derive(Clone,Debug,Eq,PartialEq)] -pub enum QueryParseError { - InvalidInput(edn::Value), - EdnParseError(edn::parse::ParseError), - MissingField(edn::Keyword), - FindParseError(FindParseError), -} - -impl From for QueryParseError { - fn from(err: edn::parse::ParseError) -> QueryParseError { - QueryParseError::EdnParseError(err) - } -} - -pub type FindParseResult = Result; -pub type QueryParseResult = Result; - diff --git a/query-parser/src/find.rs b/query-parser/src/find.rs index dfaa5474..3f5c6667 100644 --- a/query-parser/src/find.rs +++ b/query-parser/src/find.rs @@ -38,10 +38,36 @@ extern crate mentat_query; use std::collections::BTreeMap; -use self::mentat_query::{FindQuery, SrcVar}; +use self::mentat_query::{ + FindQuery, + FromValue, + SrcVar, + Variable, +}; -use super::error::{QueryParseError, QueryParseResult}; -use super::util::{values_to_variables, vec_to_keyword_map}; +use super::parse::{ + NotAVariableError, + QueryParseError, + QueryParseResult, + clause_seq_to_patterns, +}; + +use super::util::vec_to_keyword_map; + +/// If the provided slice of EDN values are all variables as +/// defined by `value_to_variable`, return a `Vec` of `Variable`s. +/// Otherwise, return the unrecognized Value in a `NotAVariableError`. +fn values_to_variables(vals: &[edn::Value]) -> Result, NotAVariableError> { + let mut out: Vec = Vec::with_capacity(vals.len()); + for v in vals { + if let Some(var) = Variable::from_value(v) { + out.push(var); + continue; + } + return Err(NotAVariableError(v.clone())); + } + return Ok(out); +} #[allow(unused_variables)] fn parse_find_parts(find: &[edn::Value], @@ -63,19 +89,27 @@ fn parse_find_parts(find: &[edn::Value], let source = SrcVar::DefaultSrc; // :with is an array of variables. This is simple, so we don't use a parser. - let with_vars = with.map(values_to_variables); + let with_vars = if let Some(vals) = with { + values_to_variables(vals)? + } else { + vec![] + }; + // :wheres is a whole datastructure. + let where_clauses = clause_seq_to_patterns(wheres)?; super::parse::find_seq_to_find_spec(find) .map(|spec| { FindQuery { find_spec: spec, default_source: source, + with: with_vars, + in_vars: vec!(), // TODO + in_sources: vec!(), // TODO + where_clauses: where_clauses, } }) .map_err(QueryParseError::FindParseError) - - } fn parse_find_map(map: BTreeMap>) -> QueryParseResult { @@ -136,3 +170,64 @@ pub fn parse_find(expr: edn::Value) -> QueryParseResult { } return Err(QueryParseError::InvalidInput(expr)); } + +#[cfg(test)] +mod test_parse { + extern crate edn; + + use self::edn::{NamespacedKeyword, PlainSymbol}; + use self::edn::types::{to_keyword, to_symbol}; + use super::mentat_query::{ + Element, + FindSpec, + Pattern, + PatternNonValuePlace, + PatternValuePlace, + SrcVar, + Variable, + WhereClause, + }; + use super::*; + + // TODO: when #224 lands, fix to_keyword to be variadic. + #[test] + fn test_parse_find() { + let truncated_input = edn::Value::Vector(vec![to_keyword(None, "find")]); + assert!(parse_find(truncated_input).is_err()); + + let input = edn::Value::Vector(vec![ + to_keyword(None, "find"), + to_symbol(None, "?x"), + to_symbol(None, "?y"), + to_keyword(None, "where"), + edn::Value::Vector(vec![ + to_symbol(None, "?x"), + to_keyword("foo", "bar"), + to_symbol(None, "?y"), + ]), + ]); + + let parsed = parse_find(input).unwrap(); + if let FindSpec::FindRel(elems) = parsed.find_spec { + assert_eq!(2, elems.len()); + assert_eq!(vec![ + Element::Variable(Variable(edn::PlainSymbol::new("?x"))), + Element::Variable(Variable(edn::PlainSymbol::new("?y"))), + ], elems); + } else { + panic!("Expected FindRel."); + } + + assert_eq!(SrcVar::DefaultSrc, parsed.default_source); + 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, + })]); + + } +} diff --git a/query-parser/src/lib.rs b/query-parser/src/lib.rs index d50c2a4c..c33ca63f 100644 --- a/query-parser/src/lib.rs +++ b/query-parser/src/lib.rs @@ -8,10 +8,14 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +#[macro_use] +extern crate matches; + #[macro_use] extern crate mentat_parser_utils; -mod error; +#[macro_use] +mod parser_util; mod util; mod parse; pub mod find; diff --git a/query-parser/src/parse.rs b/query-parser/src/parse.rs index 4c9e50a5..dfe25f59 100644 --- a/query-parser/src/parse.rs +++ b/query-parser/src/parse.rs @@ -10,156 +10,207 @@ extern crate combine; extern crate edn; +extern crate mentat_parser_utils; extern crate mentat_query; -use self::combine::{eof, many1, parser, satisfy_map, Parser, ParseResult, Stream}; -use self::combine::combinator::{Expected, FnParser, choice, try}; -use self::edn::Value::PlainSymbol; -use self::mentat_query::{Element, FromValue, FindSpec, Variable}; +use self::mentat_parser_utils::ResultParser; +use self::combine::{eof, many1, optional, parser, satisfy_map, Parser, ParseResult, Stream}; +use self::combine::combinator::{choice, try}; +use self::mentat_query::{ + Element, + FindQuery, + FindSpec, + FromValue, + Pattern, + PatternNonValuePlace, + PatternValuePlace, + SrcVar, + Variable, + WhereClause, +}; -use super::error::{FindParseError, FindParseResult}; +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct NotAVariableError(pub edn::Value); -pub struct FindSp(::std::marker::PhantomData I>); - -type FindSpParser = Expected ParseResult>>; - -fn fn_parser(f: fn(I) -> ParseResult, err: &'static str) -> FindSpParser - where I: Stream -{ - parser(f).expected(err) +#[allow(dead_code)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FindParseError { + Err, } -/// `satisfy_unwrap!` makes it a little easier to implement a `satisfy_map` -/// body that matches a particular `Value` enum case, otherwise returning `None`. -macro_rules! satisfy_unwrap { - ( $cas: path, $var: ident, $body: block ) => { - satisfy_map(|x: edn::Value| if let $cas($var) = x $body else { None }) +#[allow(dead_code)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum WhereParseError { + Err, +} + +#[derive(Clone,Debug,Eq,PartialEq)] +pub enum QueryParseError { + InvalidInput(edn::Value), + EdnParseError(edn::parse::ParseError), + MissingField(edn::Keyword), + FindParseError(FindParseError), + WhereParseError(WhereParseError), + WithParseError(NotAVariableError), +} + +impl From for QueryParseError { + fn from(err: edn::parse::ParseError) -> QueryParseError { + QueryParseError::EdnParseError(err) } } -impl FindSp +impl From for QueryParseError { + fn from(err: WhereParseError) -> QueryParseError { + QueryParseError::WhereParseError(err) + } +} + +impl From for QueryParseError { + fn from(err: NotAVariableError) -> QueryParseError { + QueryParseError::WithParseError(err) + } +} + +pub type WhereParseResult = Result, WhereParseError>; +pub type FindParseResult = Result; +pub type QueryParseResult = Result; + + +pub struct Query(::std::marker::PhantomData I>); + +impl Query where I: Stream { - fn variable() -> FindSpParser { - fn_parser(FindSp::::variable_, "variable") - } - - fn variable_(input: I) -> ParseResult { - satisfy_map(|x: edn::Value| Variable::from_value(&x)).parse_stream(input) - } - - fn period() -> FindSpParser<(), I> { - fn_parser(FindSp::::period_, "period") - } - - fn period_(input: I) -> ParseResult<(), I> { - satisfy_map(|x: edn::Value| { - if let PlainSymbol(ref s) = x { - if s.0.as_str() == "." { - return Some(()); - } - } - return None; - }) - .parse_stream(input) - } - - fn ellipsis() -> FindSpParser<(), I> { - fn_parser(FindSp::::ellipsis_, "ellipsis") - } - - fn ellipsis_(input: I) -> ParseResult<(), I> { - satisfy_map(|x: edn::Value| { - if let PlainSymbol(ref s) = x { - if s.0.as_str() == "..." { - return Some(()); - } - } - return None; - }) - .parse_stream(input) - } - - fn find_scalar() -> FindSpParser { - fn_parser(FindSp::::find_scalar_, "find_scalar") - } - - fn find_scalar_(input: I) -> ParseResult { - (FindSp::variable(), FindSp::period(), eof()) - .map(|(var, _, _)| FindSpec::FindScalar(Element::Variable(var))) - .parse_stream(input) - } - - fn find_coll() -> FindSpParser { - fn_parser(FindSp::::find_coll_, "find_coll") - } - - fn find_coll_(input: I) -> ParseResult { - satisfy_unwrap!(edn::Value::Vector, y, { - let mut p = (FindSp::variable(), FindSp::ellipsis(), eof()) - .map(|(var, _, _)| FindSpec::FindColl(Element::Variable(var))); - let r: ParseResult = p.parse_lazy(&y[..]).into(); - FindSp::to_parsed_value(r) - }) - .parse_stream(input) - } - - fn elements() -> FindSpParser, I> { - fn_parser(FindSp::::elements_, "elements") - } - - fn elements_(input: I) -> ParseResult, I> { - (many1::, _>(FindSp::variable()), eof()) - .map(|(vars, _)| { - vars.into_iter() - .map(Element::Variable) - .collect() - }) - .parse_stream(input) - } - - fn find_rel() -> FindSpParser { - fn_parser(FindSp::::find_rel_, "find_rel") - } - - fn find_rel_(input: I) -> ParseResult { - FindSp::elements().map(FindSpec::FindRel).parse_stream(input) - } - - fn find_tuple() -> FindSpParser { - fn_parser(FindSp::::find_tuple_, "find_tuple") - } - - fn find_tuple_(input: I) -> ParseResult { - satisfy_unwrap!(edn::Value::Vector, y, { - let r: ParseResult = - FindSp::elements().map(FindSpec::FindTuple).parse_lazy(&y[..]).into(); - FindSp::to_parsed_value(r) - }) - .parse_stream(input) - } - - fn find() -> FindSpParser { - fn_parser(FindSp::::find_, "find") - } - - fn find_(input: I) -> ParseResult { - // Any one of the four specs might apply, so we combine them with `choice`. - // Our parsers consume input, so we need to wrap them in `try` so that they - // operate independently. - choice::<[&mut Parser; 4], - _>([&mut try(FindSp::find_scalar()), - &mut try(FindSp::find_coll()), - &mut try(FindSp::find_tuple()), - &mut try(FindSp::find_rel())]) - .parse_stream(input) - } - fn to_parsed_value(r: ParseResult) -> Option { r.ok().map(|x| x.0) } } +def_value_satisfy_parser_fn!(Query, variable, Variable, Variable::from_value); +def_value_satisfy_parser_fn!(Query, source_var, SrcVar, SrcVar::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_parser_fn!(Where, pattern, Pattern, 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); + + // 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) + }); + + // This is a bit messy: the inner conversion to a Pattern can + // fail if the input is something like + // + // ```edn + // [?x :foo/_reversed 23.4] + // ``` + // + // because + // + // ```edn + // [23.4 :foo/reversed ?x] + // ``` + // + // 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); + v.unwrap_or(None) + } else { + None + } + }).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() + }) + .parse_stream(input) +}); + +pub struct Find(::std::marker::PhantomData I>); + +def_value_parser_fn!(Find, period, (), input, { + matches_plain_symbol!(".", input) +}); + +def_value_parser_fn!(Find, ellipsis, (), input, { + matches_plain_symbol!("...", input) +}); + +def_value_parser_fn!(Find, find_scalar, FindSpec, input, { + (Query::variable(), Find::period(), eof()) + .map(|(var, _, _)| FindSpec::FindScalar(Element::Variable(var))) + .parse_stream(input) +}); + +def_value_parser_fn!(Find, find_coll, FindSpec, input, { + satisfy_unwrap!(edn::Value::Vector, y, { + let mut p = (Query::variable(), Find::ellipsis(), eof()) + .map(|(var, _, _)| FindSpec::FindColl(Element::Variable(var))); + let r: ParseResult = p.parse_lazy(&y[..]).into(); + Query::to_parsed_value(r) + }) + .parse_stream(input) +}); + +def_value_parser_fn!(Find, elements, Vec, input, { + (many1::, _>(Query::variable()), eof()) + .map(|(vars, _)| { + vars.into_iter() + .map(Element::Variable) + .collect() + }) + .parse_stream(input) +}); + +def_value_parser_fn!(Find, find_rel, FindSpec, input, { + Find::elements().map(FindSpec::FindRel).parse_stream(input) +}); + +def_value_parser_fn!(Find, find_tuple, FindSpec, input, { + satisfy_unwrap!(edn::Value::Vector, y, { + let r: ParseResult = + Find::elements().map(FindSpec::FindTuple).parse_lazy(&y[..]).into(); + Query::to_parsed_value(r) + }) + .parse_stream(input) +}); + +def_value_parser_fn!(Find, find, FindSpec, input, { + // Any one of the four specs might apply, so we combine them with `choice`. + // Our parsers consume input, so we need to wrap them in `try` so that they + // operate independently. + choice::<[&mut Parser; 4], _> + ([&mut try(Find::find_scalar()), + &mut try(Find::find_coll()), + &mut try(Find::find_tuple()), + &mut try(Find::find_rel())]) + .parse_stream(input) +}); + // Parse a sequence of values into one of four find specs. // // `:find` must be an array of plain var symbols (?foo), pull expressions, and aggregates. @@ -172,29 +223,131 @@ impl FindSp // `?x . ` = FindScalar // `[?x ?y ?z]` = FindTuple // +#[allow(dead_code)] pub fn find_seq_to_find_spec(find: &[edn::Value]) -> FindParseResult { - FindSp::find() + Find::find() .parse(find) .map(|x| x.0) .map_err(|_| FindParseError::Err) } +#[allow(dead_code)] +pub fn clause_seq_to_patterns(clauses: &[edn::Value]) -> WhereParseResult { + Where::clauses() + .parse(clauses) + .map(|x| x.0) + .map_err(|_| WhereParseError::Err) +} + #[cfg(test)] mod test { extern crate combine; extern crate edn; extern crate mentat_query; + extern crate ordered_float; use self::combine::Parser; - use self::mentat_query::{Element, FindSpec, Variable}; + use self::ordered_float::OrderedFloat; + use self::mentat_query::{ + Element, + FindSpec, + NonIntegerConstant, + Pattern, + PatternNonValuePlace, + PatternValuePlace, + SrcVar, + Variable, + }; use super::*; + #[test] + fn test_pattern_mixed() { + let e = edn::PlainSymbol::new("_"); + 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()), + edn::Value::NamespacedKeyword(a.clone()), + edn::Value::Float(v.clone()), + edn::Value::PlainSymbol(tx.clone())))]; + assert_parses_to!(Where::pattern, input, Pattern { + source: None, + entity: PatternNonValuePlace::Placeholder, + attribute: PatternNonValuePlace::Ident(a), + value: PatternValuePlace::Constant(NonIntegerConstant::Float(v)), + tx: PatternNonValuePlace::Variable(Variable(tx)), + }); + } + + #[test] + fn test_pattern_vars() { + let s = edn::PlainSymbol::new("$x"); + let e = edn::PlainSymbol::new("?e"); + 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()), + 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 { + 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] + fn test_pattern_reversed_invalid() { + let e = edn::PlainSymbol::new("_"); + 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()), + edn::Value::NamespacedKeyword(a.clone()), + edn::Value::Float(v.clone()), + edn::Value::PlainSymbol(tx.clone())))]; + + let mut par = Where::pattern(); + let result = par.parse(&input[..]); + assert!(matches!(result, Err(_)), "Expected a parse error."); + } + + #[test] + fn test_pattern_reversed() { + let e = edn::PlainSymbol::new("_"); + 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()), + 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 { + source: None, + entity: PatternNonValuePlace::Variable(Variable(v)), + attribute: PatternNonValuePlace::Ident(edn::NamespacedKeyword::new("foo", "bar")), + value: PatternValuePlace::Placeholder, + tx: PatternNonValuePlace::Variable(Variable(tx)), + }); + } + #[test] fn test_find_sp_variable() { let sym = edn::PlainSymbol::new("?x"); let input = [edn::Value::PlainSymbol(sym.clone())]; - assert_parses_to!(FindSp::variable, input, Variable(sym)); + assert_parses_to!(Query::variable, input, Variable(sym)); } #[test] @@ -202,7 +355,7 @@ mod test { let sym = edn::PlainSymbol::new("?x"); let period = edn::PlainSymbol::new("."); let input = [edn::Value::PlainSymbol(sym.clone()), edn::Value::PlainSymbol(period.clone())]; - assert_parses_to!(FindSp::find_scalar, + assert_parses_to!(Find::find_scalar, input, FindSpec::FindScalar(Element::Variable(Variable(sym)))); } @@ -213,7 +366,7 @@ mod test { let period = edn::PlainSymbol::new("..."); let input = [edn::Value::Vector(vec![edn::Value::PlainSymbol(sym.clone()), edn::Value::PlainSymbol(period.clone())])]; - assert_parses_to!(FindSp::find_coll, + assert_parses_to!(Find::find_coll, input, FindSpec::FindColl(Element::Variable(Variable(sym)))); } @@ -223,7 +376,7 @@ mod test { let vx = edn::PlainSymbol::new("?x"); let vy = edn::PlainSymbol::new("?y"); let input = [edn::Value::PlainSymbol(vx.clone()), edn::Value::PlainSymbol(vy.clone())]; - assert_parses_to!(FindSp::find_rel, + assert_parses_to!(Find::find_rel, input, FindSpec::FindRel(vec![Element::Variable(Variable(vx)), Element::Variable(Variable(vy))])); @@ -235,7 +388,7 @@ mod test { let vy = edn::PlainSymbol::new("?y"); let input = [edn::Value::Vector(vec![edn::Value::PlainSymbol(vx.clone()), edn::Value::PlainSymbol(vy.clone())])]; - assert_parses_to!(FindSp::find_tuple, + assert_parses_to!(Find::find_tuple, input, FindSpec::FindTuple(vec![Element::Variable(Variable(vx)), Element::Variable(Variable(vy))])); diff --git a/query-parser/src/parser_util.rs b/query-parser/src/parser_util.rs index 5cf51958..6175d958 100644 --- a/query-parser/src/parser_util.rs +++ b/query-parser/src/parser_util.rs @@ -10,6 +10,8 @@ extern crate combine; extern crate edn; + +extern crate mentat_parser_utils; extern crate mentat_query; /// Generate a `satisfy_map` expression that matches a `PlainSymbol` diff --git a/query-parser/src/util.rs b/query-parser/src/util.rs index a3226688..e92a3cee 100644 --- a/query-parser/src/util.rs +++ b/query-parser/src/util.rs @@ -9,33 +9,9 @@ // specific language governing permissions and limitations under the License. extern crate edn; -extern crate mentat_query; use std::collections::BTreeMap; -use self::mentat_query::{FromValue, Variable}; -use super::error::NotAVariableError; - -/// If the provided slice of EDN values are all variables as -/// defined by `value_to_variable`, return a Vec of Variables. -/// Otherwise, return the unrecognized Value. -pub fn values_to_variables(vals: &[edn::Value]) -> Result, NotAVariableError> { - let mut out: Vec = Vec::with_capacity(vals.len()); - for v in vals { - if let Some(var) = Variable::from_value(v) { - out.push(var); - continue; - } - return Err(NotAVariableError(v.clone())); - } - return Ok(out); -} - -#[test] -fn test_values_to_variables() { - // TODO -} - /// Take a slice of EDN values, as would be extracted from an /// `edn::Value::Vector`, and turn it into a map. /// diff --git a/query/Cargo.toml b/query/Cargo.toml index b3379943..943e4709 100644 --- a/query/Cargo.toml +++ b/query/Cargo.toml @@ -3,8 +3,8 @@ name = "mentat_query" version = "0.0.1" [dependencies] -[dependencies.num] # For EDN value usage. -[dependencies.ordered-float] +num = "0.1.35" +ordered-float = "0.4.0" [dependencies.edn] path = "../edn" diff --git a/query/src/lib.rs b/query/src/lib.rs index 3ecc8120..5e3bc968 100644 --- a/query/src/lib.rs +++ b/query/src/lib.rs @@ -60,7 +60,7 @@ impl FromValue for Variable { } impl Variable { - fn from_symbol(sym: &PlainSymbol) -> Option { + pub fn from_symbol(sym: &PlainSymbol) -> Option { if sym.is_var_symbol() { Some(Variable(sym.clone())) } else { @@ -96,7 +96,7 @@ impl SrcVar { } /// These are the scalar values representable in EDN. -#[derive(Clone,Debug,Eq,PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum NonIntegerConstant { Boolean(bool), BigInteger(BigInt), @@ -118,24 +118,128 @@ pub enum FnArg { /// This encoding allows us to represent integers that aren't /// entity IDs. That'll get filtered out in the context of the /// database. +#[derive(Clone, Debug, Eq, PartialEq)] pub enum PatternNonValuePlace { Placeholder, Variable(Variable), - Entid(u64), // Note unsigned. See #190. + Entid(i64), // Will always be +ve. See #190. Ident(NamespacedKeyword), } +impl PatternNonValuePlace { + // I think we'll want move variants, so let's leave these here for now. + #[allow(dead_code)] + fn into_pattern_value_place(self) -> PatternValuePlace { + match self { + PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, + PatternNonValuePlace::Variable(x) => PatternValuePlace::Variable(x), + PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), + PatternNonValuePlace::Ident(x) => PatternValuePlace::IdentOrKeyword(x), + } + } + + fn to_pattern_value_place(&self) -> PatternValuePlace { + match *self { + PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, + PatternNonValuePlace::Variable(ref x) => PatternValuePlace::Variable(x.clone()), + PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), + PatternNonValuePlace::Ident(ref x) => PatternValuePlace::IdentOrKeyword(x.clone()), + } + } +} + +impl FromValue for PatternNonValuePlace { + fn from_value(v: &edn::Value) -> Option { + match v { + &edn::Value::Integer(x) => if x >= 0 { + Some(PatternNonValuePlace::Entid(x)) + } else { + None + }, + &edn::Value::PlainSymbol(ref x) => if x.0.as_str() == "_" { + Some(PatternNonValuePlace::Placeholder) + } else { + if let Some(v) = Variable::from_symbol(x) { + Some(PatternNonValuePlace::Variable(v)) + } else { + None + } + }, + &edn::Value::NamespacedKeyword(ref x) => + Some(PatternNonValuePlace::Ident(x.clone())), + _ => None, + } + } +} + /// The `v` part of a pattern can be much broader: it can represent /// integers that aren't entity IDs (particularly negative integers), /// strings, and all the rest. We group those under `Constant`. +#[derive(Clone, Debug, Eq, PartialEq)] pub enum PatternValuePlace { Placeholder, Variable(Variable), EntidOrInteger(i64), - Ident(NamespacedKeyword), + IdentOrKeyword(NamespacedKeyword), Constant(NonIntegerConstant), } +impl FromValue for PatternValuePlace { + fn from_value(v: &edn::Value) -> Option { + match v { + &edn::Value::Integer(x) => + Some(PatternValuePlace::EntidOrInteger(x)), + &edn::Value::PlainSymbol(ref x) if x.0.as_str() == "_" => + Some(PatternValuePlace::Placeholder), + &edn::Value::PlainSymbol(ref x) if x.is_var_symbol() => + Some(PatternValuePlace::Variable(Variable(x.clone()))), + &edn::Value::NamespacedKeyword(ref x) => + Some(PatternValuePlace::IdentOrKeyword(x.clone())), + &edn::Value::Boolean(x) => + Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x))), + &edn::Value::Float(x) => + Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))), + &edn::Value::BigInteger(ref x) => + Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))), + &edn::Value::Text(ref x) => + Some(PatternValuePlace::Constant(NonIntegerConstant::Text(x.clone()))), + _ => None, + } + } +} + +impl PatternValuePlace { + // I think we'll want move variants, so let's leave these here for now. + #[allow(dead_code)] + fn into_pattern_non_value_place(self) -> Option { + match self { + PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), + PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)), + PatternValuePlace::EntidOrInteger(x) => if x >= 0 { + Some(PatternNonValuePlace::Entid(x)) + } else { + None + }, + PatternValuePlace::IdentOrKeyword(x) => Some(PatternNonValuePlace::Ident(x)), + PatternValuePlace::Constant(_) => None, + } + } + + fn to_pattern_non_value_place(&self) -> Option { + match *self { + PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), + PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())), + PatternValuePlace::EntidOrInteger(x) => if x >= 0 { + Some(PatternNonValuePlace::Entid(x)) + } else { + None + }, + PatternValuePlace::IdentOrKeyword(ref x) => Some(PatternNonValuePlace::Ident(x.clone())), + PatternValuePlace::Constant(_) => None, + } + } +} + /* pub enum PullPattern { Constant(Constant), @@ -212,13 +316,6 @@ pub enum FindSpec { FindScalar(Element), } -#[derive(Clone,Debug,Eq,PartialEq)] -#[allow(dead_code)] -pub struct FindQuery { - pub find_spec: FindSpec, - pub default_source: SrcVar, -} - /// Returns true if the provided `FindSpec` returns at most one result. pub fn is_unit_limited(spec: &FindSpec) -> bool { match spec { @@ -255,18 +352,55 @@ pub fn requires_distinct(spec: &FindSpec) -> bool { // A pattern with a reversed attribute — :foo/_bar — is reversed // at the point of parsing. These `Pattern` instances only represent // one direction. -#[allow(dead_code)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Pattern { - source: Option, - entity: PatternNonValuePlace, - attribute: PatternNonValuePlace, - value: PatternValuePlace, - tx: PatternNonValuePlace, + pub source: Option, + pub entity: PatternNonValuePlace, + pub attribute: PatternNonValuePlace, + pub value: PatternValuePlace, + pub tx: PatternNonValuePlace, } +impl Pattern { + pub fn new(src: Option, + e: PatternNonValuePlace, + a: PatternNonValuePlace, + v: PatternValuePlace, + tx: PatternNonValuePlace) -> Option { + let aa = a.clone(); // Too tired of fighting borrow scope for now. + if let PatternNonValuePlace::Ident(ref k) = aa { + if k.is_backward() { + // e and v have different types; we must convert them. + // Not every parseable value is suitable for the entity field! + // As such, this is a failable constructor. + let e_v = e.to_pattern_value_place(); + if let Some(v_e) = v.to_pattern_non_value_place() { + return Some(Pattern { + source: src, + entity: v_e, + attribute: PatternNonValuePlace::Ident(k.to_reversed()), + value: e_v, + tx: tx, + }); + } else { + return None; + } + } + } + Some(Pattern { + source: src, + entity: e, + attribute: a, + value: v, + tx: tx, + }) + } +} + + #[allow(dead_code)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum WhereClause { - /* Not, NotJoin, Or, @@ -274,16 +408,17 @@ pub enum WhereClause { Pred, WhereFn, RuleExpr, - */ - Pattern, + Pattern(Pattern), } #[allow(dead_code)] -pub struct Query { - find: FindSpec, - with: Vec, - in_vars: Vec, - in_sources: Vec, - where_clauses: Vec, +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FindQuery { + pub find_spec: FindSpec, + pub default_source: SrcVar, + pub with: Vec, + pub in_vars: Vec, + pub in_sources: Vec, + pub where_clauses: Vec, // TODO: in_rules; }