2017-02-03 23:52:02 +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.
|
|
|
|
|
2018-03-12 22:18:50 +00:00
|
|
|
#![recursion_limit="128"]
|
|
|
|
|
2017-03-16 19:16:56 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate error_chain;
|
|
|
|
|
2017-02-22 03:55:27 +00:00
|
|
|
extern crate mentat_core;
|
2017-02-03 23:52:02 +00:00
|
|
|
extern crate mentat_query;
|
2017-02-04 00:52:03 +00:00
|
|
|
|
2017-04-17 16:23:55 +00:00
|
|
|
use std::collections::BTreeSet;
|
2017-04-17 20:14:30 +00:00
|
|
|
use std::ops::Sub;
|
2018-01-30 22:11:41 +00:00
|
|
|
use std::rc::Rc;
|
2017-04-17 16:23:55 +00:00
|
|
|
|
2017-03-16 19:16:56 +00:00
|
|
|
mod errors;
|
2017-03-15 11:37:17 +00:00
|
|
|
mod types;
|
2017-03-24 20:09:32 +00:00
|
|
|
mod validate;
|
2017-03-28 03:34:56 +00:00
|
|
|
mod clauses;
|
|
|
|
|
2017-02-22 03:55:27 +00:00
|
|
|
use mentat_core::{
|
2018-02-14 00:51:21 +00:00
|
|
|
CachedAttributes,
|
|
|
|
Entid,
|
2017-02-22 03:55:27 +00:00
|
|
|
Schema,
|
2017-04-19 23:16:19 +00:00
|
|
|
TypedValue,
|
|
|
|
ValueType,
|
2018-05-31 21:10:49 +00:00
|
|
|
parse_query,
|
2017-02-22 03:55:27 +00:00
|
|
|
};
|
|
|
|
|
2017-04-11 21:44:11 +00:00
|
|
|
use mentat_core::counter::RcCounter;
|
|
|
|
|
2017-02-03 23:52:02 +00:00
|
|
|
use mentat_query::{
|
2018-03-06 17:01:20 +00:00
|
|
|
Element,
|
2017-02-04 00:52:03 +00:00
|
|
|
FindSpec,
|
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,
|
2018-05-31 21:10:49 +00:00
|
|
|
ParsedFindQuery,
|
2017-02-03 23:52:02 +00:00
|
|
|
SrcVar,
|
2017-04-17 16:23:55 +00:00
|
|
|
Variable,
|
2018-05-31 21:10:49 +00:00
|
|
|
WhereClause,
|
2017-02-03 23:52:02 +00:00
|
|
|
};
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
pub use errors::{
|
2017-04-26 22:50:17 +00:00
|
|
|
BindingError,
|
2017-03-16 19:23:48 +00:00
|
|
|
Error,
|
|
|
|
ErrorKind,
|
|
|
|
Result,
|
|
|
|
};
|
|
|
|
|
2017-04-17 20:14:30 +00:00
|
|
|
pub use clauses::{
|
|
|
|
QueryInputs,
|
2018-03-06 17:01:20 +00:00
|
|
|
VariableBindings,
|
2017-04-17 20:14:30 +00:00
|
|
|
};
|
|
|
|
|
2017-06-14 23:17:25 +00:00
|
|
|
pub use types::{
|
|
|
|
EmptyBecause,
|
2018-05-31 21:10:49 +00:00
|
|
|
FindQuery,
|
2017-06-14 23:17:25 +00:00
|
|
|
};
|
|
|
|
|
2018-02-14 00:51:21 +00:00
|
|
|
/// A convenience wrapper around things known in memory: the schema and caches.
|
|
|
|
/// We use a trait object here to avoid making dozens of functions generic over the type
|
|
|
|
/// of the cache. If performance becomes a concern, we should hard-code specific kinds of
|
|
|
|
/// cache right here, and/or eliminate the Option.
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
pub struct Known<'s, 'c> {
|
|
|
|
pub schema: &'s Schema,
|
|
|
|
pub cache: Option<&'c CachedAttributes>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'s, 'c> Known<'s, 'c> {
|
|
|
|
pub fn for_schema(s: &'s Schema) -> Known<'s, 'static> {
|
|
|
|
Known {
|
|
|
|
schema: s,
|
|
|
|
cache: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new(s: &'s Schema, c: Option<&'c CachedAttributes>) -> Known<'s, 'c> {
|
|
|
|
Known {
|
|
|
|
schema: s,
|
|
|
|
cache: c,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This is `CachedAttributes`, but with handy generic parameters.
|
|
|
|
/// Why not make the trait generic? Because then we can't use it as a trait object in `Known`.
|
|
|
|
impl<'s, 'c> Known<'s, 'c> {
|
|
|
|
pub fn is_attribute_cached_reverse<U>(&self, entid: U) -> bool where U: Into<Entid> {
|
|
|
|
self.cache
|
|
|
|
.map(|cache| cache.is_attribute_cached_reverse(entid.into()))
|
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool where U: Into<Entid> {
|
|
|
|
self.cache
|
|
|
|
.map(|cache| cache.is_attribute_cached_forward(entid.into()))
|
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_values_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&Vec<TypedValue>>
|
|
|
|
where U: Into<Entid>, V: Into<Entid> {
|
|
|
|
self.cache.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_value_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&TypedValue>
|
|
|
|
where U: Into<Entid>, V: Into<Entid> {
|
|
|
|
self.cache.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_entid_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<Entid>
|
|
|
|
where U: Into<Entid> {
|
|
|
|
self.cache.and_then(|cache| cache.get_entid_for_value(attribute.into(), value))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_entids_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<&BTreeSet<Entid>>
|
|
|
|
where U: Into<Entid> {
|
|
|
|
self.cache.and_then(|cache| cache.get_entids_for_value(attribute.into(), value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-06 16:24:36 +00:00
|
|
|
#[derive(Debug)]
|
2017-02-04 00:52:03 +00:00
|
|
|
pub struct AlgebraicQuery {
|
|
|
|
default_source: SrcVar,
|
2018-01-30 22:11:41 +00:00
|
|
|
pub find_spec: Rc<FindSpec>,
|
2017-02-04 00:52:03 +00:00
|
|
|
has_aggregates: bool,
|
2018-03-12 22:18:50 +00:00
|
|
|
|
|
|
|
/// The set of variables that the caller wishes to be used for grouping when aggregating.
|
|
|
|
/// These are specified in the query input, as `:with`, and are then chewed up during projection.
|
|
|
|
/// If no variables are supplied, then no additional grouping is necessary beyond the
|
|
|
|
/// non-aggregated projection list.
|
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
|
|
|
pub with: BTreeSet<Variable>,
|
2018-03-12 22:18:50 +00:00
|
|
|
|
|
|
|
/// Some query features, such as ordering, are implemented by implicit reference to SQL columns.
|
|
|
|
/// In order for these references to be 'live', those columns must be projected.
|
|
|
|
/// This is the set of variables that must be so projected.
|
|
|
|
/// This is not necessarily every variable that will be so required -- some variables
|
|
|
|
/// will already be in the projection list.
|
|
|
|
pub named_projection: BTreeSet<Variable>,
|
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
|
|
|
pub order: Option<Vec<OrderBy>>,
|
2017-04-19 23:16:19 +00:00
|
|
|
pub limit: Limit,
|
2017-03-28 03:34:56 +00:00
|
|
|
pub cc: clauses::ConjoiningClauses,
|
2017-02-03 23:52:02 +00:00
|
|
|
}
|
|
|
|
|
2017-03-07 00:27:13 +00:00
|
|
|
impl AlgebraicQuery {
|
2017-04-06 00:20:13 +00:00
|
|
|
#[inline]
|
2017-03-07 04:18:38 +00:00
|
|
|
pub fn is_known_empty(&self) -> bool {
|
2017-04-06 00:20:13 +00:00
|
|
|
self.cc.is_known_empty()
|
2017-03-07 04:18:38 +00:00
|
|
|
}
|
2017-04-17 20:14:30 +00:00
|
|
|
|
2018-03-06 17:01:20 +00:00
|
|
|
/// Return true if every variable in the find spec is fully bound to a single value.
|
|
|
|
pub fn is_fully_bound(&self) -> bool {
|
|
|
|
self.find_spec
|
|
|
|
.columns()
|
|
|
|
.all(|e| match e {
|
2018-05-04 19:56:00 +00:00
|
|
|
// Pull expressions are never fully bound.
|
|
|
|
// TODO: but the 'inside' of a pull expression certainly can be.
|
|
|
|
&Element::Pull(_) => false,
|
|
|
|
|
2018-03-12 22:18:50 +00:00
|
|
|
&Element::Variable(ref var) |
|
|
|
|
&Element::Corresponding(ref var) => self.cc.is_value_bound(var),
|
|
|
|
|
|
|
|
// For now, we pretend that aggregate functions are never fully bound:
|
|
|
|
// we don't statically compute them, even if we know the value of the var.
|
|
|
|
&Element::Aggregate(ref _fn) => false,
|
2018-03-06 17:01:20 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return true if every variable in the find spec is fully bound to a single value,
|
|
|
|
/// and evaluating the query doesn't require running SQL.
|
|
|
|
pub fn is_fully_unit_bound(&self) -> bool {
|
|
|
|
self.cc.wheres.is_empty() &&
|
|
|
|
self.is_fully_bound()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-04-17 20:14:30 +00:00
|
|
|
/// Return a set of the input variables mentioned in the `:in` clause that have not yet been
|
|
|
|
/// bound. We do this by looking at the CC.
|
|
|
|
pub fn unbound_variables(&self) -> BTreeSet<Variable> {
|
2017-11-30 23:02:07 +00:00
|
|
|
self.cc.input_variables.sub(&self.cc.value_bound_variable_set())
|
2017-04-17 20:14:30 +00:00
|
|
|
}
|
2017-03-07 00:27:13 +00:00
|
|
|
}
|
|
|
|
|
2018-02-14 00:51:21 +00:00
|
|
|
pub fn algebrize_with_counter(known: Known, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
|
|
|
|
algebrize_with_inputs(known, parsed, counter, QueryInputs::default())
|
2017-04-11 21:44:11 +00:00
|
|
|
}
|
|
|
|
|
2018-02-14 00:51:21 +00:00
|
|
|
pub fn algebrize(known: Known, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
|
|
|
algebrize_with_inputs(known, parsed, 0, QueryInputs::default())
|
2017-04-11 21:44:11 +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
|
|
|
/// Take an ordering list. Any variables that aren't fixed by the query are used to produce
|
|
|
|
/// a vector of `OrderBy` instances, including type comparisons if necessary. This function also
|
|
|
|
/// returns a set of variables that should be added to the `with` clause to make the ordering
|
|
|
|
/// clauses possible.
|
|
|
|
fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>)
|
|
|
|
-> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> {
|
|
|
|
match order {
|
|
|
|
None => Ok((None, BTreeSet::default())),
|
|
|
|
Some(order) => {
|
|
|
|
let mut order_bys: Vec<OrderBy> = Vec::with_capacity(order.len() * 2); // Space for tags.
|
|
|
|
let mut vars: BTreeSet<Variable> = BTreeSet::default();
|
|
|
|
|
|
|
|
for Order(direction, var) in order.into_iter() {
|
|
|
|
// Eliminate any ordering clauses that are bound to fixed values.
|
|
|
|
if cc.bound_value(&var).is_some() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fail if the var isn't bound by the query.
|
|
|
|
if !cc.column_bindings.contains_key(&var) {
|
|
|
|
bail!(ErrorKind::UnboundVariable(var.name()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, determine if we also need to order by type…
|
|
|
|
if cc.known_type(&var).is_none() {
|
|
|
|
order_bys.push(OrderBy(direction.clone(), VariableColumn::VariableTypeTag(var.clone())));
|
|
|
|
}
|
|
|
|
order_bys.push(OrderBy(direction, VariableColumn::Variable(var.clone())));
|
|
|
|
vars.insert(var.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((if order_bys.is_empty() { None } else { Some(order_bys) }, vars))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 23:16:19 +00:00
|
|
|
|
|
|
|
fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
|
|
|
|
// Unpack any limit variables in place.
|
|
|
|
let refined_limit =
|
|
|
|
match query.limit {
|
|
|
|
Limit::Variable(ref v) => {
|
|
|
|
match query.cc.bound_value(v) {
|
|
|
|
Some(TypedValue::Long(n)) => {
|
|
|
|
if n <= 0 {
|
|
|
|
// User-specified limits should always be natural numbers (> 0).
|
|
|
|
bail!(ErrorKind::InvalidLimit(n.to_string(), ValueType::Long));
|
|
|
|
} else {
|
|
|
|
Some(Limit::Fixed(n as u64))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Some(val) => {
|
|
|
|
// Same.
|
|
|
|
bail!(ErrorKind::InvalidLimit(format!("{:?}", val), val.value_type()));
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
// We know that the limit variable is mentioned in `:in`.
|
|
|
|
// That it's not bound here implies that we haven't got all the variables
|
|
|
|
// we'll need to run the query yet.
|
|
|
|
// (We should never hit this in `q_once`.)
|
|
|
|
// Simply pass the `Limit` through to `SelectQuery` untouched.
|
|
|
|
None
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Limit::None => None,
|
|
|
|
Limit::Fixed(_) => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(lim) = refined_limit {
|
|
|
|
query.limit = lim;
|
|
|
|
}
|
|
|
|
Ok(query)
|
|
|
|
}
|
|
|
|
|
2018-02-14 00:51:21 +00:00
|
|
|
pub fn algebrize_with_inputs(known: Known,
|
2017-04-17 20:14:30 +00:00
|
|
|
parsed: FindQuery,
|
|
|
|
counter: usize,
|
|
|
|
inputs: QueryInputs) -> Result<AlgebraicQuery> {
|
|
|
|
let alias_counter = RcCounter::with_initial(counter);
|
|
|
|
let mut cc = ConjoiningClauses::with_inputs_and_alias_counter(parsed.in_vars, inputs, alias_counter);
|
|
|
|
|
2018-05-04 19:56:00 +00:00
|
|
|
// This is so the rest of the query knows that `?x` is a ref if `(pull ?x …)` appears in `:find`.
|
|
|
|
cc.derive_types_from_find_spec(&parsed.find_spec);
|
|
|
|
|
2017-04-19 23:16:19 +00:00
|
|
|
// Do we have a variable limit? If so, tell the CC that the var must be numeric.
|
|
|
|
if let &Limit::Variable(ref var) = &parsed.limit {
|
|
|
|
cc.constrain_var_to_long(var.clone());
|
|
|
|
}
|
|
|
|
|
2017-02-22 03:55:27 +00:00
|
|
|
// TODO: integrate default source into pattern processing.
|
|
|
|
// TODO: flesh out the rest of find-into-context.
|
2018-02-14 00:51:21 +00:00
|
|
|
cc.apply_clauses(known, parsed.where_clauses)?;
|
2018-01-29 22:29:16 +00:00
|
|
|
|
2017-04-04 21:54:08 +00:00
|
|
|
cc.expand_column_bindings();
|
2017-04-05 20:08:01 +00:00
|
|
|
cc.prune_extracted_types();
|
2018-01-29 22:29:16 +00:00
|
|
|
cc.process_required_types()?;
|
2017-02-22 03:55:27 +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
|
|
|
let (order, extra_vars) = validate_and_simplify_order(&cc, parsed.order)?;
|
2017-04-19 23:16:19 +00:00
|
|
|
|
|
|
|
// This might leave us with an unused `:in` variable.
|
|
|
|
let limit = if parsed.find_spec.is_unit_limited() { Limit::Fixed(1) } else { parsed.limit };
|
|
|
|
let q = AlgebraicQuery {
|
2017-02-04 00:52:03 +00:00
|
|
|
default_source: parsed.default_source,
|
2018-01-30 22:11:41 +00:00
|
|
|
find_spec: Rc::new(parsed.find_spec),
|
2017-02-04 00:52:03 +00:00
|
|
|
has_aggregates: false, // TODO: we don't parse them yet.
|
2018-03-12 22:18:50 +00:00
|
|
|
with: parsed.with,
|
|
|
|
named_projection: extra_vars,
|
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,
|
2017-03-22 21:02:00 +00:00
|
|
|
limit: limit,
|
2017-02-22 03:55:27 +00:00
|
|
|
cc: cc,
|
2017-04-19 23:16:19 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Substitute in any fixed values and fail if they're out of range.
|
|
|
|
simplify_limit(q)
|
2017-02-03 23:52:02 +00:00
|
|
|
}
|
2017-02-16 23:38:53 +00:00
|
|
|
|
2017-03-28 03:34:56 +00:00
|
|
|
pub use clauses::{
|
2017-02-16 23:38:53 +00:00
|
|
|
ConjoiningClauses,
|
|
|
|
};
|
|
|
|
|
2017-03-15 11:37:17 +00:00
|
|
|
pub use types::{
|
2017-04-08 00:23:41 +00:00
|
|
|
Column,
|
2017-03-28 02:35:39 +00:00
|
|
|
ColumnAlternation,
|
2017-03-16 19:23:48 +00:00
|
|
|
ColumnConstraint,
|
2017-03-28 02:35:39 +00:00
|
|
|
ColumnConstraintOrAlternation,
|
|
|
|
ColumnIntersection,
|
2017-04-08 00:23:41 +00:00
|
|
|
ColumnName,
|
2017-04-07 23:24:57 +00:00
|
|
|
ComputedTable,
|
2017-02-16 23:38:53 +00:00
|
|
|
DatomsColumn,
|
|
|
|
DatomsTable,
|
2017-06-12 21:24:56 +00:00
|
|
|
FulltextColumn,
|
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
|
|
|
OrderBy,
|
2017-02-16 23:38:53 +00:00
|
|
|
QualifiedAlias,
|
2017-03-16 19:23:48 +00:00
|
|
|
QueryValue,
|
2017-02-16 23:38:53 +00:00
|
|
|
SourceAlias,
|
|
|
|
TableAlias,
|
2017-04-08 00:23:41 +00:00
|
|
|
VariableColumn,
|
2017-02-16 23:38:53 +00:00
|
|
|
};
|
2018-05-31 21:10:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
impl FindQuery {
|
|
|
|
pub fn simple(spec: FindSpec, where_clauses: Vec<WhereClause>) -> FindQuery {
|
|
|
|
FindQuery {
|
|
|
|
find_spec: spec,
|
|
|
|
default_source: SrcVar::DefaultSrc,
|
|
|
|
with: BTreeSet::default(),
|
|
|
|
in_vars: BTreeSet::default(),
|
|
|
|
in_sources: BTreeSet::default(),
|
|
|
|
limit: Limit::None,
|
|
|
|
where_clauses: where_clauses,
|
|
|
|
order: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_parsed_find_query(parsed: ParsedFindQuery) -> Result<FindQuery> {
|
|
|
|
let in_vars = {
|
|
|
|
let mut set: BTreeSet<Variable> = BTreeSet::default();
|
|
|
|
|
|
|
|
for var in parsed.in_vars.into_iter() {
|
|
|
|
if !set.insert(var.clone()) {
|
|
|
|
bail!(ErrorKind::DuplicateVariableError(var.name(), ":in"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set
|
|
|
|
};
|
|
|
|
|
|
|
|
let with = {
|
|
|
|
let mut set: BTreeSet<Variable> = BTreeSet::default();
|
|
|
|
|
|
|
|
for var in parsed.with.into_iter() {
|
|
|
|
if !set.insert(var.clone()) {
|
|
|
|
bail!(ErrorKind::DuplicateVariableError(var.name(), ":with"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set
|
|
|
|
};
|
|
|
|
|
|
|
|
// Make sure that if we have `:limit ?x`, `?x` appears in `:in`.
|
|
|
|
if let Limit::Variable(ref v) = parsed.limit {
|
|
|
|
if !in_vars.contains(v) {
|
|
|
|
bail!(ErrorKind::UnknownLimitVar(v.name()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(FindQuery {
|
|
|
|
find_spec: parsed.find_spec,
|
|
|
|
default_source: parsed.default_source,
|
|
|
|
with,
|
|
|
|
in_vars,
|
|
|
|
in_sources: parsed.in_sources,
|
|
|
|
limit: parsed.limit,
|
|
|
|
where_clauses: parsed.where_clauses,
|
|
|
|
order: parsed.order,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_find_string(string: &str) -> Result<FindQuery> {
|
|
|
|
parse_query(string)
|
|
|
|
// .and_then(
|
|
|
|
.map_err(|e| e.into())
|
|
|
|
.and_then(|parsed| FindQuery::from_parsed_find_query(parsed)) // .map_err(|e| e.into()))
|
|
|
|
}
|