2017-01-25 22:06:19 +00:00
|
|
|
// 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;
|
2017-02-03 02:32:00 +00:00
|
|
|
extern crate mentat_parser_utils;
|
2017-01-25 22:06:19 +00:00
|
|
|
extern crate mentat_query;
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
use std; // To refer to std::result::Result.
|
|
|
|
|
2017-04-18 01:46:40 +00:00
|
|
|
use std::collections::BTreeSet;
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
use self::combine::{eof, many, many1, optional, parser, satisfy, satisfy_map, Parser, ParseResult, Stream};
|
2017-04-19 23:16:19 +00:00
|
|
|
use self::combine::combinator::{any, choice, or, try};
|
2017-02-24 05:13:04 +00:00
|
|
|
|
|
|
|
use self::mentat_parser_utils::{
|
2017-05-04 20:40:41 +00:00
|
|
|
KeywordMapParser,
|
2017-02-24 05:13:04 +00:00
|
|
|
ResultParser,
|
|
|
|
ValueParseError,
|
|
|
|
};
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
use self::mentat_parser_utils::value_and_span::Stream as ValueStream;
|
|
|
|
use self::mentat_parser_utils::value_and_span::{
|
|
|
|
Item,
|
|
|
|
OfExactlyParsing,
|
|
|
|
keyword_map,
|
|
|
|
list,
|
|
|
|
map,
|
|
|
|
seq,
|
|
|
|
vector,
|
|
|
|
};
|
|
|
|
|
2017-02-03 02:32:00 +00:00
|
|
|
use self::mentat_query::{
|
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
|
|
|
Direction,
|
2017-02-03 02:32:00 +00:00
|
|
|
Element,
|
|
|
|
FindQuery,
|
|
|
|
FindSpec,
|
2017-03-16 14:44:56 +00:00
|
|
|
FnArg,
|
2017-02-03 02:32:00 +00:00
|
|
|
FromValue,
|
2017-04-19 23:16:19 +00:00
|
|
|
Limit,
|
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
|
|
|
Order,
|
2017-03-23 20:10:44 +00:00
|
|
|
OrJoin,
|
|
|
|
OrWhereClause,
|
2017-04-28 09:44:11 +00:00
|
|
|
NotJoin,
|
2017-02-03 02:32:00 +00:00
|
|
|
Pattern,
|
|
|
|
PatternNonValuePlace,
|
|
|
|
PatternValuePlace,
|
2017-03-16 14:44:56 +00:00
|
|
|
Predicate,
|
|
|
|
PredicateFn,
|
2017-02-03 02:32:00 +00:00
|
|
|
SrcVar,
|
2017-03-23 20:10:44 +00:00
|
|
|
UnifyVars,
|
2017-02-03 02:32:00 +00:00
|
|
|
Variable,
|
|
|
|
WhereClause,
|
|
|
|
};
|
|
|
|
|
2017-02-24 05:13:04 +00:00
|
|
|
error_chain! {
|
|
|
|
types {
|
|
|
|
Error, ErrorKind, ResultExt, Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreign_links {
|
|
|
|
EdnParseError(edn::parse::ParseError);
|
|
|
|
}
|
|
|
|
|
|
|
|
errors {
|
2017-04-18 01:46:40 +00:00
|
|
|
DuplicateVariableError {
|
|
|
|
description("duplicate variable")
|
|
|
|
display("duplicates in variable list")
|
|
|
|
}
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
NotAVariableError(value: edn::ValueAndSpan) {
|
2017-02-24 05:13:04 +00:00
|
|
|
description("not a variable")
|
|
|
|
display("not a variable: '{}'", value)
|
|
|
|
}
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
FindParseError(e: ValueParseError) {
|
2017-02-24 05:13:04 +00:00
|
|
|
description(":find parse error")
|
|
|
|
display(":find parse error")
|
|
|
|
}
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
WhereParseError(e: ValueParseError) {
|
2017-02-24 05:13:04 +00:00
|
|
|
description(":where parse error")
|
|
|
|
display(":where parse error")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not yet used.
|
|
|
|
WithParseError {
|
|
|
|
description(":with parse error")
|
|
|
|
display(":with parse error")
|
|
|
|
}
|
|
|
|
|
|
|
|
InvalidInputError(value: edn::Value) {
|
|
|
|
description("invalid input")
|
|
|
|
display("invalid input: '{}'", value)
|
|
|
|
}
|
|
|
|
|
|
|
|
MissingFieldError(value: edn::Keyword) {
|
|
|
|
description("missing field")
|
|
|
|
display("missing field: '{}'", value)
|
|
|
|
}
|
2017-04-19 23:16:19 +00:00
|
|
|
|
|
|
|
UnknownLimitVar(var: edn::PlainSymbol) {
|
|
|
|
description("limit var not present in :in")
|
|
|
|
display("limit var {} not present in :in", var)
|
|
|
|
}
|
|
|
|
|
|
|
|
InvalidLimit(val: edn::Value) {
|
|
|
|
description("limit value not valid")
|
|
|
|
display("expected natural number, got {}", val)
|
|
|
|
}
|
2017-02-24 05:13:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
pub struct Query<'a>(std::marker::PhantomData<&'a ()>);
|
2017-01-25 22:06:19 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Query, variable, Variable, {
|
|
|
|
satisfy_map(Variable::from_value)
|
|
|
|
});
|
2017-01-25 22:06:19 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Query, source_var, SrcVar, {
|
|
|
|
satisfy_map(SrcVar::from_value)
|
|
|
|
});
|
2017-01-25 22:06:19 +00:00
|
|
|
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
// TODO: interning.
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Query, predicate_fn, PredicateFn, {
|
|
|
|
satisfy_map(PredicateFn::from_value)
|
|
|
|
});
|
2017-03-23 20:10:44 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Query, fn_arg, FnArg, {
|
|
|
|
satisfy_map(FnArg::from_value)
|
|
|
|
});
|
2017-02-03 02:32:00 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Query, arguments, Vec<FnArg>, {
|
|
|
|
(many::<Vec<FnArg>, _>(Query::fn_arg()))
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
|
|
|
def_parser!(Query, direction, Direction, {
|
2017-05-04 20:40:41 +00:00
|
|
|
satisfy_map(|v: &edn::ValueAndSpan| {
|
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
|
|
|
match v.inner {
|
|
|
|
edn::SpannedValue::PlainSymbol(ref s) => {
|
|
|
|
let name = s.0.as_str();
|
|
|
|
match name {
|
|
|
|
"asc" => Some(Direction::Ascending),
|
|
|
|
"desc" => Some(Direction::Descending),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
def_parser!(Query, order, Order, {
|
|
|
|
seq().of_exactly((Query::direction(), Query::variable()))
|
|
|
|
.map(|(d, v)| Order(d, v))
|
|
|
|
.or(Query::variable().map(|v| Order(Direction::Ascending, v)))
|
|
|
|
});
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
pub struct Where<'a>(std::marker::PhantomData<&'a ()>);
|
2017-04-06 17:06:28 +00:00
|
|
|
|
|
|
|
def_parser!(Where, pattern_value_place, PatternValuePlace, {
|
|
|
|
satisfy_map(PatternValuePlace::from_value)
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-19 23:16:19 +00:00
|
|
|
def_parser!(Query, natural_number, u64, {
|
2017-05-04 20:40:41 +00:00
|
|
|
any().and_then(|v: &edn::ValueAndSpan| {
|
2017-04-19 23:16:19 +00:00
|
|
|
match v.inner {
|
|
|
|
edn::SpannedValue::Integer(x) if (x > 0) => {
|
|
|
|
Ok(x as u64)
|
|
|
|
},
|
2017-05-04 20:40:41 +00:00
|
|
|
ref spanned => {
|
|
|
|
let e = Box::new(Error::from_kind(ErrorKind::InvalidLimit(spanned.clone().into())));
|
2017-04-19 23:16:19 +00:00
|
|
|
Err(combine::primitives::Error::Other(e))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, pattern_non_value_place, PatternNonValuePlace, {
|
|
|
|
satisfy_map(PatternNonValuePlace::from_value)
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_matches_plain_symbol!(Where, and, "and");
|
|
|
|
|
|
|
|
def_matches_plain_symbol!(Where, or, "or");
|
|
|
|
|
|
|
|
def_matches_plain_symbol!(Where, or_join, "or-join");
|
|
|
|
|
2017-04-28 09:44:11 +00:00
|
|
|
def_matches_plain_symbol!(Where, not, "not");
|
|
|
|
|
|
|
|
def_matches_plain_symbol!(Where, not_join, "not-join");
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, rule_vars, Vec<Variable>, {
|
|
|
|
seq()
|
|
|
|
.of_exactly(many1(Query::variable()))
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, or_pattern_clause, OrWhereClause, {
|
|
|
|
Where::clause().map(|clause| OrWhereClause::Clause(clause))
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, or_and_clause, OrWhereClause, {
|
|
|
|
seq()
|
|
|
|
.of_exactly(Where::and()
|
|
|
|
.with(many1(Where::clause()))
|
|
|
|
.map(OrWhereClause::And))
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, or_where_clause, OrWhereClause, {
|
|
|
|
choice([Where::or_pattern_clause(), Where::or_and_clause()])
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, or_clause, WhereClause, {
|
|
|
|
seq()
|
|
|
|
.of_exactly(Where::or()
|
|
|
|
.with(many1(Where::or_where_clause()))
|
|
|
|
.map(|clauses| {
|
2017-04-04 21:54:08 +00:00
|
|
|
WhereClause::OrJoin(OrJoin::new(UnifyVars::Implicit, clauses))
|
2017-04-06 17:06:28 +00:00
|
|
|
}))
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, or_join_clause, WhereClause, {
|
|
|
|
seq()
|
|
|
|
.of_exactly(Where::or_join()
|
|
|
|
.with(Where::rule_vars())
|
|
|
|
.and(many1(Where::or_where_clause()))
|
|
|
|
.map(|(vars, clauses)| {
|
2017-04-04 21:54:08 +00:00
|
|
|
WhereClause::OrJoin(OrJoin::new(UnifyVars::Explicit(vars), clauses))
|
2017-04-06 17:06:28 +00:00
|
|
|
}))
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-28 09:44:11 +00:00
|
|
|
def_parser!(Where, not_clause, WhereClause, {
|
|
|
|
seq()
|
|
|
|
.of_exactly(Where::not()
|
|
|
|
.with(many1(Where::clause()))
|
|
|
|
.map(|clauses| {
|
|
|
|
WhereClause::NotJoin(
|
|
|
|
NotJoin {
|
|
|
|
unify_vars: UnifyVars::Implicit,
|
|
|
|
clauses: clauses,
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
});
|
|
|
|
|
|
|
|
def_parser!(Where, not_join_clause, WhereClause, {
|
|
|
|
seq()
|
|
|
|
.of_exactly(Where::not_join()
|
|
|
|
.with(Where::rule_vars())
|
|
|
|
.and(many1(Where::clause()))
|
|
|
|
.map(|(vars, clauses)| {
|
|
|
|
WhereClause::NotJoin(
|
|
|
|
NotJoin {
|
|
|
|
unify_vars: UnifyVars::Explicit(vars),
|
|
|
|
clauses: clauses,
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
});
|
|
|
|
|
2017-03-16 14:44:56 +00:00
|
|
|
/// A vector containing just a parenthesized filter expression.
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, pred, WhereClause, {
|
|
|
|
// Accept either a nested list or a nested vector here:
|
|
|
|
// `[(foo ?x ?y)]` or `[[foo ?x ?y]]`
|
|
|
|
vector()
|
|
|
|
.of_exactly(seq()
|
|
|
|
.of_exactly((Query::predicate_fn(), Query::arguments())
|
|
|
|
.map(|(f, args)| {
|
|
|
|
WhereClause::Pred(
|
|
|
|
Predicate {
|
|
|
|
operator: f.0,
|
|
|
|
args: args,
|
|
|
|
})
|
|
|
|
})))
|
2017-03-16 14:44:56 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, pattern, WhereClause, {
|
|
|
|
vector()
|
|
|
|
.of_exactly(
|
2017-02-03 02:32:00 +00:00
|
|
|
// While *technically* Datomic allows you to have a query like:
|
|
|
|
// [:find … :where [[?x]]]
|
2017-03-16 14:44:56 +00:00
|
|
|
// We don't -- we require at least e, a.
|
2017-04-06 17:06:28 +00:00
|
|
|
(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
|
|
|
|
.and_then(|(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].
|
|
|
|
//
|
|
|
|
// 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 a nested optional, which we unwrap here.
|
|
|
|
Pattern::new(src, e, a, v, tx)
|
|
|
|
.map(WhereClause::Pattern)
|
|
|
|
.ok_or(combine::primitives::Error::Expected("pattern".into()))
|
|
|
|
}))
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, clause, WhereClause, {
|
|
|
|
choice([try(Where::pattern()),
|
2017-03-23 20:10:44 +00:00
|
|
|
// It's either
|
|
|
|
// (or-join [vars] clauses…)
|
|
|
|
// or
|
|
|
|
// (or clauses…)
|
|
|
|
// We don't yet handle source vars.
|
2017-04-06 17:06:28 +00:00
|
|
|
try(Where::or_join_clause()),
|
|
|
|
try(Where::or_clause()),
|
2017-04-28 09:44:11 +00:00
|
|
|
try(Where::not_join_clause()),
|
|
|
|
try(Where::not_clause()),
|
2017-04-06 17:06:28 +00:00
|
|
|
|
|
|
|
try(Where::pred()),
|
|
|
|
])
|
2017-03-23 20:10:44 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Where, clauses, Vec<WhereClause>, {
|
2017-03-16 14:44:56 +00:00
|
|
|
// Right now we only support patterns and predicates. See #239 for more.
|
2017-03-23 20:10:44 +00:00
|
|
|
(many1::<Vec<WhereClause>, _>(Where::clause()))
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
pub struct Find<'a>(std::marker::PhantomData<&'a ()>);
|
2017-02-03 02:32:00 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_matches_plain_symbol!(Find, period, ".");
|
2017-02-03 02:32:00 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_matches_plain_symbol!(Find, ellipsis, "...");
|
2017-02-03 02:32:00 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Find, find_scalar, FindSpec, {
|
2017-03-23 20:10:44 +00:00
|
|
|
Query::variable()
|
|
|
|
.skip(Find::period())
|
|
|
|
.map(|var| FindSpec::FindScalar(Element::Variable(var)))
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Find, find_coll, FindSpec, {
|
|
|
|
vector()
|
|
|
|
.of_exactly(Query::variable()
|
|
|
|
.skip(Find::ellipsis()))
|
|
|
|
.map(|var| FindSpec::FindColl(Element::Variable(var)))
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Find, elements, Vec<Element>, {
|
|
|
|
many1::<Vec<Element>, _>(Query::variable().map(Element::Variable))
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Find, find_rel, FindSpec, {
|
|
|
|
Find::elements().map(FindSpec::FindRel)
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
def_parser!(Find, find_tuple, FindSpec, {
|
|
|
|
vector()
|
|
|
|
.of_exactly(Find::elements().map(FindSpec::FindTuple))
|
2017-02-03 02:32:00 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
/// Parse a stream of values into one of four find specs.
|
|
|
|
///
|
|
|
|
/// `:find` must be an array of plain var symbols (?foo), pull expressions, and aggregates. For now
|
|
|
|
/// we only support variables and the annotations necessary to declare which flavor of :find we
|
|
|
|
/// want:
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// `?x ?y ?z ` = FindRel
|
|
|
|
/// `[?x ...] ` = FindColl
|
|
|
|
/// `?x . ` = FindScalar
|
|
|
|
/// `[?x ?y ?z]` = FindTuple
|
|
|
|
def_parser!(Find, spec, FindSpec, {
|
|
|
|
// 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<Input = _, Output = FindSpec>; 4], _>
|
2017-02-03 02:32:00 +00:00
|
|
|
([&mut try(Find::find_scalar()),
|
|
|
|
&mut try(Find::find_coll()),
|
|
|
|
&mut try(Find::find_tuple()),
|
|
|
|
&mut try(Find::find_rel())])
|
|
|
|
});
|
|
|
|
|
2017-04-18 01:46:40 +00:00
|
|
|
def_parser!(Find, vars, BTreeSet<Variable>, {
|
2017-05-04 20:40:41 +00:00
|
|
|
many(Query::variable()).and_then(|vars: Vec<Variable>| {
|
|
|
|
let given = vars.len();
|
|
|
|
let set: BTreeSet<Variable> = vars.into_iter().collect();
|
|
|
|
if given != set.len() {
|
|
|
|
// TODO: find out what the variable is!
|
|
|
|
let e = Box::new(Error::from_kind(ErrorKind::DuplicateVariableError));
|
|
|
|
Err(combine::primitives::Error::Other(e))
|
|
|
|
} else {
|
|
|
|
Ok(set)
|
|
|
|
}
|
|
|
|
})
|
2017-04-18 01:46:40 +00:00
|
|
|
});
|
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
/// This is awkward, but will do for now. We use `keyword_map()` to optionally accept vector find
|
|
|
|
/// queries, then we use `FindQueryPart` to collect parts that have heterogeneous types; and then we
|
|
|
|
/// construct a `FindQuery` from them.
|
|
|
|
def_parser!(Find, query, FindQuery, {
|
2017-05-04 20:40:41 +00:00
|
|
|
let find_map = keyword_map_of!(
|
|
|
|
("find", Find::spec()),
|
|
|
|
("in", Find::vars()),
|
|
|
|
("limit", Query::variable().map(Limit::Variable).or(Query::natural_number().map(Limit::Fixed))),
|
|
|
|
("order", many1(Query::order())),
|
|
|
|
("where", Where::clauses()),
|
|
|
|
("with", Find::vars()) // Note: no trailing comma allowed!
|
|
|
|
);
|
|
|
|
|
|
|
|
(or(keyword_map(), vector()))
|
|
|
|
.of_exactly(find_map)
|
|
|
|
.and_then(|(find_spec, in_vars, limit, order_clauses, where_clauses, with_vars) | -> std::result::Result<FindQuery, combine::primitives::Error<&edn::ValueAndSpan, &edn::ValueAndSpan>> {
|
|
|
|
let limit = limit.unwrap_or(Limit::None);
|
2017-04-19 23:16:19 +00:00
|
|
|
|
|
|
|
// Make sure that if we have `:limit ?x`, `?x` appears in `:in`.
|
|
|
|
let in_vars = in_vars.unwrap_or(BTreeSet::default());
|
|
|
|
if let Limit::Variable(ref v) = limit {
|
|
|
|
if !in_vars.contains(v) {
|
|
|
|
let e = Box::new(Error::from_kind(ErrorKind::UnknownLimitVar(v.name())));
|
|
|
|
return Err(combine::primitives::Error::Other(e));
|
2017-04-06 17:06:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(FindQuery {
|
|
|
|
default_source: SrcVar::DefaultSrc,
|
2017-04-19 23:16:19 +00:00
|
|
|
find_spec: find_spec.clone().ok_or(combine::primitives::Error::Unexpected("expected :find".into()))?,
|
2017-04-18 01:46:40 +00:00
|
|
|
in_sources: BTreeSet::default(), // TODO
|
2017-04-19 23:16:19 +00:00
|
|
|
in_vars: in_vars,
|
|
|
|
limit: limit,
|
Implement :order. (#415) (#416) r=nalexander
This adds an `:order` keyword to `:find`.
If present, the results of the query will be an ordered set, rather than
an unordered set; rows will appear in an ordered defined by each
`:order` entry.
Each can be one of three things:
- A var, `?x`, meaning "order by ?x ascending".
- A pair, `(asc ?x)`, meaning "order by ?x ascending".
- A pair, `(desc ?x)`, meaning "order by ?x descending".
Values will be ordered in this sequence for asc, and in reverse for desc:
1. Entity IDs, in ascending numerical order.
2. Booleans, false then true.
3. Timestamps, in ascending numerical order.
4. Longs and doubles, intermixed, in ascending numerical order.
5. Strings, in ascending lexicographic order.
6. Keywords, in ascending lexicographic order, considering the entire
ns/name pair as a single string separated by '/'.
Subcommits:
Pre: make bound_value public.
Pre: generalize ErrorKind::UnboundVariable for use in order.
Part 1: parse (direction, var) pairs.
Part 2: parse :order clause into FindQuery.
Part 3: include order variables in algebrized query.
We add order variables to :with, so we can reuse its type tag projection
logic, and so that we can phrase ordering in terms of variables rather
than datoms columns.
Part 4: produce SQL for order clauses.
2017-04-14 23:10:56 +00:00
|
|
|
order: order_clauses,
|
2017-04-06 17:06:28 +00:00
|
|
|
where_clauses: where_clauses.ok_or(combine::primitives::Error::Unexpected("expected :where".into()))?,
|
2017-04-19 23:16:19 +00:00
|
|
|
with: with_vars.unwrap_or(BTreeSet::default()),
|
2017-04-06 17:06:28 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
pub fn parse_find_string(string: &str) -> Result<FindQuery> {
|
|
|
|
let expr = edn::parse::value(string)?;
|
|
|
|
Find::query()
|
2017-05-04 20:40:41 +00:00
|
|
|
.parse(expr.atom_stream())
|
2017-02-03 02:32:00 +00:00
|
|
|
.map(|x| x.0)
|
2017-05-04 20:40:41 +00:00
|
|
|
.map_err(|e| Error::from_kind(ErrorKind::FindParseError(e.into())))
|
2017-02-03 02:32:00 +00:00
|
|
|
}
|
|
|
|
|
2017-01-27 20:07:01 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
extern crate combine;
|
|
|
|
extern crate edn;
|
|
|
|
extern crate mentat_query;
|
|
|
|
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
use std::rc::Rc;
|
|
|
|
|
2017-01-27 20:07:01 +00:00
|
|
|
use self::combine::Parser;
|
2017-02-08 11:19:16 +00:00
|
|
|
use self::edn::OrderedFloat;
|
2017-02-03 02:32:00 +00:00
|
|
|
use self::mentat_query::{
|
|
|
|
Element,
|
|
|
|
FindSpec,
|
|
|
|
NonIntegerConstant,
|
|
|
|
Pattern,
|
|
|
|
PatternNonValuePlace,
|
|
|
|
PatternValuePlace,
|
|
|
|
SrcVar,
|
|
|
|
Variable,
|
|
|
|
};
|
2017-01-27 20:07:01 +00:00
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
fn variable(x: edn::PlainSymbol) -> Variable {
|
|
|
|
Variable(Rc::new(x))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ident_kw(kw: edn::NamespacedKeyword) -> PatternNonValuePlace {
|
|
|
|
PatternNonValuePlace::Ident(Rc::new(kw))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ident(ns: &str, name: &str) -> PatternNonValuePlace {
|
|
|
|
ident_kw(edn::NamespacedKeyword::new(ns, name))
|
|
|
|
}
|
|
|
|
|
2017-02-03 02:32:00 +00:00
|
|
|
#[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");
|
2017-04-06 17:06:28 +00:00
|
|
|
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())));
|
2017-03-16 14:44:56 +00:00
|
|
|
assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern {
|
2017-02-03 02:32:00 +00:00
|
|
|
source: None,
|
|
|
|
entity: PatternNonValuePlace::Placeholder,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
attribute: ident_kw(a),
|
2017-02-03 02:32:00 +00:00
|
|
|
value: PatternValuePlace::Constant(NonIntegerConstant::Float(v)),
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
tx: PatternNonValuePlace::Variable(variable(tx)),
|
2017-03-16 14:44:56 +00:00
|
|
|
}));
|
2017-02-03 02:32:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec!(edn::Value::PlainSymbol(s.clone()),
|
2017-02-03 02:32:00 +00:00
|
|
|
edn::Value::PlainSymbol(e.clone()),
|
|
|
|
edn::Value::PlainSymbol(a.clone()),
|
|
|
|
edn::Value::PlainSymbol(v.clone()),
|
2017-04-06 17:06:28 +00:00
|
|
|
edn::Value::PlainSymbol(tx.clone())));
|
2017-03-16 14:44:56 +00:00
|
|
|
assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern {
|
2017-02-03 02:32:00 +00:00
|
|
|
source: Some(SrcVar::NamedSrc("x".to_string())),
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
entity: PatternNonValuePlace::Variable(variable(e)),
|
|
|
|
attribute: PatternNonValuePlace::Variable(variable(a)),
|
|
|
|
value: PatternValuePlace::Variable(variable(v)),
|
|
|
|
tx: PatternNonValuePlace::Variable(variable(tx)),
|
2017-03-16 14:44:56 +00:00
|
|
|
}));
|
2017-02-03 02:32:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()),
|
2017-02-03 02:32:00 +00:00
|
|
|
edn::Value::NamespacedKeyword(a.clone()),
|
|
|
|
edn::Value::Float(v.clone()),
|
2017-04-06 17:06:28 +00:00
|
|
|
edn::Value::PlainSymbol(tx.clone())));
|
2017-02-03 02:32:00 +00:00
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let input = input.with_spans();
|
2017-02-03 02:32:00 +00:00
|
|
|
let mut par = Where::pattern();
|
2017-05-04 20:40:41 +00:00
|
|
|
let result = par.parse(input.atom_stream());
|
2017-02-03 02:32:00 +00:00
|
|
|
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");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec!(edn::Value::PlainSymbol(e.clone()),
|
2017-02-03 02:32:00 +00:00
|
|
|
edn::Value::NamespacedKeyword(a.clone()),
|
|
|
|
edn::Value::PlainSymbol(v.clone()),
|
2017-04-06 17:06:28 +00:00
|
|
|
edn::Value::PlainSymbol(tx.clone())));
|
2017-02-03 02:32:00 +00:00
|
|
|
|
|
|
|
// Note that the attribute is no longer reversed, and the entity and value have
|
|
|
|
// switched places.
|
2017-03-16 14:44:56 +00:00
|
|
|
assert_parses_to!(Where::pattern, input, WhereClause::Pattern(Pattern {
|
2017-02-03 02:32:00 +00:00
|
|
|
source: None,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
entity: PatternNonValuePlace::Variable(variable(v)),
|
|
|
|
attribute: ident("foo", "bar"),
|
2017-02-03 02:32:00 +00:00
|
|
|
value: PatternValuePlace::Placeholder,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
tx: PatternNonValuePlace::Variable(variable(tx)),
|
2017-03-16 14:44:56 +00:00
|
|
|
}));
|
2017-02-03 02:32:00 +00:00
|
|
|
}
|
|
|
|
|
2017-03-23 20:10:44 +00:00
|
|
|
#[test]
|
|
|
|
fn test_rule_vars() {
|
|
|
|
let e = edn::PlainSymbol::new("?e");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(e.clone())]);
|
2017-03-23 20:10:44 +00:00
|
|
|
assert_parses_to!(Where::rule_vars, input,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
vec![variable(e.clone())]);
|
2017-03-23 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2017-04-18 20:01:26 +00:00
|
|
|
#[test]
|
|
|
|
fn test_repeated_vars() {
|
|
|
|
let e = edn::PlainSymbol::new("?e");
|
|
|
|
let f = edn::PlainSymbol::new("?f");
|
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(e.clone()),
|
|
|
|
edn::Value::PlainSymbol(f.clone()),]);
|
2017-05-04 20:40:41 +00:00
|
|
|
assert_parses_to!(|| vector().of_exactly(Find::vars()), input,
|
2017-04-18 20:01:26 +00:00
|
|
|
vec![variable(e.clone()), variable(f.clone())].into_iter().collect());
|
|
|
|
|
|
|
|
let g = edn::PlainSymbol::new("?g");
|
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(g.clone()),
|
|
|
|
edn::Value::PlainSymbol(g.clone()),]);
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let input = input.with_spans();
|
|
|
|
let mut par = vector().of_exactly(Find::vars());
|
|
|
|
let result = par.parse(input.atom_stream())
|
2017-04-18 20:01:26 +00:00
|
|
|
.map(|x| x.0)
|
|
|
|
.map_err(|e| if let Some(combine::primitives::Error::Other(x)) = e.errors.into_iter().next() {
|
|
|
|
// Pattern matching on boxes is rocket science until Rust Nightly features hit
|
|
|
|
// stable. ErrorKind isn't Clone, so convert to strings. We could pattern match
|
|
|
|
// for exact comparison here.
|
|
|
|
x.downcast::<Error>().ok().map(|e| e.to_string())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
});
|
|
|
|
assert_eq!(result, Err(Some("duplicates in variable list".to_string())));
|
|
|
|
}
|
|
|
|
|
2017-03-23 20:10:44 +00:00
|
|
|
#[test]
|
|
|
|
fn test_or() {
|
|
|
|
let oj = edn::PlainSymbol::new("or");
|
|
|
|
let e = edn::PlainSymbol::new("?e");
|
|
|
|
let a = edn::PlainSymbol::new("?a");
|
|
|
|
let v = edn::PlainSymbol::new("?v");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::List(
|
2017-03-23 20:10:44 +00:00
|
|
|
vec![edn::Value::PlainSymbol(oj),
|
|
|
|
edn::Value::Vector(vec![edn::Value::PlainSymbol(e.clone()),
|
|
|
|
edn::Value::PlainSymbol(a.clone()),
|
2017-04-06 17:06:28 +00:00
|
|
|
edn::Value::PlainSymbol(v.clone())])].into_iter().collect());
|
2017-03-23 20:10:44 +00:00
|
|
|
assert_parses_to!(Where::or_clause, input,
|
|
|
|
WhereClause::OrJoin(
|
2017-04-04 21:54:08 +00:00
|
|
|
OrJoin::new(UnifyVars::Implicit,
|
|
|
|
vec![OrWhereClause::Clause(
|
|
|
|
WhereClause::Pattern(Pattern {
|
|
|
|
source: None,
|
|
|
|
entity: PatternNonValuePlace::Variable(variable(e)),
|
|
|
|
attribute: PatternNonValuePlace::Variable(variable(a)),
|
|
|
|
value: PatternValuePlace::Variable(variable(v)),
|
|
|
|
tx: PatternNonValuePlace::Placeholder,
|
|
|
|
}))])));
|
2017-03-23 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_or_join() {
|
|
|
|
let oj = edn::PlainSymbol::new("or-join");
|
|
|
|
let e = edn::PlainSymbol::new("?e");
|
|
|
|
let a = edn::PlainSymbol::new("?a");
|
|
|
|
let v = edn::PlainSymbol::new("?v");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::List(
|
2017-03-23 20:10:44 +00:00
|
|
|
vec![edn::Value::PlainSymbol(oj),
|
|
|
|
edn::Value::Vector(vec![edn::Value::PlainSymbol(e.clone())]),
|
|
|
|
edn::Value::Vector(vec![edn::Value::PlainSymbol(e.clone()),
|
|
|
|
edn::Value::PlainSymbol(a.clone()),
|
2017-04-06 17:06:28 +00:00
|
|
|
edn::Value::PlainSymbol(v.clone())])].into_iter().collect());
|
2017-03-23 20:10:44 +00:00
|
|
|
assert_parses_to!(Where::or_join_clause, input,
|
|
|
|
WhereClause::OrJoin(
|
2017-04-04 21:54:08 +00:00
|
|
|
OrJoin::new(UnifyVars::Explicit(vec![variable(e.clone())]),
|
|
|
|
vec![OrWhereClause::Clause(
|
|
|
|
WhereClause::Pattern(Pattern {
|
|
|
|
source: None,
|
|
|
|
entity: PatternNonValuePlace::Variable(variable(e)),
|
|
|
|
attribute: PatternNonValuePlace::Variable(variable(a)),
|
|
|
|
value: PatternValuePlace::Variable(variable(v)),
|
|
|
|
tx: PatternNonValuePlace::Placeholder,
|
|
|
|
}))])));
|
2017-03-23 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2017-04-28 09:44:11 +00:00
|
|
|
#[test]
|
|
|
|
fn test_not() {
|
|
|
|
let e = edn::PlainSymbol::new("?e");
|
|
|
|
let a = edn::PlainSymbol::new("?a");
|
|
|
|
let v = edn::PlainSymbol::new("?v");
|
|
|
|
|
|
|
|
assert_edn_parses_to!(Where::not_clause,
|
|
|
|
"(not [?e ?a ?v])",
|
|
|
|
WhereClause::NotJoin(
|
|
|
|
NotJoin {
|
|
|
|
unify_vars: UnifyVars::Implicit,
|
|
|
|
clauses: vec![
|
|
|
|
WhereClause::Pattern(Pattern {
|
|
|
|
source: None,
|
|
|
|
entity: PatternNonValuePlace::Variable(variable(e)),
|
|
|
|
attribute: PatternNonValuePlace::Variable(variable(a)),
|
|
|
|
value: PatternValuePlace::Variable(variable(v)),
|
|
|
|
tx: PatternNonValuePlace::Placeholder,
|
|
|
|
})],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_not_join() {
|
|
|
|
let e = edn::PlainSymbol::new("?e");
|
|
|
|
let a = edn::PlainSymbol::new("?a");
|
|
|
|
let v = edn::PlainSymbol::new("?v");
|
|
|
|
|
|
|
|
assert_edn_parses_to!(Where::not_join_clause,
|
|
|
|
"(not-join [?e] [?e ?a ?v])",
|
|
|
|
WhereClause::NotJoin(
|
|
|
|
NotJoin {
|
|
|
|
unify_vars: UnifyVars::Explicit(vec![variable(e.clone())]),
|
|
|
|
clauses: vec![WhereClause::Pattern(Pattern {
|
|
|
|
source: None,
|
|
|
|
entity: PatternNonValuePlace::Variable(variable(e)),
|
|
|
|
attribute: PatternNonValuePlace::Variable(variable(a)),
|
|
|
|
value: PatternValuePlace::Variable(variable(v)),
|
|
|
|
tx: PatternNonValuePlace::Placeholder,
|
|
|
|
})],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2017-01-27 20:07:01 +00:00
|
|
|
#[test]
|
|
|
|
fn test_find_sp_variable() {
|
|
|
|
let sym = edn::PlainSymbol::new("?x");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(sym.clone())]);
|
|
|
|
assert_parses_to!(|| vector().of_exactly(Query::variable()), input, variable(sym));
|
2017-01-27 20:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_scalar() {
|
|
|
|
let sym = edn::PlainSymbol::new("?x");
|
|
|
|
let period = edn::PlainSymbol::new(".");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(sym.clone()), edn::Value::PlainSymbol(period.clone())]);
|
|
|
|
assert_parses_to!(|| vector().of_exactly(Find::find_scalar()),
|
2017-01-27 20:07:01 +00:00
|
|
|
input,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
FindSpec::FindScalar(Element::Variable(variable(sym))));
|
2017-01-27 20:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_coll() {
|
|
|
|
let sym = edn::PlainSymbol::new("?x");
|
|
|
|
let period = edn::PlainSymbol::new("...");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(sym.clone()),
|
|
|
|
edn::Value::PlainSymbol(period.clone())]);
|
2017-02-03 02:32:00 +00:00
|
|
|
assert_parses_to!(Find::find_coll,
|
2017-01-27 20:07:01 +00:00
|
|
|
input,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
FindSpec::FindColl(Element::Variable(variable(sym))));
|
2017-01-27 20:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_rel() {
|
|
|
|
let vx = edn::PlainSymbol::new("?x");
|
|
|
|
let vy = edn::PlainSymbol::new("?y");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(vx.clone()), edn::Value::PlainSymbol(vy.clone())]);
|
|
|
|
assert_parses_to!(|| vector().of_exactly(Find::find_rel()),
|
2017-01-27 20:07:01 +00:00
|
|
|
input,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
FindSpec::FindRel(vec![Element::Variable(variable(vx)),
|
|
|
|
Element::Variable(variable(vy))]));
|
2017-01-27 20:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_tuple() {
|
|
|
|
let vx = edn::PlainSymbol::new("?x");
|
|
|
|
let vy = edn::PlainSymbol::new("?y");
|
2017-04-06 17:06:28 +00:00
|
|
|
let input = edn::Value::Vector(vec![edn::Value::PlainSymbol(vx.clone()),
|
|
|
|
edn::Value::PlainSymbol(vy.clone())]);
|
2017-02-03 02:32:00 +00:00
|
|
|
assert_parses_to!(Find::find_tuple,
|
2017-01-27 20:07:01 +00:00
|
|
|
input,
|
Use Rc for TypedValue, Variable, and query Ident keywords. (#395) r=nalexander
Part 1, core: use Rc for String and Keyword.
Part 2, query: use Rc for Variable.
Part 3, sql: use Rc for args in SQLiteQueryBuilder.
Part 4, query-algebrizer: use Rc.
Part 5, db: use Rc.
Part 6, query-parser: use Rc.
Part 7, query-projector: use Rc.
Part 8, query-translator: use Rc.
Part 9, top level: use Rc.
Part 10: intern Ident and IdentOrKeyword.
2017-03-29 20:18:17 +00:00
|
|
|
FindSpec::FindTuple(vec![Element::Variable(variable(vx)),
|
|
|
|
Element::Variable(variable(vy))]));
|
2017-01-27 20:07:01 +00:00
|
|
|
}
|
2017-04-19 23:16:19 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_natural_numbers() {
|
|
|
|
let text = edn::Value::Text("foo".to_string());
|
|
|
|
let neg = edn::Value::Integer(-10);
|
|
|
|
let zero = edn::Value::Integer(0);
|
|
|
|
let pos = edn::Value::Integer(5);
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
// This is terrible, but destructuring errors is frustrating.
|
|
|
|
let input = text.with_spans();
|
2017-04-19 23:16:19 +00:00
|
|
|
let mut par = Query::natural_number();
|
2017-05-04 20:40:41 +00:00
|
|
|
let x = par.parse(input.atom_stream()).err().expect("an error").errors;
|
2017-04-19 23:16:19 +00:00
|
|
|
let result = format!("{:?}", x);
|
|
|
|
assert_eq!(result, "[Other(Error(InvalidLimit(Text(\"foo\")), State { next_error: None, backtrace: None })), Expected(Borrowed(\"natural_number\"))]");
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let input = neg.with_spans();
|
2017-04-19 23:16:19 +00:00
|
|
|
let mut par = Query::natural_number();
|
2017-05-04 20:40:41 +00:00
|
|
|
let x = par.parse(input.atom_stream()).err().expect("an error").errors;
|
2017-04-19 23:16:19 +00:00
|
|
|
let result = format!("{:?}", x);
|
|
|
|
assert_eq!(result, "[Other(Error(InvalidLimit(Integer(-10)), State { next_error: None, backtrace: None })), Expected(Borrowed(\"natural_number\"))]");
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let input = zero.with_spans();
|
2017-04-19 23:16:19 +00:00
|
|
|
let mut par = Query::natural_number();
|
2017-05-04 20:40:41 +00:00
|
|
|
let x = par.parse(input.atom_stream()).err().expect("an error").errors;
|
2017-04-19 23:16:19 +00:00
|
|
|
let result = format!("{:?}", x);
|
|
|
|
assert_eq!(result, "[Other(Error(InvalidLimit(Integer(0)), State { next_error: None, backtrace: None })), Expected(Borrowed(\"natural_number\"))]");
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let input = pos.with_spans();
|
2017-04-19 23:16:19 +00:00
|
|
|
let mut par = Query::natural_number();
|
2017-05-04 20:40:41 +00:00
|
|
|
assert_eq!(None, par.parse(input.atom_stream()).err());
|
2017-04-19 23:16:19 +00:00
|
|
|
}
|
2017-01-25 22:06:19 +00:00
|
|
|
}
|