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.
|
|
|
|
|
2017-04-24 21:15:26 +00:00
|
|
|
extern crate enum_set;
|
|
|
|
|
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;
|
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::{
|
|
|
|
Schema,
|
2017-04-19 23:16:19 +00:00
|
|
|
TypedValue,
|
|
|
|
ValueType,
|
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::{
|
|
|
|
FindQuery,
|
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,
|
2017-02-03 23:52:02 +00:00
|
|
|
SrcVar,
|
2017-04-17 16:23:55 +00:00
|
|
|
Variable,
|
2017-02-03 23:52:02 +00:00
|
|
|
};
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
pub use errors::{
|
|
|
|
Error,
|
|
|
|
ErrorKind,
|
|
|
|
Result,
|
|
|
|
};
|
|
|
|
|
2017-04-17 20:14:30 +00:00
|
|
|
pub use clauses::{
|
|
|
|
QueryInputs,
|
|
|
|
};
|
|
|
|
|
2017-02-04 00:52:03 +00:00
|
|
|
#[allow(dead_code)]
|
|
|
|
pub struct AlgebraicQuery {
|
|
|
|
default_source: SrcVar,
|
2017-03-06 22:40:10 +00:00
|
|
|
pub find_spec: FindSpec,
|
2017-02-04 00:52:03 +00:00
|
|
|
has_aggregates: bool,
|
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>,
|
|
|
|
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
|
|
|
|
|
|
|
/// 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> {
|
|
|
|
self.cc.input_variables.sub(&self.cc.value_bound_variables())
|
|
|
|
}
|
2017-03-07 00:27:13 +00:00
|
|
|
}
|
|
|
|
|
2017-04-11 21:44:11 +00:00
|
|
|
pub fn algebrize_with_counter(schema: &Schema, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
|
2017-04-17 20:14:30 +00:00
|
|
|
algebrize_with_inputs(schema, parsed, counter, QueryInputs::default())
|
2017-04-11 21:44:11 +00:00
|
|
|
}
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
pub fn algebrize(schema: &Schema, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
2017-04-17 20:14:30 +00:00
|
|
|
algebrize_with_inputs(schema, 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)
|
|
|
|
}
|
|
|
|
|
2017-04-17 20:14:30 +00:00
|
|
|
pub fn algebrize_with_inputs(schema: &Schema,
|
|
|
|
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);
|
|
|
|
|
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.
|
|
|
|
let where_clauses = parsed.where_clauses;
|
|
|
|
for where_clause in where_clauses {
|
2017-03-24 20:09:32 +00:00
|
|
|
cc.apply_clause(schema, where_clause)?;
|
2017-02-22 03:55:27 +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();
|
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)?;
|
|
|
|
let with: BTreeSet<Variable> = parsed.with.into_iter().chain(extra_vars.into_iter()).collect();
|
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,
|
|
|
|
find_spec: parsed.find_spec,
|
|
|
|
has_aggregates: false, // TODO: we don't parse them yet.
|
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
|
|
|
with: with,
|
|
|
|
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,
|
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
|
|
|
};
|
|
|
|
|