2017-03-15 11:37:17 +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-07 23:24:57 +00:00
|
|
|
use std::collections::BTreeSet;
|
2017-03-15 11:37:17 +00:00
|
|
|
use std::fmt::{
|
|
|
|
Debug,
|
|
|
|
Formatter,
|
|
|
|
Result,
|
|
|
|
};
|
|
|
|
|
2017-04-24 21:15:26 +00:00
|
|
|
use enum_set::{
|
|
|
|
CLike,
|
|
|
|
EnumSet,
|
|
|
|
};
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
use mentat_core::{
|
|
|
|
Entid,
|
|
|
|
TypedValue,
|
|
|
|
ValueType,
|
|
|
|
};
|
2017-03-28 02:34:02 +00:00
|
|
|
|
|
|
|
use 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-03-28 02:34:02 +00:00
|
|
|
NamespacedKeyword,
|
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-28 02:34:02 +00:00
|
|
|
Variable,
|
|
|
|
};
|
|
|
|
|
2017-03-15 11:37:17 +00:00
|
|
|
/// This enum models the fixed set of default tables we have -- two
|
2017-04-07 23:24:57 +00:00
|
|
|
/// tables and two views -- and computed tables defined in the enclosing CC.
|
2017-03-15 11:37:17 +00:00
|
|
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
|
|
pub enum DatomsTable {
|
|
|
|
Datoms, // The non-fulltext datoms table.
|
|
|
|
FulltextValues, // The virtual table mapping IDs to strings.
|
|
|
|
FulltextDatoms, // The fulltext-datoms view.
|
|
|
|
AllDatoms, // Fulltext and non-fulltext datoms.
|
2017-04-07 23:24:57 +00:00
|
|
|
Computed(usize), // A computed table, tracked elsewhere in the query.
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A source of rows that isn't a named table -- typically a subquery or union.
|
2017-04-28 09:44:11 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2017-04-07 23:24:57 +00:00
|
|
|
pub enum ComputedTable {
|
2017-04-28 09:44:11 +00:00
|
|
|
Subquery(::clauses::ConjoiningClauses),
|
2017-04-07 23:24:57 +00:00
|
|
|
Union {
|
|
|
|
projection: BTreeSet<Variable>,
|
|
|
|
type_extraction: BTreeSet<Variable>,
|
|
|
|
arms: Vec<::clauses::ConjoiningClauses>,
|
|
|
|
},
|
2017-03-15 11:37:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DatomsTable {
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
|
|
match *self {
|
|
|
|
DatomsTable::Datoms => "datoms",
|
|
|
|
DatomsTable::FulltextValues => "fulltext_values",
|
|
|
|
DatomsTable::FulltextDatoms => "fulltext_datoms",
|
|
|
|
DatomsTable::AllDatoms => "all_datoms",
|
2017-04-07 23:24:57 +00:00
|
|
|
DatomsTable::Computed(_) => "c",
|
2017-03-15 11:37:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-08 00:23:41 +00:00
|
|
|
pub trait ColumnName {
|
|
|
|
fn column_name(&self) -> String;
|
|
|
|
}
|
|
|
|
|
2017-03-15 11:37:17 +00:00
|
|
|
/// One of the named columns of our tables.
|
2017-04-08 00:23:41 +00:00
|
|
|
#[derive(PartialEq, Eq, Clone)]
|
2017-03-15 11:37:17 +00:00
|
|
|
pub enum DatomsColumn {
|
|
|
|
Entity,
|
|
|
|
Attribute,
|
|
|
|
Value,
|
|
|
|
Tx,
|
|
|
|
ValueTypeTag,
|
|
|
|
}
|
|
|
|
|
2017-04-08 00:23:41 +00:00
|
|
|
#[derive(PartialEq, Eq, Clone)]
|
|
|
|
pub enum VariableColumn {
|
|
|
|
Variable(Variable),
|
|
|
|
VariableTypeTag(Variable),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Clone)]
|
|
|
|
pub enum Column {
|
|
|
|
Fixed(DatomsColumn),
|
|
|
|
Variable(VariableColumn),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<DatomsColumn> for Column {
|
|
|
|
fn from(from: DatomsColumn) -> Column {
|
|
|
|
Column::Fixed(from)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<VariableColumn> for Column {
|
|
|
|
fn from(from: VariableColumn) -> Column {
|
|
|
|
Column::Variable(from)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 11:37:17 +00:00
|
|
|
impl DatomsColumn {
|
|
|
|
pub fn as_str(&self) -> &'static str {
|
|
|
|
use self::DatomsColumn::*;
|
|
|
|
match *self {
|
|
|
|
Entity => "e",
|
|
|
|
Attribute => "a",
|
|
|
|
Value => "v",
|
|
|
|
Tx => "tx",
|
|
|
|
ValueTypeTag => "value_type_tag",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-08 00:23:41 +00:00
|
|
|
impl ColumnName for DatomsColumn {
|
|
|
|
fn column_name(&self) -> String {
|
|
|
|
self.as_str().to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ColumnName for VariableColumn {
|
|
|
|
fn column_name(&self) -> String {
|
|
|
|
match self {
|
|
|
|
&VariableColumn::Variable(ref v) => v.to_string(),
|
|
|
|
&VariableColumn::VariableTypeTag(ref v) => format!("{}_value_type_tag", v.as_str()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for VariableColumn {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
match self {
|
|
|
|
// These should agree with VariableColumn::column_name.
|
|
|
|
&VariableColumn::Variable(ref v) => write!(f, "{}", v.as_str()),
|
|
|
|
&VariableColumn::VariableTypeTag(ref v) => write!(f, "{}_value_type_tag", v.as_str()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for DatomsColumn {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
write!(f, "{}", self.as_str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for Column {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
match self {
|
|
|
|
&Column::Fixed(ref c) => c.fmt(f),
|
|
|
|
&Column::Variable(ref v) => v.fmt(f),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 11:37:17 +00:00
|
|
|
/// A specific instance of a table within a query. E.g., "datoms123".
|
|
|
|
pub type TableAlias = String;
|
|
|
|
|
|
|
|
/// The association between a table and its alias. E.g., AllDatoms, "all_datoms123".
|
|
|
|
#[derive(PartialEq, Eq, Clone)]
|
|
|
|
pub struct SourceAlias(pub DatomsTable, pub TableAlias);
|
|
|
|
|
|
|
|
impl Debug for SourceAlias {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
write!(f, "SourceAlias({:?}, {})", self.0, self.1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A particular column of a particular aliased table. E.g., "datoms123", Attribute.
|
|
|
|
#[derive(PartialEq, Eq, Clone)]
|
2017-04-08 00:23:41 +00:00
|
|
|
pub struct QualifiedAlias(pub TableAlias, pub Column);
|
2017-03-15 11:37:17 +00:00
|
|
|
|
|
|
|
impl Debug for QualifiedAlias {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
2017-04-08 00:23:41 +00:00
|
|
|
write!(f, "{}.{:?}", self.0, self.1)
|
2017-03-15 11:37:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QualifiedAlias {
|
2017-04-08 00:23:41 +00:00
|
|
|
pub fn new<C: Into<Column>>(table: TableAlias, column: C) -> Self {
|
|
|
|
QualifiedAlias(table, column.into())
|
|
|
|
}
|
|
|
|
|
2017-03-15 11:37:17 +00:00
|
|
|
pub fn for_type_tag(&self) -> QualifiedAlias {
|
2017-04-08 00:23:41 +00:00
|
|
|
// TODO: this only makes sense for `DatomsColumn` tables.
|
|
|
|
QualifiedAlias(self.0.clone(), Column::Fixed(DatomsColumn::ValueTypeTag))
|
2017-03-15 11:37:17 +00:00
|
|
|
}
|
2017-03-16 19:23:48 +00:00
|
|
|
}
|
|
|
|
|
2017-04-04 21:54:08 +00:00
|
|
|
#[derive(PartialEq, Eq, Clone)]
|
2017-03-16 19:23:48 +00:00
|
|
|
pub enum QueryValue {
|
|
|
|
Column(QualifiedAlias),
|
|
|
|
Entid(Entid),
|
|
|
|
TypedValue(TypedValue),
|
|
|
|
|
|
|
|
// This is different: a numeric value can only apply to the 'v' column, and it implicitly
|
|
|
|
// constrains the `value_type_tag` column. For instance, a primitive long on `datoms00` of `5`
|
|
|
|
// cannot be a boolean, so `datoms00.value_type_tag` must be in the set `#{0, 4, 5}`.
|
|
|
|
// Note that `5 = 5.0` in SQLite, and we preserve that here.
|
|
|
|
PrimitiveLong(i64),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for QueryValue {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
|
|
|
use self::QueryValue::*;
|
|
|
|
match self {
|
|
|
|
&Column(ref qa) => {
|
|
|
|
write!(f, "{:?}", qa)
|
|
|
|
},
|
|
|
|
&Entid(ref entid) => {
|
|
|
|
write!(f, "entity({:?})", entid)
|
|
|
|
},
|
|
|
|
&TypedValue(ref typed_value) => {
|
|
|
|
write!(f, "value({:?})", typed_value)
|
|
|
|
},
|
|
|
|
&PrimitiveLong(value) => {
|
|
|
|
write!(f, "primitive({:?})", value)
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
/// Represents an entry in the ORDER BY list: a variable or a variable's type tag.
|
|
|
|
/// (We require order vars to be projected, so we can simply use a variable here.)
|
|
|
|
pub struct OrderBy(pub Direction, pub VariableColumn);
|
|
|
|
|
|
|
|
impl From<Order> for OrderBy {
|
|
|
|
fn from(item: Order) -> OrderBy {
|
|
|
|
let Order(direction, variable) = item;
|
|
|
|
OrderBy(direction, VariableColumn::Variable(variable))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
|
|
/// Define the different numeric inequality operators that we support.
|
|
|
|
/// Note that we deliberately don't just use "<=" and friends as strings:
|
|
|
|
/// Datalog and SQL don't use the same operators (e.g., `<>` and `!=`).
|
|
|
|
pub enum NumericComparison {
|
|
|
|
LessThan,
|
|
|
|
LessThanOrEquals,
|
|
|
|
GreaterThan,
|
|
|
|
GreaterThanOrEquals,
|
|
|
|
NotEquals,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NumericComparison {
|
|
|
|
pub fn to_sql_operator(self) -> &'static str {
|
|
|
|
use self::NumericComparison::*;
|
|
|
|
match self {
|
|
|
|
LessThan => "<",
|
|
|
|
LessThanOrEquals => "<=",
|
|
|
|
GreaterThan => ">",
|
|
|
|
GreaterThanOrEquals => ">=",
|
|
|
|
NotEquals => "<>",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_datalog_operator(s: &str) -> Option<NumericComparison> {
|
|
|
|
match s {
|
|
|
|
"<" => Some(NumericComparison::LessThan),
|
|
|
|
"<=" => Some(NumericComparison::LessThanOrEquals),
|
|
|
|
">" => Some(NumericComparison::GreaterThan),
|
|
|
|
">=" => Some(NumericComparison::GreaterThanOrEquals),
|
|
|
|
"!=" => Some(NumericComparison::NotEquals),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for NumericComparison {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
|
|
|
use self::NumericComparison::*;
|
|
|
|
f.write_str(match self {
|
|
|
|
&LessThan => "<",
|
|
|
|
&LessThanOrEquals => "<=",
|
|
|
|
&GreaterThan => ">",
|
|
|
|
&GreaterThanOrEquals => ">=",
|
|
|
|
&NotEquals => "!=", // Datalog uses !=. SQL uses <>.
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
|
pub enum ColumnConstraint {
|
|
|
|
Equals(QualifiedAlias, QueryValue),
|
|
|
|
NumericInequality {
|
|
|
|
operator: NumericComparison,
|
|
|
|
left: QueryValue,
|
|
|
|
right: QueryValue,
|
|
|
|
},
|
|
|
|
HasType(TableAlias, ValueType),
|
2017-04-28 09:44:11 +00:00
|
|
|
NotExists(ComputedTable),
|
2017-03-16 19:23:48 +00:00
|
|
|
}
|
|
|
|
|
2017-03-28 02:35:39 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub enum ColumnConstraintOrAlternation {
|
|
|
|
Constraint(ColumnConstraint),
|
|
|
|
Alternation(ColumnAlternation),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<ColumnConstraint> for ColumnConstraintOrAlternation {
|
|
|
|
fn from(thing: ColumnConstraint) -> Self {
|
|
|
|
ColumnConstraintOrAlternation::Constraint(thing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A `ColumnIntersection` constraint is satisfied if all of its inner constraints are satisfied.
|
|
|
|
/// An empty intersection is always satisfied.
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
|
pub struct ColumnIntersection(pub Vec<ColumnConstraintOrAlternation>);
|
|
|
|
|
|
|
|
impl From<Vec<ColumnConstraint>> for ColumnIntersection {
|
|
|
|
fn from(thing: Vec<ColumnConstraint>) -> Self {
|
|
|
|
ColumnIntersection(thing.into_iter().map(|x| x.into()).collect())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ColumnIntersection {
|
|
|
|
fn default() -> Self {
|
|
|
|
ColumnIntersection(vec![])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IntoIterator for ColumnIntersection {
|
|
|
|
type Item = ColumnConstraintOrAlternation;
|
|
|
|
type IntoIter = ::std::vec::IntoIter<ColumnConstraintOrAlternation>;
|
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
self.0.into_iter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ColumnIntersection {
|
2017-04-04 21:54:08 +00:00
|
|
|
#[inline]
|
2017-03-28 02:35:39 +00:00
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.0.len()
|
|
|
|
}
|
|
|
|
|
2017-04-04 21:54:08 +00:00
|
|
|
#[inline]
|
2017-03-28 02:35:39 +00:00
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.0.is_empty()
|
|
|
|
}
|
|
|
|
|
2017-04-04 21:54:08 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn add(&mut self, constraint: ColumnConstraintOrAlternation) {
|
|
|
|
self.0.push(constraint);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-03-31 01:16:04 +00:00
|
|
|
pub fn add_intersection(&mut self, constraint: ColumnConstraint) {
|
2017-04-04 21:54:08 +00:00
|
|
|
self.add(ColumnConstraintOrAlternation::Constraint(constraint));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn append(&mut self, other: &mut Self) {
|
|
|
|
self.0.append(&mut other.0)
|
2017-03-28 02:35:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A `ColumnAlternation` constraint is satisfied if at least one of its inner constraints is
|
|
|
|
/// satisfied. An empty `ColumnAlternation` is never satisfied.
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub struct ColumnAlternation(pub Vec<ColumnIntersection>);
|
|
|
|
|
|
|
|
impl Default for ColumnAlternation {
|
|
|
|
fn default() -> Self {
|
|
|
|
ColumnAlternation(vec![])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IntoIterator for ColumnAlternation {
|
|
|
|
type Item = ColumnIntersection;
|
|
|
|
type IntoIter = ::std::vec::IntoIter<ColumnIntersection>;
|
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
self.0.into_iter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ColumnAlternation {
|
2017-03-31 01:16:04 +00:00
|
|
|
pub fn add_alternate(&mut self, intersection: ColumnIntersection) {
|
2017-03-28 02:35:39 +00:00
|
|
|
self.0.push(intersection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for ColumnIntersection {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
|
|
|
write!(f, "{:?}", self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
impl Debug for ColumnConstraint {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
|
|
|
use self::ColumnConstraint::*;
|
|
|
|
match self {
|
|
|
|
&Equals(ref qa1, ref thing) => {
|
|
|
|
write!(f, "{:?} = {:?}", qa1, thing)
|
|
|
|
},
|
|
|
|
|
|
|
|
&NumericInequality { operator, ref left, ref right } => {
|
|
|
|
write!(f, "{:?} {:?} {:?}", left, operator, right)
|
|
|
|
},
|
|
|
|
|
|
|
|
&HasType(ref qa, value_type) => {
|
|
|
|
write!(f, "{:?}.value_type_tag = {:?}", qa, value_type)
|
|
|
|
},
|
2017-04-28 09:44:11 +00:00
|
|
|
&NotExists(ref ct) => {
|
|
|
|
write!(f, "NOT EXISTS {:?}", ct)
|
|
|
|
},
|
2017-03-16 19:23:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-28 02:34:02 +00:00
|
|
|
|
2017-03-28 03:36:03 +00:00
|
|
|
#[derive(PartialEq, Clone)]
|
2017-03-28 02:34:02 +00:00
|
|
|
pub enum EmptyBecause {
|
|
|
|
// Var, existing, desired.
|
2017-04-24 21:15:26 +00:00
|
|
|
TypeMismatch(Variable, ValueTypeSet, ValueType),
|
2017-04-04 21:54:08 +00:00
|
|
|
NoValidTypes(Variable),
|
2017-03-28 02:34:02 +00:00
|
|
|
NonNumericArgument,
|
2017-03-28 03:36:03 +00:00
|
|
|
NonStringFulltextValue,
|
2017-03-28 02:34:02 +00:00
|
|
|
UnresolvedIdent(NamespacedKeyword),
|
|
|
|
InvalidAttributeIdent(NamespacedKeyword),
|
|
|
|
InvalidAttributeEntid(Entid),
|
2017-04-08 00:23:41 +00:00
|
|
|
InvalidBinding(Column, TypedValue),
|
2017-03-28 02:34:02 +00:00
|
|
|
ValueTypeMismatch(ValueType, TypedValue),
|
|
|
|
AttributeLookupFailed, // Catch-all, because the table lookup code is lazy. TODO
|
2017-03-28 03:34:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for EmptyBecause {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
|
|
|
use self::EmptyBecause::*;
|
|
|
|
match self {
|
|
|
|
&TypeMismatch(ref var, ref existing, ref desired) => {
|
|
|
|
write!(f, "Type mismatch: {:?} can't be {:?}, because it's already {:?}",
|
|
|
|
var, desired, existing)
|
|
|
|
},
|
2017-04-04 21:54:08 +00:00
|
|
|
&NoValidTypes(ref var) => {
|
|
|
|
write!(f, "Type mismatch: {:?} has no valid types", var)
|
|
|
|
},
|
2017-03-28 03:34:56 +00:00
|
|
|
&NonNumericArgument => {
|
|
|
|
write!(f, "Non-numeric argument in numeric place")
|
|
|
|
},
|
|
|
|
&NonStringFulltextValue => {
|
|
|
|
write!(f, "Non-string argument for fulltext attribute")
|
|
|
|
},
|
|
|
|
&UnresolvedIdent(ref kw) => {
|
|
|
|
write!(f, "Couldn't resolve keyword {}", kw)
|
|
|
|
},
|
|
|
|
&InvalidAttributeIdent(ref kw) => {
|
|
|
|
write!(f, "{} does not name an attribute", kw)
|
|
|
|
},
|
|
|
|
&InvalidAttributeEntid(entid) => {
|
|
|
|
write!(f, "{} is not an attribute", entid)
|
|
|
|
},
|
|
|
|
&InvalidBinding(ref column, ref tv) => {
|
|
|
|
write!(f, "{:?} cannot name column {:?}", tv, column)
|
|
|
|
},
|
|
|
|
&ValueTypeMismatch(value_type, ref typed_value) => {
|
|
|
|
write!(f, "Type mismatch: {:?} doesn't match attribute type {:?}",
|
|
|
|
typed_value, value_type)
|
|
|
|
},
|
|
|
|
&AttributeLookupFailed => {
|
|
|
|
write!(f, "Attribute lookup failed")
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2017-04-24 21:15:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
trait EnumSetExtensions<T: CLike + Clone> {
|
|
|
|
/// Return a set containing both `x` and `y`.
|
|
|
|
fn of_both(x: T, y: T) -> EnumSet<T>;
|
|
|
|
|
|
|
|
/// Return a clone of `self` with `y` added.
|
|
|
|
fn with(&self, y: T) -> EnumSet<T>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: CLike + Clone> EnumSetExtensions<T> for EnumSet<T> {
|
|
|
|
/// Return a set containing both `x` and `y`.
|
|
|
|
fn of_both(x: T, y: T) -> Self {
|
|
|
|
let mut o = EnumSet::new();
|
|
|
|
o.insert(x);
|
|
|
|
o.insert(y);
|
|
|
|
o
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a clone of `self` with `y` added.
|
|
|
|
fn with(&self, y: T) -> EnumSet<T> {
|
|
|
|
let mut o = self.clone();
|
|
|
|
o.insert(y);
|
|
|
|
o
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct ValueTypeSet(pub EnumSet<ValueType>);
|
|
|
|
|
|
|
|
impl Default for ValueTypeSet {
|
|
|
|
fn default() -> ValueTypeSet {
|
|
|
|
ValueTypeSet::any()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ValueTypeSet {
|
|
|
|
pub fn any() -> ValueTypeSet {
|
|
|
|
ValueTypeSet(ValueType::all_enums())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn none() -> ValueTypeSet {
|
|
|
|
ValueTypeSet(EnumSet::new())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a set containing only `t`.
|
|
|
|
pub fn of_one(t: ValueType) -> ValueTypeSet {
|
|
|
|
let mut s = EnumSet::new();
|
|
|
|
s.insert(t);
|
|
|
|
ValueTypeSet(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a set containing `Double` and `Long`.
|
|
|
|
pub fn of_numeric_types() -> ValueTypeSet {
|
|
|
|
ValueTypeSet(EnumSet::of_both(ValueType::Double, ValueType::Long))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ValueTypeSet {
|
|
|
|
/// Returns a set containing all the types in this set and `other`.
|
|
|
|
pub fn union(&self, other: &ValueTypeSet) -> ValueTypeSet {
|
|
|
|
ValueTypeSet(self.0.union(other.0))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn intersection(&self, other: &ValueTypeSet) -> ValueTypeSet {
|
|
|
|
ValueTypeSet(self.0.intersection(other.0))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return an arbitrary type that's part of this set.
|
|
|
|
/// For a set containing a single type, this will be that type.
|
|
|
|
pub fn exemplar(&self) -> Option<ValueType> {
|
|
|
|
self.0.iter().next()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn contains(&self, vt: ValueType) -> bool {
|
|
|
|
self.0.contains(&vt)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.0.is_empty()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_unit(&self) -> bool {
|
|
|
|
self.0.len() == 1
|
|
|
|
}
|
2017-03-28 02:34:02 +00:00
|
|
|
}
|