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:
Nick Alexander 2017-06-07 10:59:25 -07:00 committed by Richard Newman
parent b9cbf92205
commit d04d22a6a6
2 changed files with 60 additions and 43 deletions

View file

@ -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,29 +159,43 @@ 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());
let qa = columns[0].clone(); let qa = columns[0].clone();
let name = VariableColumn::Variable(var.clone()).column_name(); let name = VariableColumn::Variable(var.clone()).column_name();
(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));
} }
} }

View file

@ -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,33 +202,33 @@ 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.
let expression = if type_extraction.contains(var) {
if let Some(known) = cc.known_type(var) { let expression =
// If we know the type for sure, just project the constant. if let Some(ty) = maybe_type {
// SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag` // If we know the type for sure, just project the constant.
ColumnOrExpression::Integer(known.value_type_tag()) // SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag`
} else { ColumnOrExpression::Integer(ty.value_type_tag())
// Otherwise, we'll have an established type binding! This'll be } else {
// either a datoms table or, recursively, a subquery. Project // Otherwise, we'll have an established type binding! This'll be
// this: // either a datoms table or, recursively, a subquery. Project
// SELECT datoms03.v AS `?x`, // this:
// datoms03.value_type_tag AS `?x_value_type_tag` // SELECT datoms03.v AS `?x`,
let extract = cc.extracted_types // datoms03.value_type_tag AS `?x_value_type_tag`
.get(var) let extract = cc.extracted_types
.expect("Expected variable to have a known type or an extracted type"); .get(var)
ColumnOrExpression::Column(extract.clone()) .expect("Expected variable to have a known type or an extracted type");
}; ColumnOrExpression::Column(extract.clone())
let type_column = VariableColumn::VariableTypeTag(var.clone()); };
let proj = ProjectedColumn(expression, type_column.column_name()); let type_column = VariableColumn::VariableTypeTag(var.clone());
columns.push(proj); let proj = ProjectedColumn(expression, type_column.column_name());
columns.push(proj);
}
} }
// Each arm simply turns into a subquery. // Each arm simply turns into a subquery.