// Copyright 2016-2018 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 edn; use edn::{Keyword, PlainSymbol}; use edn::query::{ Direction, Element, FindSpec, FnArg, Limit, NonIntegerConstant, Offset, OrJoin, OrWhereClause, Order, Pattern, PatternNonValuePlace, PatternValuePlace, Predicate, UnifyVars, Variable, WhereClause, }; use edn::parse::parse_query; // 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_predicates() { let s = "[:find [?x ...] :where [?x _ ?y] [(< ?y 10)]]"; let p = parse_query(s).unwrap(); assert_eq!( p.find_spec, FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x"))) ); assert_eq!( p.where_clauses, vec![ WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, }), WhereClause::Pred(Predicate { operator: PlainSymbol::plain("<"), args: vec![ FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10), ] }), ] ); } #[test] fn can_parse_simple_or() { let s = "[:find ?x . :where (or [?x _ 10] [?x _ 15])]"; let p = parse_query(s).unwrap(); assert_eq!( p.find_spec, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) ); assert_eq!( p.where_clauses, vec![WhereClause::OrJoin(OrJoin::new( UnifyVars::Implicit, vec![ OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(10), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(15), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), ], )),] ); } #[test] fn can_parse_unit_or_join() { let s = "[:find ?x . :where (or-join [?x] [?x _ 15])]"; let p = parse_query(s).expect("to be able to parse find"); assert_eq!( p.find_spec, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) ); assert_eq!( p.where_clauses, vec![WhereClause::OrJoin(OrJoin::new( UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), vec![OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(15), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })),], )),] ); } #[test] fn can_parse_simple_or_join() { let s = "[:find ?x . :where (or-join [?x] [?x _ 10] [?x _ -15])]"; let p = parse_query(s).unwrap(); assert_eq!( p.find_spec, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) ); assert_eq!( p.where_clauses, vec![WhereClause::OrJoin(OrJoin::new( UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), vec![ OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(10), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(-15), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), ], )),] ); } #[cfg(test)] fn ident(ns: &str, name: &str) -> PatternNonValuePlace { Keyword::namespaced(ns, name).into() } #[test] fn can_parse_simple_or_and_join() { let s = "[:find ?x . :where (or [?x _ 10] (and (or [?x :foo/bar ?y] [?x :foo/baz ?y]) [(< ?y 1)]))]"; let p = parse_query(s).unwrap(); assert_eq!( p.find_spec, FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) ); assert_eq!( p.where_clauses, vec![WhereClause::OrJoin(OrJoin::new( UnifyVars::Implicit, vec![ OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), attribute: PatternNonValuePlace::Placeholder, value: PatternValuePlace::EntidOrInteger(10), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), OrWhereClause::And(vec![ WhereClause::OrJoin(OrJoin::new( UnifyVars::Implicit, vec![ OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name( "?x" )), attribute: ident("foo", "bar"), value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, entity: PatternNonValuePlace::Variable(Variable::from_valid_name( "?x" )), attribute: ident("foo", "baz"), value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), tx: PatternNonValuePlace::Placeholder, added: PatternNonValuePlace::Placeholder, })), ], )), WhereClause::Pred(Predicate { operator: PlainSymbol::plain("<"), args: vec![ FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(1), ] }), ],) ], )),] ); } #[test] fn can_parse_order_by() { let invalid = "[:find ?x :where [?x :foo/baz ?y] :order]"; assert!(parse_query(invalid).is_err()); // Defaults to ascending. let default = "[:find ?x :where [?x :foo/baz ?y] :order ?y]"; assert_eq!( parse_query(default).unwrap().order, Some(vec![Order( Direction::Ascending, Variable::from_valid_name("?y") )]) ); let ascending = "[:find ?x :where [?x :foo/baz ?y] :order (asc ?y)]"; assert_eq!( parse_query(ascending).unwrap().order, Some(vec![Order( Direction::Ascending, Variable::from_valid_name("?y") )]) ); let descending = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y)]"; assert_eq!( parse_query(descending).unwrap().order, Some(vec![Order( Direction::Descending, Variable::from_valid_name("?y") )]) ); let mixed = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y) (asc ?x)]"; assert_eq!( parse_query(mixed).unwrap().order, Some(vec![ Order(Direction::Descending, Variable::from_valid_name("?y")), Order(Direction::Ascending, Variable::from_valid_name("?x")) ]) ); } #[test] fn can_parse_limit() { let invalid = "[:find ?x :where [?x :foo/baz ?y] :limit]"; assert!(parse_query(invalid).is_err()); let zero_invalid = "[:find ?x :where [?x :foo/baz ?y] :limit 00]"; assert!(parse_query(zero_invalid).is_err()); let none = "[:find ?x :where [?x :foo/baz ?y]]"; assert_eq!(parse_query(none).unwrap().limit, Limit::Unlimited); let one = "[:find ?x :where [?x :foo/baz ?y] :limit 1]"; assert_eq!(parse_query(one).unwrap().limit, Limit::Fixed(1)); let onethousand = "[:find ?x :where [?x :foo/baz ?y] :limit 1000]"; assert_eq!(parse_query(onethousand).unwrap().limit, Limit::Fixed(1000)); let variable_with_in = "[:find ?x :in ?limit :where [?x :foo/baz ?y] :limit ?limit]"; assert_eq!( parse_query(variable_with_in).unwrap().limit, Limit::Variable(Variable::from_valid_name("?limit")) ); let variable_with_in_used = "[:find ?x :in ?limit :where [?x :foo/baz ?limit] :limit ?limit]"; assert_eq!( parse_query(variable_with_in_used).unwrap().limit, Limit::Variable(Variable::from_valid_name("?limit")) ); } #[test] fn can_parse_uuid() { let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid"); let s = "[:find ?x :where [?x :foo/baz #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]]"; assert_eq!( parse_query(s) .expect("parsed") .where_clauses .pop() .expect("a where clause"), WhereClause::Pattern( Pattern::new( None, PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), Keyword::namespaced("foo", "baz").into(), PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)), PatternNonValuePlace::Placeholder, PatternNonValuePlace::Placeholder ) .expect("valid pattern") ) ); } #[test] fn can_parse_exotic_whitespace() { let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid"); // The query string from `can_parse_uuid`, with newlines, commas, and line comments interspersed. let s = r#"[:find ?x ,, :where, ;atest [?x :foo/baz #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644", ;testa ,],, ,],;"#; assert_eq!( parse_query(s) .expect("parsed") .where_clauses .pop() .expect("a where clause"), WhereClause::Pattern( Pattern::new( None, PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), Keyword::namespaced("foo", "baz").into(), PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)), PatternNonValuePlace::Placeholder, PatternNonValuePlace::Placeholder ) .expect("valid pattern") ) ); } // --- Ported from pg_mentat: query grammar superset (:offset, :distinct, :rules, // rule invocations). These prove the parser produces the right AST; SQL // generation for these is a later per-feature port. #[test] fn can_parse_offset() { let p = parse_query("[:find ?x :where [?x _ ?y] :limit 10 :offset 5]").unwrap(); assert_eq!(p.limit, Limit::Fixed(10)); assert_eq!(p.offset, Offset::Fixed(5)); } #[test] fn offset_defaults_to_unlimited() { let p = parse_query("[:find ?x :where [?x _ ?y]]").unwrap(); assert_eq!(p.offset, Offset::Unlimited); assert!(!p.distinct); assert!(p.rules.is_empty()); } #[test] fn offset_variable_and_zero() { let p = parse_query("[:find ?x :in ?n :where [?x _ ?y] :offset ?n]").unwrap(); assert_eq!(p.offset, Offset::Variable(Variable::from_valid_name("?n"))); // zero offset is valid (unlike limit, which requires positive) let z = parse_query("[:find ?x :where [?x _ ?y] :offset 0]").unwrap(); assert_eq!(z.offset, Offset::Fixed(0)); } #[test] fn can_parse_distinct() { let p = parse_query("[:find ?x :where [?x _ ?y] :distinct]").unwrap(); assert!(p.distinct); } #[test] fn can_parse_rule_invocation_in_where() { // A parenthesized non-reserved symbol with args is a rule invocation. let p = parse_query("[:find ?a :where (ancestor ?p ?a)]").unwrap(); assert_eq!(p.where_clauses.len(), 1); match &p.where_clauses[0] { WhereClause::RuleExpr(inv) => { assert_eq!(inv.name, PlainSymbol::plain("ancestor")); assert_eq!(inv.args.len(), 2); assert_eq!( inv.args[0], FnArg::Variable(Variable::from_valid_name("?p")) ); } other => panic!("expected RuleExpr, got {:?}", other), } } #[test] fn can_parse_rule_definitions() { // Recursive rule with two clauses (base + recursive), Datomic-style :rules. let s = "[:find ?a \ :where (ancestor ?p ?a) \ :rules [[(ancestor ?p ?a) [?p :parent ?a]] \ [(ancestor ?p ?a) [?p :parent ?x] (ancestor ?x ?a)]]]"; let p = parse_query(s).unwrap(); assert_eq!(p.rules.len(), 1, "two clauses share one rule name"); let rule = &p.rules[0]; assert_eq!(rule.name, PlainSymbol::plain("ancestor")); assert_eq!(rule.clauses.len(), 2); // recursive clause body has a pattern + a nested rule invocation assert_eq!(rule.clauses[1].body.len(), 2); assert!(matches!(rule.clauses[1].body[1], WhereClause::RuleExpr(_))); } #[test] fn reserved_clauses_are_not_rule_invocations() { // `(not ...)` etc. must still parse as their own clause types, not rules. let p = parse_query("[:find ?x :where [?x _ ?y] (not [?x :hidden true])]").unwrap(); assert!(p .where_clauses .iter() .any(|w| matches!(w, WhereClause::NotJoin(_)))); assert!(!p .where_clauses .iter() .any(|w| matches!(w, WhereClause::RuleExpr(_)))); } #[test] fn can_parse_reverse_pull_attribute() { let p = parse_query("[:find (pull ?e [:person/_friend]) . :where [?e _ _]]").unwrap(); // The forward ident is stored, with reverse = true. match &p.find_spec { FindSpec::FindScalar(Element::Pull(pull)) => { assert_eq!(pull.patterns.len(), 1); match &pull.patterns[0] { edn::query::PullAttributeSpec::Attribute(a) => { assert!(a.reverse, "underscore prefix marks reverse"); assert_eq!( format!("{}", a.attribute), ":person/friend", "stores forward ident" ); } other => panic!("expected Attribute, got {:?}", other), } } other => panic!("expected scalar pull, got {:?}", other), } }