diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index 117e033c4aea8f59745c7e0c05dda1bec5ccb0c6..aa6f8cf8731a056ee51f6c60d741c4ffee1cb52a 100644 --- a/doc/sql/query.ebnf +++ b/doc/sql/query.ebnf @@ -18,7 +18,7 @@ select ::= 'SELECT' 'DISTINCT'? ((projection (',' projection)*)) ('GROUP' 'BY' expression (',' expression)*)? ('HAVING' expression)? ((('UNION' 'ALL') | ('EXCEPT' 'DISTINCT'?)) select)? -projection ::= '*' | expression (('AS')? name)? | aggregate +projection ::= (table '.')? '*' | expression (('AS')? name)? | aggregate expression ::= (table '.')? column | expression ('IS' ('NOT')? 'NULL') | expression ('OR' | 'AND' | '*' | '/' | '+' | '-' | '=' | '>' | '<' | '>=' | '<=' | ('<>' | '!=')) expression diff --git a/sbroad-core/src/executor/engine/helpers.rs b/sbroad-core/src/executor/engine/helpers.rs index 16a4fdcdb564b608fbd293306d12a50489cc0bc0..967cadf1d4be2943c8ba75202af2994747784e31 100644 --- a/sbroad-core/src/executor/engine/helpers.rs +++ b/sbroad-core/src/executor/engine/helpers.rs @@ -71,8 +71,8 @@ pub fn normalize_name_from_sql(s: &str) -> String { } /// Transform: -/// * "s" -> s (uppercased, unquoted) -/// * s -> S (same cased, unquoted) +/// * "s" -> s (same cased, unquoted) +/// * s -> S (uppercased, unquoted) #[must_use] pub fn normalize_name_for_space_api(s: &str) -> String { if let (Some('"'), Some('"')) = (s.chars().next(), s.chars().last()) { diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 9f3e179a07339f59b135255ebc003b5c3965a7ec..83d32312afed70745a9798b1d9a3ded995bc45de 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -23,7 +23,9 @@ use crate::frontend::Ast; use crate::ir::ddl::{ColumnDef, Ddl}; use crate::ir::ddl::{Language, ParamDef}; use crate::ir::expression::cast::Type as CastType; -use crate::ir::expression::{ColumnPositionMap, Expression, ExpressionId, Position}; +use crate::ir::expression::{ + ColumnPositionMap, ColumnsRetrievalSpec, Expression, ExpressionId, Position, +}; use crate::ir::operator::{Arithmetic, Bool, ConflictStrategy, JoinKind, Relational, Unary}; use crate::ir::relation::{Column, ColumnRole, Type as RelationType}; use crate::ir::tree::traversal::PostOrder; @@ -37,6 +39,7 @@ use crate::ir::acl::AlterOption; use crate::ir::acl::{Acl, GrantRevokeType, Privilege}; use crate::ir::aggregates::AggregateKind; use crate::ir::block::Block; +use crate::ir::expression::NewColumnsSource; use crate::ir::helpers::RepeatableState; use crate::ir::transformation::redistribution::ColumnPosition; use sbroad_proc::otm_child_span; @@ -222,10 +225,10 @@ fn parse_rename_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, let child_node = ast.nodes.get_node(*child_id)?; match child_node.rule { Rule::NewProc => { - new_name = normalize_name_for_space_api(parse_string_value_node(ast, *child_id)?); + new_name = parse_identifier(ast, *child_id)?; } Rule::OldProc => { - old_name = normalize_name_for_space_api(parse_string_value_node(ast, *child_id)?); + old_name = parse_identifier(ast, *child_id)?; } Rule::ProcParams => { params = Some(parse_proc_params(ast, child_node)?); @@ -2098,16 +2101,26 @@ impl AbstractSyntaxTree { proj_columns.push(plan_alias_id); } Rule::Asterisk => { - let plan_asterisk_id = - plan.add_row_for_output(plan_rel_child_id, &[], false)?; - let Node::Expression(Expression::Row { list, .. }) = - plan.get_node(plan_asterisk_id).expect( - "`add_row_for_output` must've added an Expression::Row", - ) - else { - unreachable!("Row expected under Asterisk") + let table_name_id = ast_column.children.first(); + let plan_asterisk_id = if let Some(table_name_id) = table_name_id { + let table_name = + parse_normalized_identifier(self, *table_name_id)?; + + let col_name_pos_map = + ColumnPositionMap::new(&plan, plan_rel_child_id)?; + let filtered_col_ids = + col_name_pos_map.get_by_scan_name(&table_name)?; + plan.add_row_by_indices( + plan_rel_child_id, + filtered_col_ids, + false, + )? + } else { + plan.add_row_for_output(plan_rel_child_id, &[], false)? }; - for row_id in list { + + let row_list = plan.get_row_list(plan_asterisk_id)?; + for row_id in row_list { proj_columns.push(*row_id); } } @@ -2339,10 +2352,10 @@ impl AbstractSyntaxTree { pk_columns.push(column.name.as_str()); } let pk_column_ids = plan.new_columns( - &[proj_child_id], - false, - &[0], - pk_columns.as_ref(), + &NewColumnsSource::Other { + child: proj_child_id, + columns_spec: Some(ColumnsRetrievalSpec::Names(pk_columns)), + }, false, false, )?; diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs index 06e71a049e91388eaf5cc21e1e69be2dc9c9c05c..920aa55714943d0114ae24eb0c5a11350810b1b1 100644 --- a/sbroad-core/src/frontend/sql/ir/tests.rs +++ b/sbroad-core/src/frontend/sql/ir/tests.rs @@ -424,6 +424,75 @@ vtable_max_rows = 5000 assert_eq!(expected_explain, plan.as_explain().unwrap()); } +#[test] +fn front_projection_with_scan_specification_under_scan() { + let input = r#"SELECT "hash_testing".* FROM "hash_testing""#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"projection ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op") + scan "hash_testing" +execution options: +sql_vdbe_max_steps = 45000 +vtable_max_rows = 5000 +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_projection_with_scan_specification_under_join() { + let input = r#"SELECT "hash_testing".* FROM "hash_testing" join "test_space" on true"#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"projection ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op") + join on true::boolean + scan "hash_testing" + projection ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op") + scan "hash_testing" + motion [policy: full] + scan "test_space" + projection ("test_space"."id"::unsigned -> "id", "test_space"."sysFrom"::unsigned -> "sysFrom", "test_space"."FIRST_NAME"::string -> "FIRST_NAME", "test_space"."sys_op"::unsigned -> "sys_op") + scan "test_space" +execution options: +sql_vdbe_max_steps = 45000 +vtable_max_rows = 5000 +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_projection_with_scan_specification_under_join_of_subqueries() { + let input = r#"SELECT "ts_sq".*, "hs".* FROM "hash_testing" as "hs" + join (select "ts".* from "test_space" as "ts") as "ts_sq" on true"#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"projection ("ts_sq"."id"::unsigned -> "id", "ts_sq"."sysFrom"::unsigned -> "sysFrom", "ts_sq"."FIRST_NAME"::string -> "FIRST_NAME", "ts_sq"."sys_op"::unsigned -> "sys_op", "hs"."identification_number"::integer -> "identification_number", "hs"."product_code"::string -> "product_code", "hs"."product_units"::boolean -> "product_units", "hs"."sys_op"::unsigned -> "sys_op") + join on true::boolean + scan "hs" + projection ("hs"."identification_number"::integer -> "identification_number", "hs"."product_code"::string -> "product_code", "hs"."product_units"::boolean -> "product_units", "hs"."sys_op"::unsigned -> "sys_op") + scan "hash_testing" -> "hs" + motion [policy: full] + scan "ts_sq" + projection ("ts"."id"::unsigned -> "id", "ts"."sysFrom"::unsigned -> "sysFrom", "ts"."FIRST_NAME"::string -> "FIRST_NAME", "ts"."sys_op"::unsigned -> "sys_op") + scan "test_space" -> "ts" +execution options: +sql_vdbe_max_steps = 45000 +vtable_max_rows = 5000 +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + #[test] fn front_sql_subquery_column_duplicates() { let input = r#"SELECT "id" FROM "test_space" WHERE ("id", "id") diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index f8248af145ea733e7cef624afa6614346daf6f61..2c6856bb0b5f3fc292d971b127781d2c785d6884 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -115,7 +115,7 @@ Query = { (SelectWithOptionalContinuation | Values | Insert | Update | Delete) ~ Projection = { Distinct? ~ ProjectionElement ~ ("," ~ ProjectionElement)* } ProjectionElement = _{ Asterisk | Column } Column = { Expr ~ ((^"as")? ~ Identifier)? } - Asterisk = { "*" } + Asterisk = { (Identifier ~ ".")? ~ "*" } WhereClause = _{ ^"where" ~ Selection } Selection = { Expr } Scan = { (ScanTable | SubQuery) ~ ((^"as")? ~ Identifier)? } diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs index 6764afb9aa54cd566e8330f008d1cc46633c4114..f4a3e8ffd013855bd0fc3755b616d916d6e958fa 100644 --- a/sbroad-core/src/ir/expression.rs +++ b/sbroad-core/src/ir/expression.rs @@ -417,32 +417,6 @@ impl Nodes { self.push(Node::Expression(Expression::Row { list, distribution })) } - /// Adds row node, where every column has an alias. - /// Mostly used for relational node output. - /// - /// # Errors - /// - nodes in the list do not exist in the arena - /// - some nodes in the list are not aliases - pub fn add_row_of_aliases( - &mut self, - list: Vec<usize>, - distribution: Option<Distribution>, - ) -> Result<usize, SbroadError> { - for alias_id in &list { - let node = self.arena.get(*alias_id).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, format!("from arena with index {alias_id}")) - })?; - if let Node::Expression(Expression::Alias { .. }) = node { - continue; - } - return Err(SbroadError::Invalid( - Entity::Expression, - Some(format!("expected {alias_id} to be alias, got {node:?}")), - )); - } - Ok(self.add_row(list, distribution)) - } - /// Adds unary boolean node. /// /// # Errors @@ -833,7 +807,7 @@ impl ColumnPositionMap { }) } - /// Get position of relational output that corresponds to given `column`. + /// Get position of relational node output that corresponds to given `column`. /// Note that we don't specify a Scan name here (see `get_with_scan` below for that logic). pub(crate) fn get(&self, column: &str) -> Result<Position, SbroadError> { let from_key = (SmolStr::from(column), None); @@ -871,6 +845,7 @@ impl ColumnPositionMap { } } + /// Get position of relational node output that corresponds to given `scan.column`. pub(crate) fn get_with_scan( &self, column: &str, @@ -896,6 +871,172 @@ impl ColumnPositionMap { format!("with name {column} and scan {scan:?}"), )) } + + /// Get positions of all columns in relational node output + /// that corresponds to given `target_scan_name`. + pub(crate) fn get_by_scan_name( + &self, + target_scan_name: &str, + ) -> Result<Vec<Position>, SbroadError> { + let mut res = Vec::new(); + for (_, positions) in self.map.iter().filter(|((_, scan_name), _)| { + if let Some(scan_name) = scan_name { + scan_name == target_scan_name + } else { + false + } + }) { + if let Positions::Single(pos) = positions { + res.push(*pos); + } else { + return Err(SbroadError::DuplicatedValue(format!( + "column name for {target_scan_name} scan name is ambiguous" + ))); + } + } + + // Note: sorting of usizes doesn't take much time. + res.sort_unstable(); + Ok(res) + } +} + +/// Specification of column names/indices that we want to retrieve in `new_columns` call. +#[derive(Clone)] +pub enum ColumnsRetrievalSpec<'spec> { + Names(Vec<&'spec str>), + Indices(Vec<usize>), +} + +/// Specification of targets to retrieve from join within `new_columns` call. +pub enum JoinTargets<'targets> { + Left { + columns_spec: Option<ColumnsRetrievalSpec<'targets>>, + }, + Right { + columns_spec: Option<ColumnsRetrievalSpec<'targets>>, + }, + Both, +} + +/// Indicator of relational nodes source for `new_columns` call. +/// +/// If `columns_spec` is met, it means we'd like to retrieve only specific columns. +/// Otherwise, we retrieve all the columns from children. +pub enum NewColumnsSource<'targets> { + Join { + outer_child: usize, + inner_child: usize, + targets: JoinTargets<'targets>, + }, + /// Enum variant used both for Except and UnionAll operators. + ExceptUnion { + left_child: usize, + right_child: usize, + }, + /// Other relational nodes. + Other { + child: usize, + columns_spec: Option<ColumnsRetrievalSpec<'targets>>, + }, +} + +/// Iterator needed for unified way of source nodes traversal during `new_columns` call. +pub struct NewColumnSourceIterator<'iter> { + source: &'iter NewColumnsSource<'iter>, + index: usize, +} + +impl<'targets> Iterator for NewColumnSourceIterator<'targets> { + // Pair of (relational node id, target id) + type Item = (usize, usize); + + fn next(&mut self) -> Option<(usize, usize)> { + let result = match &self.source { + NewColumnsSource::Join { + outer_child, + inner_child, + targets, + } => match targets { + JoinTargets::Left { .. } => match self.index { + 0 => outer_child, + _ => return None, + }, + JoinTargets::Right { .. } => match self.index { + 0 => inner_child, + _ => return None, + }, + JoinTargets::Both => match self.index { + 0 => outer_child, + 1 => inner_child, + _ => return None, + }, + }, + NewColumnsSource::ExceptUnion { left_child, .. } => match self.index { + // For the `UnionAll` and `Except` operators we need only the first + // child to get correct column names for a new tuple + // (the second child aliases would be shadowed). But each reference should point + // to both children to give us additional information + // during transformations. + 0 => left_child, + _ => return None, + }, + NewColumnsSource::Other { child, .. } => match self.index { + 0 => child, + _ => return None, + }, + }; + let res = Some((*result, self.index)); + self.index += 1; + res + } +} + +impl<'iter, 'source: 'iter> IntoIterator for &'source NewColumnsSource<'iter> { + type Item = (usize, usize); + type IntoIter = NewColumnSourceIterator<'iter>; + + fn into_iter(self) -> Self::IntoIter { + NewColumnSourceIterator { + source: self, + index: 0, + } + } +} + +impl<'source> NewColumnsSource<'source> { + fn is_join(&self) -> bool { + matches!(self, NewColumnsSource::Join { .. }) + } + + fn get_columns_spec(&self) -> Option<ColumnsRetrievalSpec> { + match self { + NewColumnsSource::Join { targets, .. } => match targets { + JoinTargets::Left { columns_spec } | JoinTargets::Right { columns_spec } => { + columns_spec.clone() + } + JoinTargets::Both => None, + }, + NewColumnsSource::ExceptUnion { .. } => None, + NewColumnsSource::Other { columns_spec, .. } => columns_spec.clone(), + } + } + + fn targets(&self) -> Vec<usize> { + match self { + NewColumnsSource::Join { targets, .. } => match targets { + JoinTargets::Left { .. } => vec![0], + JoinTargets::Right { .. } => vec![1], + JoinTargets::Both => vec![0, 1], + }, + NewColumnsSource::ExceptUnion { .. } => vec![0, 1], + NewColumnsSource::Other { .. } => vec![0], + } + } + + fn iter(&'source self) -> NewColumnSourceIterator { + <&Self as IntoIterator>::into_iter(self) + } } impl Plan { @@ -906,170 +1047,92 @@ impl Plan { /// Returns a list of columns from the children relational nodes outputs. /// - /// * If `col_names` is empty then copies all the columns - /// from the child node to a new tuple.`need_sharding_column` indicates whether we want - /// to copy sharding columns from the child relational node - /// * If `col_names` is not empty then copies only child output columns that correspond - /// to that names. - /// /// `need_aliases` indicates whether we'd like to copy aliases (their names) from the child /// node or whether we'd like to build raw References list. /// - /// The `is_join` option "on" builds an output tuple for the left child and - /// appends the right child's one to it. Otherwise we build an output tuple - /// only from the first (left) child. - /// /// # Errors /// Returns `SbroadError`: /// - relation node contains invalid `Row` in the output - /// - targets and children are inconsistent /// - column names don't exist /// /// # Panics - /// - `targets` has unreachable values + /// - Plan is in inconsistent state. #[allow(clippy::too_many_lines)] pub fn new_columns( &mut self, - child_rel_nodes: &[usize], - is_join: bool, - targets: &[usize], - col_names: &[&str], + source: &NewColumnsSource, need_aliases: bool, need_sharding_column: bool, ) -> Result<Vec<usize>, SbroadError> { - // We can pass two target children nodes only in case - // of `UnionAll` and `InnerJoin`. - // - For the `UnionAll` operator we need only the first - // child to get correct column names for a new tuple - // (the second child aliases would be shadowed). But each reference should point - // to both children to give us additional information - // during transformations. - if (targets.len() > 2) || targets.is_empty() { - return Err(SbroadError::UnexpectedNumberOfValues(format!( - "invalid target length: {}", - targets.len() - ))); - } - if let Some(max) = targets.iter().max() { - if *max >= child_rel_nodes.len() { - return Err(SbroadError::UnexpectedNumberOfValues(format!( - "invalid children length: {}", - child_rel_nodes.len() - ))); - } - } + // Vec of (column position in child output, column plan id, new_targets). + let mut filtered_children_row_list: Vec<(usize, usize, Vec<usize>)> = Vec::new(); + + if let Some(columns_spec) = source.get_columns_spec() { + let (rel_child, _) = source + .iter() + .next() + .expect("Source must have a single target"); + + let relational_op = self.get_relation_node(rel_child)?; + let output_id = relational_op.output(); + let child_node_row_list = self.get_row_list(output_id)?.to_vec(); + + let indices: Vec<usize> = match columns_spec { + ColumnsRetrievalSpec::Names(names) => { + let col_name_pos_map = ColumnPositionMap::new(self, rel_child)?; + let indices: Result<Vec<usize>, SbroadError> = + names.iter().map(|n| col_name_pos_map.get(n)).collect(); + indices? + } + ColumnsRetrievalSpec::Indices(indices) => indices.clone(), + }; - // List of columns to be passed into `Expression::Row`. - let mut result_row_list: Vec<usize> = Vec::new(); - - if col_names.is_empty() { - let required_targets = if is_join { targets } else { &targets[0..1] }; - for target_idx in required_targets { - let target_rel_child_id = targets - .get(*target_idx) - .expect("target child not found among required"); - let child_node_id = child_rel_nodes - .get(*target_rel_child_id) - .expect("child rel node not found"); - let rel_node = self.get_relation_node(*child_node_id)?; - let child_row_list: Vec<(usize, usize)> = if let Expression::Row { list, .. } = - self.get_expression_node(rel_node.output())? - { - if need_sharding_column { - list.iter() - .enumerate() - .map(|(pos, id)| (pos, *id)) - .collect() - } else { - let mut new_list = Vec::with_capacity(list.len()); - for (pos, expr_id) in list.iter().enumerate() { - if let Expression::Alias { name, .. } = - self.get_expression_node(*expr_id)? - { - if is_sharding_column_name(name.as_str()) { - continue; - } - } - new_list.push((pos, *expr_id)); - } - new_list - } + for index in indices { + let col_id = *child_node_row_list + .get(index) + .expect("Column id not found under relational child output"); + let alias_name = self.get_expression_node(col_id)?.get_alias_name()?; + if is_sharding_column_name(alias_name) { + continue; + } + filtered_children_row_list.push((index, col_id, source.targets())); + } + } else { + for (child_node_id, target_idx) in source { + let new_targets: Vec<usize> = if source.is_join() { + vec![target_idx] } else { - return Err(SbroadError::Invalid( - Entity::Expression, - Some("child node is not a row".into()), - )); + source.targets() }; - result_row_list.reserve(child_row_list.len()); - for (pos, alias_node) in child_row_list { - let expr = self.get_expression_node(alias_node)?; - let name: String = if let Expression::Alias { ref name, .. } = expr { - String::from(name) - } else { - return Err(SbroadError::Invalid( - Entity::Expression, - Some(format!("expression {expr:?} is not an Alias")), - )); - }; - let new_targets: Vec<usize> = if is_join { - // Reference in a join tuple first points to the left, - // then to the right child. - vec![*target_idx] - } else { - // Reference in union tuple points to **both** left and right children. - targets.to_vec() - }; - let col_type = expr.calculate_type(self)?; - // Adds new references and aliases to arena (if we need them). - let r_id = self.nodes.add_ref(None, Some(new_targets), pos, col_type); - if need_aliases { - let a_id = self.nodes.add_alias(&name, r_id)?; - result_row_list.push(a_id); - } else { - result_row_list.push(r_id); + let rel_node = self.get_relation_node(child_node_id)?; + let child_row_list = self.get_row_list(rel_node.output())?; + if need_sharding_column { + child_row_list.iter().enumerate().for_each(|(pos, id)| { + filtered_children_row_list.push((pos, *id, new_targets.clone())); + }); + } else { + for (pos, expr_id) in child_row_list.iter().enumerate() { + let alias_name = self.get_expression_node(*expr_id)?.get_alias_name()?; + if is_sharding_column_name(alias_name) { + continue; + } + filtered_children_row_list.push((pos, *expr_id, new_targets.clone())); } } } + }; - return Ok(result_row_list); - } - - result_row_list.reserve(col_names.len()); - let target_child = targets.first().expect("targets are empty"); - let child_node = child_rel_nodes - .get(*target_child) - .expect("child_rel_nodes doesn't contain needed target"); - - let mut col_names_set: HashSet<&str, RandomState> = - HashSet::with_capacity_and_hasher(col_names.len(), RandomState::new()); - for col_name in col_names { - col_names_set.insert(col_name); - } - - // Map of { column name (aliased) from child output -> its index in output } - let map = ColumnPositionMap::new(self, *child_node)?; - - // Vec of { `map` key (column name), targets, `map` value (column index in child output) } - let mut refs: Vec<(&str, Vec<usize>, usize)> = Vec::with_capacity(col_names.len()); - for col in col_names { - let pos = map.get(col)?; - refs.push((col, targets.to_vec(), pos)); - } + // List of columns to be passed into `Expression::Row`. + let mut result_row_list: Vec<usize> = Vec::with_capacity(filtered_children_row_list.len()); + for (pos, alias_node_id, new_targets) in filtered_children_row_list { + let alias_expr = self.get_expression_node(alias_node_id)?; + let alias_name = String::from(alias_expr.get_alias_name()?); + let col_type = alias_expr.calculate_type(self)?; - let relational_op = self.get_relation_node(*child_node)?; - let output_id = relational_op.output(); - let output = self.get_expression_node(output_id)?; - let columns = output.clone_row_list()?; - for (col, new_targets, pos) in refs { - let col_id = *columns - .get(pos) - .expect("Column id not found under relational child output"); - let col_expr = self.get_expression_node(col_id)?; - let col_type = col_expr.calculate_type(self)?; let r_id = self.nodes.add_ref(None, Some(new_targets), pos, col_type); if need_aliases { - let a_id = self.nodes.add_alias(col, r_id)?; + let a_id = self.nodes.add_alias(&alias_name, r_id)?; result_row_list.push(a_id); } else { result_row_list.push(r_id); @@ -1079,6 +1142,29 @@ impl Plan { Ok(result_row_list) } + /// New output for a single child node (with aliases) + /// specified by indices we should retrieve from given `rel_node` output. + /// + /// # Errors + /// Returns `SbroadError`: + /// - child is an inconsistent relational node + pub fn add_row_by_indices( + &mut self, + rel_node: usize, + indices: Vec<usize>, + need_sharding_column: bool, + ) -> Result<usize, SbroadError> { + let list = self.new_columns( + &NewColumnsSource::Other { + child: rel_node, + columns_spec: Some(ColumnsRetrievalSpec::Indices(indices)), + }, + true, + need_sharding_column, + )?; + Ok(self.nodes.add_row(list, None)) + } + /// New output for a single child node (with aliases). /// /// If column names are empty, copy all the columns from the child. @@ -1092,15 +1178,21 @@ impl Plan { col_names: &[&str], need_sharding_column: bool, ) -> Result<usize, SbroadError> { + let specific_columns = if col_names.is_empty() { + None + } else { + Some(ColumnsRetrievalSpec::Names(Vec::from(col_names))) + }; + let list = self.new_columns( - &[rel_node], - false, - &[0], - col_names, + &NewColumnsSource::Other { + child: rel_node, + columns_spec: specific_columns, + }, true, need_sharding_column, )?; - self.nodes.add_row_of_aliases(list, None) + Ok(self.nodes.add_row(list, None)) } /// New output row for union node. @@ -1113,8 +1205,15 @@ impl Plan { left: usize, right: usize, ) -> Result<usize, SbroadError> { - let list = self.new_columns(&[left, right], false, &[0, 1], &[], true, true)?; - self.nodes.add_row_of_aliases(list, None) + let list = self.new_columns( + &NewColumnsSource::ExceptUnion { + left_child: left, + right_child: right, + }, + true, + true, + )?; + Ok(self.nodes.add_row(list, None)) } /// New output row for join node. @@ -1125,8 +1224,16 @@ impl Plan { /// Returns `SbroadError`: /// - children are inconsistent relational nodes pub fn add_row_for_join(&mut self, left: usize, right: usize) -> Result<usize, SbroadError> { - let list = self.new_columns(&[left, right], true, &[0, 1], &[], true, true)?; - self.nodes.add_row_of_aliases(list, None) + let list = self.new_columns( + &NewColumnsSource::Join { + outer_child: left, + inner_child: right, + targets: JoinTargets::Both, + }, + true, + true, + )?; + Ok(self.nodes.add_row(list, None)) } /// Project columns from the child node. @@ -1142,7 +1249,20 @@ impl Plan { child: usize, col_names: &[&str], ) -> Result<usize, SbroadError> { - let list = self.new_columns(&[child], false, &[0], col_names, false, true)?; + let specific_columns = if col_names.is_empty() { + None + } else { + Some(ColumnsRetrievalSpec::Names(Vec::from(col_names))) + }; + + let list = self.new_columns( + &NewColumnsSource::Other { + child, + columns_spec: specific_columns, + }, + false, + true, + )?; Ok(self.nodes.add_row(list, None)) } @@ -1196,7 +1316,17 @@ impl Plan { right: usize, col_names: &[&str], ) -> Result<usize, SbroadError> { - let list = self.new_columns(&[left, right], true, &[0], col_names, false, true)?; + let list = self.new_columns( + &NewColumnsSource::Join { + outer_child: left, + inner_child: right, + targets: JoinTargets::Left { + columns_spec: Some(ColumnsRetrievalSpec::Names(Vec::from(col_names))), + }, + }, + false, + true, + )?; Ok(self.nodes.add_row(list, None)) } @@ -1214,7 +1344,17 @@ impl Plan { right: usize, col_names: &[&str], ) -> Result<usize, SbroadError> { - let list = self.new_columns(&[left, right], true, &[1], col_names, false, true)?; + let list = self.new_columns( + &NewColumnsSource::Join { + outer_child: left, + inner_child: right, + targets: JoinTargets::Right { + columns_spec: Some(ColumnsRetrievalSpec::Names(Vec::from(col_names))), + }, + }, + false, + true, + )?; Ok(self.nodes.add_row(list, None)) } diff --git a/sbroad-core/src/ir/expression/tests.rs b/sbroad-core/src/ir/expression/tests.rs index bd5233bb2f60fc8ce09ffdb585c7a99af1a11fe7..9e39f5036a868d78a2c1b91990e54619d8e1c62f 100644 --- a/sbroad-core/src/ir/expression/tests.rs +++ b/sbroad-core/src/ir/expression/tests.rs @@ -14,9 +14,7 @@ fn row_duplicate_column_names() { let c1_alias_a = plan.nodes.add_alias("a", c1).unwrap(); let c2 = plan.nodes.add_const(Value::from(2_u64)); let c2_alias_a = plan.nodes.add_alias("a", c2).unwrap(); - plan.nodes - .add_row_of_aliases(vec![c1_alias_a, c2_alias_a], None) - .unwrap(); + plan.nodes.add_row(vec![c1_alias_a, c2_alias_a], None); } #[test] diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs index 7cdcab368d5e703f4397cd003eb61d181ac22885..aefd905c1503fa3e1ecd7041d1351e83110828d0 100644 --- a/sbroad-core/src/ir/operator.rs +++ b/sbroad-core/src/ir/operator.rs @@ -1173,7 +1173,7 @@ impl Plan { let dist = Distribution::Segment { keys: KeySet::from(keys), }; - let output = self.nodes.add_row_of_aliases(refs, Some(dist))?; + let output = self.nodes.add_row(refs, Some(dist)); let insert = Node::Relational(Relational::Insert { relation: relation.into(), columns, @@ -1201,7 +1201,7 @@ impl Plan { refs.push(col_alias_id); } - let output_id = nodes.add_row_of_aliases(refs, None)?; + let output_id = nodes.add_row(refs, None); let scan = Relational::ScanRelation { output: output_id, relation: String::from(table), @@ -1421,7 +1421,7 @@ impl Plan { columns: &[usize], is_distinct: bool, ) -> Result<usize, SbroadError> { - let output = self.nodes.add_row_of_aliases(columns.to_vec(), None)?; + let output = self.nodes.add_row(columns.to_vec(), None); let proj = Relational::Projection { children: vec![child], output,