From d04d22a6a693a181e3be98770b2eb4c02142cdcf Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Wed, 7 Jun 2017 10:59:25 -0700 Subject: [PATCH] Part 2: refactor projector to be reusable from translator. This allows the translator to also use bound values in nested queries. --- query-projector/src/lib.rs | 50 +++++++++++++++++++---------- query-translator/src/translate.rs | 53 ++++++++++++++++--------------- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/query-projector/src/lib.rs b/query-projector/src/lib.rs index 64988b19..b9922731 100644 --- a/query-projector/src/lib.rs +++ b/query-projector/src/lib.rs @@ -28,6 +28,7 @@ use rusqlite::{ use mentat_core::{ SQLValueType, TypedValue, + ValueType, }; use mentat_db::{ @@ -44,6 +45,7 @@ use mentat_query::{ use mentat_query_algebrizer::{ AlgebraicQuery, ColumnName, + ConjoiningClauses, 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 // one column in the query. If that constraint is violated it's a // bug in our code, so it's appropriate to panic here. - let columns = query.cc - .column_bindings - .get(var) - .expect(format!("Every variable should have a binding, but {:?} does not", var).as_str()); + let columns = cc.column_bindings + .get(var) + .expect(format!("Every variable should have a binding, but {:?} does not", var).as_str()); let qa = columns[0].clone(); let name = VariableColumn::Variable(var.clone()).column_name(); (ColumnOrExpression::Column(qa), name) } -fn candidate_type_column(query: &AlgebraicQuery, var: &Variable) -> (ColumnOrExpression, Name) { - let extracted_alias = query.cc - .extracted_types - .get(var) - .expect("Every variable has a known type or an extracted type"); +fn candidate_type_column(cc: &ConjoiningClauses, var: &Variable) -> (ColumnOrExpression, Name) { + let extracted_alias = cc.extracted_types + .get(var) + .expect("Every variable has a known type or an extracted type"); let type_name = VariableColumn::VariableTypeTag(var.clone()).column_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) { + 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. /// /// Returns a pair: the SQL projection (which should always be a `Projection::Columns`) @@ -211,10 +227,10 @@ fn project_elements<'a, I: IntoIterator>( // If we're projecting this, we don't need it in :with. with.remove(var); - let (column, name) = candidate_column(query, var); - cols.push(ProjectedColumn(column, name)); - if let Some(t) = query.cc.known_type(var) { - let tag = t.value_type_tag(); + let (projected_column, maybe_type) = projected_column_for_var(&var, &query.cc); + cols.push(projected_column); + if let Some(ty) = maybe_type { + let tag = ty.value_type_tag(); templates.push(TypedIndex::Known(i, tag)); i += 1; // We used one SQL column. } else { @@ -222,7 +238,7 @@ fn project_elements<'a, I: IntoIterator>( i += 2; // We used two SQL columns. // 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)); } } @@ -233,10 +249,10 @@ fn project_elements<'a, I: IntoIterator>( // 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 // 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)); 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)); } } diff --git a/query-translator/src/translate.rs b/query-translator/src/translate.rs index 0dfd28a1..f755af65 100644 --- a/query-translator/src/translate.rs +++ b/query-translator/src/translate.rs @@ -38,6 +38,7 @@ use mentat_query_algebrizer::{ use mentat_query_projector::{ CombinedProjection, Projector, + projected_column_for_var, query_projection, }; @@ -201,33 +202,33 @@ fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubq // project it as the variable name. // E.g., SELECT datoms03.v AS `?x`. for var in projection.iter() { - let col = cc.column_bindings.get(&var).unwrap()[0].clone(); - let proj = ProjectedColumn(ColumnOrExpression::Column(col), var.to_string()); - columns.push(proj); - } + let (projected_column, maybe_type) = projected_column_for_var(var, &cc); + columns.push(projected_column); - // Similarly, project type tags if they're not known conclusively in the - // outer query. - for var in type_extraction.iter() { - let expression = - if let Some(known) = cc.known_type(var) { - // If we know the type for sure, just project the constant. - // SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag` - ColumnOrExpression::Integer(known.value_type_tag()) - } 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) - .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()); - columns.push(proj); + // 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 = + if let Some(ty) = maybe_type { + // If we know the type for sure, just project the constant. + // SELECT datoms03.v AS `?x`, 10 AS `?x_value_type_tag` + ColumnOrExpression::Integer(ty.value_type_tag()) + } 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) + .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()); + columns.push(proj); + } } // Each arm simply turns into a subquery.