2017-02-22 03:57:00 +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.
|
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
use core_traits::{TypedValue, ValueType, ValueTypeSet};
|
2018-08-08 17:36:41 +00:00
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
use mentat_core::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet, Schema, ValueTypeTag};
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
use mentat_core::util::Either;
|
2018-03-06 17:01:20 +00:00
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
use edn::query::Limit;
|
2017-04-19 23:16:19 +00:00
|
|
|
|
|
2017-02-22 03:57:00 +00:00
|
|
|
|
use mentat_query_algebrizer::{
|
2020-01-14 15:46:21 +00:00
|
|
|
|
AlgebraicQuery, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation,
|
|
|
|
|
ColumnIntersection, ColumnName, ComputedTable, ConjoiningClauses, DatomsColumn, DatomsTable,
|
|
|
|
|
OrderBy, QualifiedAlias, QueryValue, SourceAlias, TableAlias, VariableColumn,
|
2017-02-22 03:57:00 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
use {
|
|
|
|
|
projected_column_for_var, query_projection, CombinedProjection, ConstantProjector, Projector,
|
2017-03-06 22:40:10 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use mentat_query_sql::{
|
2020-01-14 15:46:21 +00:00
|
|
|
|
ColumnOrExpression, Constraint, FromClause, GroupBy, Op, ProjectedColumn, Projection,
|
|
|
|
|
SelectQuery, TableList, TableOrSubquery, Values,
|
2017-02-22 03:57:00 +00:00
|
|
|
|
};
|
|
|
|
|
|
2018-01-29 22:29:16 +00:00
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
2017-11-30 23:02:07 +00:00
|
|
|
|
use super::Result;
|
|
|
|
|
|
2017-02-22 03:57:00 +00:00
|
|
|
|
trait ToConstraint {
|
|
|
|
|
fn to_constraint(self) -> Constraint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait ToColumn {
|
|
|
|
|
fn to_column(self) -> ColumnOrExpression;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToColumn for QualifiedAlias {
|
|
|
|
|
fn to_column(self) -> ColumnOrExpression {
|
|
|
|
|
ColumnOrExpression::Column(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-28 02:35:39 +00:00
|
|
|
|
impl ToConstraint for ColumnIntersection {
|
|
|
|
|
fn to_constraint(self) -> Constraint {
|
|
|
|
|
Constraint::And {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
constraints: self.into_iter().map(|x| x.to_constraint()).collect(),
|
2017-03-28 02:35:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToConstraint for ColumnAlternation {
|
|
|
|
|
fn to_constraint(self) -> Constraint {
|
|
|
|
|
Constraint::Or {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
constraints: self.into_iter().map(|x| x.to_constraint()).collect(),
|
2017-03-28 02:35:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToConstraint for ColumnConstraintOrAlternation {
|
|
|
|
|
fn to_constraint(self) -> Constraint {
|
|
|
|
|
use self::ColumnConstraintOrAlternation::*;
|
|
|
|
|
match self {
|
|
|
|
|
Alternation(alt) => alt.to_constraint(),
|
|
|
|
|
Constraint(c) => c.to_constraint(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-29 22:29:16 +00:00
|
|
|
|
fn affinity_count(tag: i32) -> usize {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
ValueTypeSet::any()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter(|t| t.value_type_tag() == tag)
|
|
|
|
|
.count()
|
2018-01-29 22:29:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
fn type_constraint(
|
|
|
|
|
table: &TableAlias,
|
|
|
|
|
tag: i32,
|
|
|
|
|
to_check: Option<Vec<SQLTypeAffinity>>,
|
|
|
|
|
) -> Constraint {
|
|
|
|
|
let type_column = QualifiedAlias::new(table.clone(), DatomsColumn::ValueTypeTag).to_column();
|
2018-01-29 22:29:16 +00:00
|
|
|
|
let check_type_tag = Constraint::equal(type_column, ColumnOrExpression::Integer(tag));
|
|
|
|
|
if let Some(affinities) = to_check {
|
|
|
|
|
let check_affinities = Constraint::Or {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
constraints: affinities
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|affinity| Constraint::TypeCheck {
|
|
|
|
|
value: QualifiedAlias::new(table.clone(), DatomsColumn::Value).to_column(),
|
2018-01-29 22:29:16 +00:00
|
|
|
|
affinity,
|
2020-01-14 15:46:21 +00:00
|
|
|
|
})
|
|
|
|
|
.collect(),
|
2018-01-29 22:29:16 +00:00
|
|
|
|
};
|
|
|
|
|
Constraint::And {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
constraints: vec![check_type_tag, check_affinities],
|
2018-01-29 22:29:16 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
check_type_tag
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns a map of tags to a vector of all the possible affinities that those tags can represent
|
|
|
|
|
// given the types in `value_types`.
|
|
|
|
|
fn possible_affinities(value_types: ValueTypeSet) -> HashMap<ValueTypeTag, Vec<SQLTypeAffinity>> {
|
|
|
|
|
let mut result = HashMap::with_capacity(value_types.len());
|
|
|
|
|
for ty in value_types {
|
|
|
|
|
let (tag, affinity_to_check) = ty.sql_representation();
|
2018-02-09 17:55:19 +00:00
|
|
|
|
let affinities = result.entry(tag).or_insert_with(Vec::new);
|
2018-01-29 22:29:16 +00:00
|
|
|
|
if let Some(affinity) = affinity_to_check {
|
|
|
|
|
affinities.push(affinity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-22 03:57:00 +00:00
|
|
|
|
impl ToConstraint for ColumnConstraint {
|
|
|
|
|
fn to_constraint(self) -> Constraint {
|
|
|
|
|
use self::ColumnConstraint::*;
|
|
|
|
|
match self {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Equals(qa, QueryValue::Entid(entid)) => {
|
|
|
|
|
Constraint::equal(qa.to_column(), ColumnOrExpression::Entid(entid))
|
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Equals(qa, QueryValue::TypedValue(tv)) => {
|
|
|
|
|
Constraint::equal(qa.to_column(), ColumnOrExpression::Value(tv))
|
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Equals(left, QueryValue::Column(right)) => {
|
|
|
|
|
Constraint::equal(left.to_column(), right.to_column())
|
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
2017-03-16 19:23:48 +00:00
|
|
|
|
Equals(qa, QueryValue::PrimitiveLong(value)) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
let tag_column = qa
|
|
|
|
|
.for_associated_type_tag()
|
|
|
|
|
.expect("an associated type tag alias")
|
|
|
|
|
.to_column();
|
2017-03-16 19:23:48 +00:00
|
|
|
|
let value_column = qa.to_column();
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
2017-11-21 16:24:08 +00:00
|
|
|
|
// A bare long in a query might match a ref, an instant, a long (obviously), or a
|
|
|
|
|
// double. If it's negative, it can't match a ref, but that's OK -- it won't!
|
|
|
|
|
//
|
|
|
|
|
// However, '1' and '0' are used to represent booleans, and some integers are also
|
|
|
|
|
// used to represent FTS values. We don't want to accidentally match those.
|
|
|
|
|
//
|
|
|
|
|
// We ask `SQLValueType` whether this value is in range for how booleans are
|
|
|
|
|
// represented in the database.
|
|
|
|
|
//
|
|
|
|
|
// We only hit this code path when the attribute is unknown, so we're querying
|
|
|
|
|
// `all_datoms`. That means we don't see FTS IDs at all -- they're transparently
|
|
|
|
|
// replaced by their strings. If that changes, then you should also exclude the
|
|
|
|
|
// string type code (10) here.
|
2017-03-07 04:18:38 +00:00
|
|
|
|
let must_exclude_boolean = ValueType::Boolean.accommodates_integer(value);
|
|
|
|
|
if must_exclude_boolean {
|
|
|
|
|
Constraint::And {
|
|
|
|
|
constraints: vec![
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Constraint::equal(
|
|
|
|
|
value_column,
|
|
|
|
|
ColumnOrExpression::Value(TypedValue::Long(value)),
|
|
|
|
|
),
|
|
|
|
|
Constraint::not_equal(
|
|
|
|
|
tag_column,
|
|
|
|
|
ColumnOrExpression::Integer(ValueType::Boolean.value_type_tag()),
|
|
|
|
|
),
|
2017-03-07 04:18:38 +00:00
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Constraint::equal(
|
|
|
|
|
value_column,
|
|
|
|
|
ColumnOrExpression::Value(TypedValue::Long(value)),
|
|
|
|
|
)
|
2017-03-07 04:18:38 +00:00
|
|
|
|
}
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2017-03-07 04:18:38 +00:00
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Inequality {
|
|
|
|
|
operator,
|
|
|
|
|
left,
|
|
|
|
|
right,
|
|
|
|
|
} => Constraint::Infix {
|
|
|
|
|
op: Op(operator.to_sql_operator()),
|
|
|
|
|
left: left.into(),
|
|
|
|
|
right: right.into(),
|
2017-06-12 21:24:56 +00:00
|
|
|
|
},
|
|
|
|
|
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Matches(left, right) => Constraint::Infix {
|
|
|
|
|
op: Op("MATCH"),
|
|
|
|
|
left: ColumnOrExpression::Column(left),
|
|
|
|
|
right: right.into(),
|
2017-03-16 19:23:48 +00:00
|
|
|
|
},
|
2020-01-14 15:46:21 +00:00
|
|
|
|
HasTypes {
|
|
|
|
|
value: table,
|
|
|
|
|
value_types,
|
|
|
|
|
check_value,
|
|
|
|
|
} => {
|
2018-01-29 22:29:16 +00:00
|
|
|
|
let constraints = if check_value {
|
|
|
|
|
possible_affinities(value_types)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(tag, affinities)| {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
let to_check = if affinities.is_empty()
|
|
|
|
|
|| affinities.len() == affinity_count(tag)
|
|
|
|
|
{
|
2018-01-29 22:29:16 +00:00
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(affinities)
|
|
|
|
|
};
|
|
|
|
|
type_constraint(&table, tag, to_check)
|
2020-01-14 15:46:21 +00:00
|
|
|
|
})
|
|
|
|
|
.collect()
|
2018-01-29 22:29:16 +00:00
|
|
|
|
} else {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
value_types
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|vt| type_constraint(&table, vt.value_type_tag(), None))
|
|
|
|
|
.collect()
|
2018-01-29 22:29:16 +00:00
|
|
|
|
};
|
|
|
|
|
Constraint::Or { constraints }
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2017-04-28 09:44:11 +00:00
|
|
|
|
|
|
|
|
|
NotExists(computed_table) => {
|
|
|
|
|
let subquery = table_for_computed(computed_table, TableAlias::new());
|
2020-02-21 15:27:39 +00:00
|
|
|
|
Constraint::NotExists { subquery }
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2017-02-22 03:57:00 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-06 17:01:20 +00:00
|
|
|
|
pub enum ProjectedSelect {
|
|
|
|
|
Constant(ConstantProjector),
|
|
|
|
|
Query {
|
2020-02-21 18:51:03 +00:00
|
|
|
|
query: Box<SelectQuery>,
|
2020-01-14 15:46:21 +00:00
|
|
|
|
projector: Box<dyn Projector>,
|
2018-03-06 17:01:20 +00:00
|
|
|
|
},
|
2017-03-06 22:40:10 +00:00
|
|
|
|
}
|
2017-02-22 03:57:00 +00:00
|
|
|
|
|
2017-04-08 00:23:41 +00:00
|
|
|
|
// Nasty little hack to let us move out of indexed context.
|
|
|
|
|
struct ConsumableVec<T> {
|
|
|
|
|
inner: Vec<Option<T>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> From<Vec<T>> for ConsumableVec<T> {
|
|
|
|
|
fn from(vec: Vec<T>) -> ConsumableVec<T> {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
ConsumableVec {
|
2020-02-21 15:27:39 +00:00
|
|
|
|
inner: vec.into_iter().map(Some).collect(),
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2017-04-08 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> ConsumableVec<T> {
|
|
|
|
|
fn take_dangerously(&mut self, i: usize) -> T {
|
|
|
|
|
::std::mem::replace(&mut self.inner[i], None).expect("each value to only be fetched once")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubquery {
|
|
|
|
|
match computed {
|
|
|
|
|
ComputedTable::Union {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
projection,
|
|
|
|
|
type_extraction,
|
|
|
|
|
arms,
|
2017-04-08 00:23:41 +00:00
|
|
|
|
} => {
|
|
|
|
|
// The projection list for each CC must have the same shape and the same names.
|
2017-04-11 17:31:31 +00:00
|
|
|
|
// The values we project might be fixed or they might be columns.
|
|
|
|
|
TableOrSubquery::Union(
|
|
|
|
|
arms.into_iter()
|
|
|
|
|
.map(|cc| {
|
|
|
|
|
// We're going to end up with the variables being projected and also some
|
|
|
|
|
// type tag columns.
|
|
|
|
|
let mut columns: Vec<ProjectedColumn> = Vec::with_capacity(projection.len() + type_extraction.len());
|
|
|
|
|
|
|
|
|
|
// For each variable, find out which column it maps to within this arm, and
|
|
|
|
|
// project it as the variable name.
|
|
|
|
|
// E.g., SELECT datoms03.v AS `?x`.
|
|
|
|
|
for var in projection.iter() {
|
2018-03-12 22:18:50 +00:00
|
|
|
|
// TODO: chain results out.
|
|
|
|
|
let (projected_column, type_set) = projected_column_for_var(var, &cc).expect("every var to be bound");
|
2017-06-07 17:59:25 +00:00
|
|
|
|
columns.push(projected_column);
|
|
|
|
|
|
|
|
|
|
// Similarly, project type tags if they're not known conclusively in the
|
|
|
|
|
// outer query.
|
|
|
|
|
// Assumption: we'll never need to project a tag without projecting the value of a variable.
|
|
|
|
|
if type_extraction.contains(var) {
|
|
|
|
|
let expression =
|
2018-03-12 22:18:50 +00:00
|
|
|
|
if let Some(tag) = type_set.unique_type_tag() {
|
2017-06-07 17:59:25 +00:00
|
|
|
|
// If we know the type for sure, just project the constant.
|
|
|
|
|
// SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag`
|
2018-03-12 22:18:50 +00:00
|
|
|
|
ColumnOrExpression::Integer(tag)
|
2017-06-07 17:59:25 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, we'll have an established type binding! This'll be
|
|
|
|
|
// either a datoms table or, recursively, a subquery. Project
|
|
|
|
|
// this:
|
|
|
|
|
// SELECT datoms03.v AS `?x`,
|
|
|
|
|
// datoms03.value_type_tag AS `?x_value_type_tag`
|
|
|
|
|
let extract = cc.extracted_types
|
|
|
|
|
.get(var)
|
2020-02-20 17:16:21 +00:00
|
|
|
|
.expect("Expected variable to have a known type, or an extracted type");
|
2017-06-07 17:59:25 +00:00
|
|
|
|
ColumnOrExpression::Column(extract.clone())
|
|
|
|
|
};
|
|
|
|
|
let type_column = VariableColumn::VariableTypeTag(var.clone());
|
|
|
|
|
let proj = ProjectedColumn(expression, type_column.column_name());
|
|
|
|
|
columns.push(proj);
|
|
|
|
|
}
|
2017-04-11 17:31:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Each arm simply turns into a subquery.
|
|
|
|
|
// The SQL translation will stuff "UNION" between each arm.
|
|
|
|
|
let projection = Projection::Columns(columns);
|
2018-03-12 22:18:50 +00:00
|
|
|
|
cc_to_select_query(projection, cc, false, vec![], None, Limit::None)
|
2017-04-11 17:31:31 +00:00
|
|
|
|
}).collect(),
|
|
|
|
|
alias)
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2017-04-28 09:44:11 +00:00
|
|
|
|
ComputedTable::Subquery(subquery) => {
|
2020-02-20 17:16:21 +00:00
|
|
|
|
TableOrSubquery::Subquery(Box::new(cc_to_exists(*subquery)))
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
|
|
|
|
ComputedTable::NamedValues { names, values } => {
|
2017-04-26 22:50:17 +00:00
|
|
|
|
// We assume column homogeneity, so we won't have any type tag columns.
|
|
|
|
|
TableOrSubquery::Values(Values::Named(names, values), alias)
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2017-04-08 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-06 17:01:20 +00:00
|
|
|
|
fn empty_query() -> SelectQuery {
|
|
|
|
|
SelectQuery {
|
|
|
|
|
distinct: false,
|
|
|
|
|
projection: Projection::One,
|
|
|
|
|
from: FromClause::Nothing,
|
2018-03-12 22:18:50 +00:00
|
|
|
|
group_by: vec![],
|
2018-03-06 17:01:20 +00:00
|
|
|
|
constraints: vec![],
|
|
|
|
|
order: vec![],
|
|
|
|
|
limit: Limit::None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-07 04:18:38 +00:00
|
|
|
|
/// Returns a `SelectQuery` that queries for the provided `cc`. Note that this _always_ returns a
|
|
|
|
|
/// query that runs SQL. The next level up the call stack can check for known-empty queries if
|
|
|
|
|
/// needed.
|
2020-01-14 15:46:21 +00:00
|
|
|
|
fn cc_to_select_query(
|
|
|
|
|
projection: Projection,
|
|
|
|
|
cc: ConjoiningClauses,
|
|
|
|
|
distinct: bool,
|
|
|
|
|
group_by: Vec<GroupBy>,
|
|
|
|
|
order: Option<Vec<OrderBy>>,
|
|
|
|
|
limit: Limit,
|
|
|
|
|
) -> SelectQuery {
|
2017-03-07 04:18:38 +00:00
|
|
|
|
let from = if cc.from.is_empty() {
|
|
|
|
|
FromClause::Nothing
|
|
|
|
|
} else {
|
2017-04-08 00:23:41 +00:00
|
|
|
|
// Move these out of the CC.
|
|
|
|
|
let from = cc.from;
|
|
|
|
|
let mut computed: ConsumableVec<_> = cc.computed_tables.into();
|
|
|
|
|
|
2017-04-11 17:31:31 +00:00
|
|
|
|
// Why do we put computed tables directly into the `FROM` clause? The alternative is to use
|
|
|
|
|
// a CTE (`WITH`). They're typically equivalent, but some SQL systems (notably Postgres)
|
|
|
|
|
// treat CTEs as optimization barriers, so a `WITH` can be significantly slower. Given that
|
|
|
|
|
// this is easy enough to change later, we'll opt for using direct inclusion in `FROM`.
|
2020-01-14 15:46:21 +00:00
|
|
|
|
let tables = from.into_iter().map(|source_alias| match source_alias {
|
|
|
|
|
SourceAlias(DatomsTable::Computed(i), alias) => {
|
|
|
|
|
let comp = computed.take_dangerously(i);
|
|
|
|
|
table_for_computed(comp, alias)
|
|
|
|
|
}
|
|
|
|
|
_ => TableOrSubquery::Table(source_alias),
|
|
|
|
|
});
|
2017-04-08 00:23:41 +00:00
|
|
|
|
|
|
|
|
|
FromClause::TableList(TableList(tables.collect()))
|
2017-03-07 04:18:38 +00:00
|
|
|
|
};
|
2017-03-22 21:02:00 +00:00
|
|
|
|
|
2020-02-21 15:27:39 +00:00
|
|
|
|
let order = order.map_or(vec![], |vec| vec.into_iter().map(|o| o).collect());
|
2020-01-14 15:46:21 +00:00
|
|
|
|
let limit = if cc.empty_because.is_some() {
|
|
|
|
|
Limit::Fixed(0)
|
|
|
|
|
} else {
|
|
|
|
|
limit
|
|
|
|
|
};
|
2017-02-22 03:57:00 +00:00
|
|
|
|
SelectQuery {
|
2020-02-21 15:27:39 +00:00
|
|
|
|
distinct,
|
|
|
|
|
projection,
|
|
|
|
|
from,
|
|
|
|
|
group_by,
|
2020-01-14 15:46:21 +00:00
|
|
|
|
constraints: cc.wheres.into_iter().map(|c| c.to_constraint()).collect(),
|
2020-02-21 15:27:39 +00:00
|
|
|
|
order,
|
|
|
|
|
limit,
|
2017-03-07 04:18:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return a query that projects `1` if the `cc` matches the store, and returns no results
|
|
|
|
|
/// if it doesn't.
|
|
|
|
|
pub fn cc_to_exists(cc: ConjoiningClauses) -> SelectQuery {
|
2017-04-06 00:20:13 +00:00
|
|
|
|
if cc.is_known_empty() {
|
2017-03-07 04:18:38 +00:00
|
|
|
|
// In this case we can produce a very simple query that returns no results.
|
2018-03-06 17:01:20 +00:00
|
|
|
|
empty_query()
|
2017-03-07 04:18:38 +00:00
|
|
|
|
} else {
|
2018-03-12 22:18:50 +00:00
|
|
|
|
cc_to_select_query(Projection::One, cc, false, vec![], None, Limit::None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Take a query and wrap it as a subquery of a new query with the provided projection list.
|
|
|
|
|
/// All limits, ordering, and grouping move to the outer query. The inner query is marked as
|
|
|
|
|
/// distinct.
|
|
|
|
|
fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery {
|
|
|
|
|
let outer_distinct = inner.distinct;
|
|
|
|
|
inner.distinct = true;
|
|
|
|
|
let group_by = inner.group_by;
|
|
|
|
|
inner.group_by = vec![];
|
|
|
|
|
let order_by = inner.order;
|
|
|
|
|
inner.order = vec![];
|
|
|
|
|
let limit = inner.limit;
|
|
|
|
|
inner.limit = Limit::None;
|
|
|
|
|
|
2018-06-01 21:17:31 +00:00
|
|
|
|
use self::Projection::*;
|
|
|
|
|
|
2020-02-21 15:27:39 +00:00
|
|
|
|
let nullable = match projection {
|
|
|
|
|
Columns(ref columns) => columns
|
2020-01-14 15:46:21 +00:00
|
|
|
|
.iter()
|
2020-02-21 15:27:39 +00:00
|
|
|
|
.filter_map(|pc| match *pc {
|
|
|
|
|
ProjectedColumn(ColumnOrExpression::NullableAggregate(_, _), ref name) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
Some(Constraint::IsNotNull {
|
|
|
|
|
value: ColumnOrExpression::ExistingColumn(name.clone()),
|
|
|
|
|
})
|
2018-06-01 21:17:31 +00:00
|
|
|
|
}
|
2020-01-14 15:46:21 +00:00
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
2020-02-21 15:27:39 +00:00
|
|
|
|
Star => vec![],
|
|
|
|
|
One => vec![],
|
2018-06-01 21:17:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if nullable.is_empty() {
|
|
|
|
|
return SelectQuery {
|
|
|
|
|
distinct: outer_distinct,
|
2020-02-21 15:27:39 +00:00
|
|
|
|
projection,
|
2020-01-14 15:46:21 +00:00
|
|
|
|
from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(
|
|
|
|
|
inner,
|
|
|
|
|
))])),
|
2018-06-01 21:17:31 +00:00
|
|
|
|
constraints: vec![],
|
2020-02-21 15:27:39 +00:00
|
|
|
|
group_by,
|
2018-06-01 21:17:31 +00:00
|
|
|
|
order: order_by,
|
2020-02-21 15:27:39 +00:00
|
|
|
|
limit,
|
2018-06-01 21:17:31 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Our pattern is `SELECT * FROM (SELECT ...) WHERE (nullable aggregate) IS NOT NULL`. If
|
|
|
|
|
// there's an `ORDER BY` in the subselect, SQL does not guarantee that the outer select will
|
|
|
|
|
// respect that order. But `ORDER BY` is relevant to the subselect when we have a `LIMIT`.
|
|
|
|
|
// Thus we lift the `ORDER BY` if there’s no `LIMIT` in the subselect, and repeat the `ORDER BY`
|
|
|
|
|
// if there is.
|
|
|
|
|
let subselect = SelectQuery {
|
2018-03-12 22:18:50 +00:00
|
|
|
|
distinct: outer_distinct,
|
2020-02-21 15:27:39 +00:00
|
|
|
|
projection,
|
2018-03-12 22:18:50 +00:00
|
|
|
|
from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(inner))])),
|
|
|
|
|
constraints: vec![],
|
2020-02-21 15:27:39 +00:00
|
|
|
|
group_by,
|
2018-06-01 21:17:31 +00:00
|
|
|
|
order: match &limit {
|
|
|
|
|
&Limit::None => vec![],
|
|
|
|
|
&Limit::Fixed(_) | &Limit::Variable(_) => order_by.clone(),
|
|
|
|
|
},
|
|
|
|
|
limit,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SelectQuery {
|
|
|
|
|
distinct: false,
|
|
|
|
|
projection: Projection::Star,
|
2020-01-14 15:46:21 +00:00
|
|
|
|
from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(
|
|
|
|
|
subselect,
|
|
|
|
|
))])),
|
2018-06-01 21:17:31 +00:00
|
|
|
|
constraints: nullable,
|
|
|
|
|
group_by: vec![],
|
2018-03-12 22:18:50 +00:00
|
|
|
|
order: order_by,
|
2018-06-01 21:17:31 +00:00
|
|
|
|
limit: Limit::None, // Any limiting comes from the internal query.
|
2017-03-06 22:40:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-07 04:18:38 +00:00
|
|
|
|
/// Consume a provided `AlgebraicQuery` to yield a new
|
|
|
|
|
/// `ProjectedSelect`.
|
2018-05-04 19:56:00 +00:00
|
|
|
|
pub fn query_to_select(schema: &Schema, query: AlgebraicQuery) -> Result<ProjectedSelect> {
|
2017-03-22 21:02:00 +00:00
|
|
|
|
// TODO: we can't pass `query.limit` here if we aggregate during projection.
|
|
|
|
|
// SQL-based aggregation -- `SELECT SUM(datoms00.e)` -- is fine.
|
2018-05-04 19:56:00 +00:00
|
|
|
|
query_projection(schema, &query).map(|e| match e {
|
2018-03-06 17:01:20 +00:00
|
|
|
|
Either::Left(constant) => ProjectedSelect::Constant(constant),
|
2018-03-12 22:18:50 +00:00
|
|
|
|
Either::Right(CombinedProjection {
|
|
|
|
|
sql_projection,
|
|
|
|
|
pre_aggregate_projection,
|
|
|
|
|
datalog_projector,
|
|
|
|
|
distinct,
|
|
|
|
|
group_by_cols,
|
|
|
|
|
}) => {
|
2018-03-06 17:01:20 +00:00
|
|
|
|
ProjectedSelect::Query {
|
2018-03-12 22:18:50 +00:00
|
|
|
|
query: match pre_aggregate_projection {
|
|
|
|
|
// If we know we need a nested query for aggregation, build that first.
|
|
|
|
|
Some(pre_aggregate) => {
|
2020-01-14 15:46:21 +00:00
|
|
|
|
let inner = cc_to_select_query(
|
|
|
|
|
pre_aggregate,
|
|
|
|
|
query.cc,
|
|
|
|
|
distinct,
|
|
|
|
|
group_by_cols,
|
|
|
|
|
query.order,
|
|
|
|
|
query.limit,
|
|
|
|
|
);
|
2020-02-21 18:51:03 +00:00
|
|
|
|
Box::new(re_project(inner, sql_projection)) // outer
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2020-02-21 18:51:03 +00:00
|
|
|
|
None => Box::new(cc_to_select_query(
|
2020-01-14 15:46:21 +00:00
|
|
|
|
sql_projection,
|
|
|
|
|
query.cc,
|
|
|
|
|
distinct,
|
|
|
|
|
group_by_cols,
|
|
|
|
|
query.order,
|
|
|
|
|
query.limit,
|
2020-02-21 18:51:03 +00:00
|
|
|
|
)),
|
2018-03-12 22:18:50 +00:00
|
|
|
|
},
|
2018-03-06 17:01:20 +00:00
|
|
|
|
projector: datalog_projector,
|
|
|
|
|
}
|
2020-01-14 15:46:21 +00:00
|
|
|
|
}
|
2018-06-27 20:04:31 +00:00
|
|
|
|
})
|
2017-03-06 22:40:10 +00:00
|
|
|
|
}
|