Part 2: refactor projector to be reusable from translator.
This allows the translator to also use bound values in nested queries.
This commit is contained in:
parent
b9cbf92205
commit
d04d22a6a6
2 changed files with 60 additions and 43 deletions
|
@ -28,6 +28,7 @@ use rusqlite::{
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
SQLValueType,
|
SQLValueType,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
|
ValueType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_db::{
|
use mentat_db::{
|
||||||
|
@ -44,6 +45,7 @@ use mentat_query::{
|
||||||
use mentat_query_algebrizer::{
|
use mentat_query_algebrizer::{
|
||||||
AlgebraicQuery,
|
AlgebraicQuery,
|
||||||
ColumnName,
|
ColumnName,
|
||||||
|
ConjoiningClauses,
|
||||||
VariableColumn,
|
VariableColumn,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -157,12 +159,11 @@ impl TypedIndex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn candidate_column(query: &AlgebraicQuery, var: &Variable) -> (ColumnOrExpression, Name) {
|
fn candidate_column(cc: &ConjoiningClauses, var: &Variable) -> (ColumnOrExpression, Name) {
|
||||||
// Every variable should be bound by the top-level CC to at least
|
// Every variable should be bound by the top-level CC to at least
|
||||||
// one column in the query. If that constraint is violated it's a
|
// one column in the query. If that constraint is violated it's a
|
||||||
// bug in our code, so it's appropriate to panic here.
|
// bug in our code, so it's appropriate to panic here.
|
||||||
let columns = query.cc
|
let columns = cc.column_bindings
|
||||||
.column_bindings
|
|
||||||
.get(var)
|
.get(var)
|
||||||
.expect(format!("Every variable should have a binding, but {:?} does not", var).as_str());
|
.expect(format!("Every variable should have a binding, but {:?} does not", var).as_str());
|
||||||
|
|
||||||
|
@ -171,15 +172,30 @@ fn candidate_column(query: &AlgebraicQuery, var: &Variable) -> (ColumnOrExpressi
|
||||||
(ColumnOrExpression::Column(qa), name)
|
(ColumnOrExpression::Column(qa), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn candidate_type_column(query: &AlgebraicQuery, var: &Variable) -> (ColumnOrExpression, Name) {
|
fn candidate_type_column(cc: &ConjoiningClauses, var: &Variable) -> (ColumnOrExpression, Name) {
|
||||||
let extracted_alias = query.cc
|
let extracted_alias = cc.extracted_types
|
||||||
.extracted_types
|
|
||||||
.get(var)
|
.get(var)
|
||||||
.expect("Every variable has a known type or an extracted type");
|
.expect("Every variable has a known type or an extracted type");
|
||||||
let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name();
|
let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name();
|
||||||
(ColumnOrExpression::Column(extracted_alias.clone()), type_name)
|
(ColumnOrExpression::Column(extracted_alias.clone()), type_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the projected column -- that is, a value or SQL column and an associated name -- for a
|
||||||
|
/// given variable. Also return the type, if known.
|
||||||
|
/// Callers are expected to determine whether to project a type tag as an additional SQL column.
|
||||||
|
pub fn projected_column_for_var(var: &Variable, cc: &ConjoiningClauses) -> (ProjectedColumn, Option<ValueType>) {
|
||||||
|
if let Some(value) = cc.bound_value(&var) {
|
||||||
|
// If we already know the value, then our lives are easy.
|
||||||
|
let tag = value.value_type();
|
||||||
|
let name = VariableColumn::Variable(var.clone()).column_name();
|
||||||
|
(ProjectedColumn(ColumnOrExpression::Value(value.clone()), name), Some(tag))
|
||||||
|
} else {
|
||||||
|
// If we don't, then the CC *must* have bound the variable.
|
||||||
|
let (column, name) = candidate_column(cc, var);
|
||||||
|
(ProjectedColumn(column, name), cc.known_type(var))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Walk an iterator of `Element`s, collecting projector templates and columns.
|
/// Walk an iterator of `Element`s, collecting projector templates and columns.
|
||||||
///
|
///
|
||||||
/// Returns a pair: the SQL projection (which should always be a `Projection::Columns`)
|
/// Returns a pair: the SQL projection (which should always be a `Projection::Columns`)
|
||||||
|
@ -211,10 +227,10 @@ fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
|
||||||
// If we're projecting this, we don't need it in :with.
|
// If we're projecting this, we don't need it in :with.
|
||||||
with.remove(var);
|
with.remove(var);
|
||||||
|
|
||||||
let (column, name) = candidate_column(query, var);
|
let (projected_column, maybe_type) = projected_column_for_var(&var, &query.cc);
|
||||||
cols.push(ProjectedColumn(column, name));
|
cols.push(projected_column);
|
||||||
if let Some(t) = query.cc.known_type(var) {
|
if let Some(ty) = maybe_type {
|
||||||
let tag = t.value_type_tag();
|
let tag = ty.value_type_tag();
|
||||||
templates.push(TypedIndex::Known(i, tag));
|
templates.push(TypedIndex::Known(i, tag));
|
||||||
i += 1; // We used one SQL column.
|
i += 1; // We used one SQL column.
|
||||||
} else {
|
} else {
|
||||||
|
@ -222,7 +238,7 @@ fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
|
||||||
i += 2; // We used two SQL columns.
|
i += 2; // We used two SQL columns.
|
||||||
|
|
||||||
// Also project the type from the SQL query.
|
// Also project the type from the SQL query.
|
||||||
let (type_column, type_name) = candidate_type_column(query, &var);
|
let (type_column, type_name) = candidate_type_column(&query.cc, &var);
|
||||||
cols.push(ProjectedColumn(type_column, type_name));
|
cols.push(ProjectedColumn(type_column, type_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,10 +249,10 @@ fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
|
||||||
// We need to collect these into the SQL column list, but they don't affect projection.
|
// We need to collect these into the SQL column list, but they don't affect projection.
|
||||||
// If a variable is of a non-fixed type, also project the type tag column, so we don't
|
// If a variable is of a non-fixed type, also project the type tag column, so we don't
|
||||||
// accidentally unify across types when considering uniqueness!
|
// accidentally unify across types when considering uniqueness!
|
||||||
let (column, name) = candidate_column(query, &var);
|
let (column, name) = candidate_column(&query.cc, &var);
|
||||||
cols.push(ProjectedColumn(column, name));
|
cols.push(ProjectedColumn(column, name));
|
||||||
if query.cc.known_type(&var).is_none() {
|
if query.cc.known_type(&var).is_none() {
|
||||||
let (type_column, type_name) = candidate_type_column(query, &var);
|
let (type_column, type_name) = candidate_type_column(&query.cc, &var);
|
||||||
cols.push(ProjectedColumn(type_column, type_name));
|
cols.push(ProjectedColumn(type_column, type_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ use mentat_query_algebrizer::{
|
||||||
use mentat_query_projector::{
|
use mentat_query_projector::{
|
||||||
CombinedProjection,
|
CombinedProjection,
|
||||||
Projector,
|
Projector,
|
||||||
|
projected_column_for_var,
|
||||||
query_projection,
|
query_projection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,19 +202,18 @@ fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubq
|
||||||
// project it as the variable name.
|
// project it as the variable name.
|
||||||
// E.g., SELECT datoms03.v AS `?x`.
|
// E.g., SELECT datoms03.v AS `?x`.
|
||||||
for var in projection.iter() {
|
for var in projection.iter() {
|
||||||
let col = cc.column_bindings.get(&var).unwrap()[0].clone();
|
let (projected_column, maybe_type) = projected_column_for_var(var, &cc);
|
||||||
let proj = ProjectedColumn(ColumnOrExpression::Column(col), var.to_string());
|
columns.push(projected_column);
|
||||||
columns.push(proj);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similarly, project type tags if they're not known conclusively in the
|
// Similarly, project type tags if they're not known conclusively in the
|
||||||
// outer query.
|
// outer query.
|
||||||
for var in type_extraction.iter() {
|
// Assumption: we'll never need to project a tag without projecting the value of a variable.
|
||||||
|
if type_extraction.contains(var) {
|
||||||
let expression =
|
let expression =
|
||||||
if let Some(known) = cc.known_type(var) {
|
if let Some(ty) = maybe_type {
|
||||||
// If we know the type for sure, just project the constant.
|
// If we know the type for sure, just project the constant.
|
||||||
// SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag`
|
// SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag`
|
||||||
ColumnOrExpression::Integer(known.value_type_tag())
|
ColumnOrExpression::Integer(ty.value_type_tag())
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we'll have an established type binding! This'll be
|
// Otherwise, we'll have an established type binding! This'll be
|
||||||
// either a datoms table or, recursively, a subquery. Project
|
// either a datoms table or, recursively, a subquery. Project
|
||||||
|
@ -229,6 +229,7 @@ fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubq
|
||||||
let proj = ProjectedColumn(expression, type_column.column_name());
|
let proj = ProjectedColumn(expression, type_column.column_name());
|
||||||
columns.push(proj);
|
columns.push(proj);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Each arm simply turns into a subquery.
|
// Each arm simply turns into a subquery.
|
||||||
// The SQL translation will stuff "UNION" between each arm.
|
// The SQL translation will stuff "UNION" between each arm.
|
||||||
|
|
Loading…
Reference in a new issue