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-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-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,
|
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-03-07 00:27:13 +00:00
|
|
|
pub limit: Option<u64>,
|
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-03-07 04:18:38 +00:00
|
|
|
/**
|
|
|
|
* Apply a new limit to this query, if one is provided and any existing limit is larger.
|
|
|
|
*/
|
2017-03-07 00:27:13 +00:00
|
|
|
pub fn apply_limit(&mut self, limit: Option<u64>) {
|
|
|
|
match self.limit {
|
|
|
|
None => self.limit = limit,
|
|
|
|
Some(existing) =>
|
|
|
|
match limit {
|
|
|
|
None => (),
|
|
|
|
Some(new) =>
|
|
|
|
if new < existing {
|
|
|
|
self.limit = limit;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
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-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-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-03-22 21:02:00 +00:00
|
|
|
let limit = if parsed.find_spec.is_unit_limited() { Some(1) } else { None };
|
2017-03-16 19:23:48 +00:00
|
|
|
Ok(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-03-16 19:23:48 +00:00
|
|
|
})
|
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
|
|
|
};
|
|
|
|
|