diff --git a/Makefile b/Makefile index d1f6d309cff7d80a81408325571b5962d3a7349d..9c43952ccae82938e0eed1395df73e7ed9c0277d 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build: cargo build --release integration_test_app: - cd test_app && rm -rf tmp/tarantool.log && TARANTOOL_LOG=tmp/tarantool.log ./.rocks/bin/luatest --coverage -v test/ + cd test_app && rm -rf tmp/tarantool.log && TARANTOOL_LOG_LEVEL=7 TARANTOOL_LOG=tmp/tarantool.log ./.rocks/bin/luatest --coverage -v test/ test: cargo test diff --git a/src/executor/engine/cartridge.rs b/src/executor/engine/cartridge.rs index 1591d995a7ef515a70fff13a6f9845cda57027f3..2d2b5fe2333d08c81e8a0b5df265a418507d665a 100644 --- a/src/executor/engine/cartridge.rs +++ b/src/executor/engine/cartridge.rs @@ -68,6 +68,14 @@ impl Engine for Runtime { let mut result = BoxExecuteFormat::new(); let sql = plan.subtree_as_sql(top_id)?; + say( + SayLevel::Debug, + file!(), + line!().try_into().unwrap_or(0), + Option::from("exec"), + &format!("exec query: {:?}", sql), + ); + if let Some(shard_keys) = plan.discovery(top_id)? { // sending query to nodes for shard in shard_keys { @@ -92,7 +100,13 @@ impl Engine for Runtime { let top = &plan.get_motion_subtree_root(motion_node_id)?; let result = self.exec(plan, *top)?; - return result.as_virtual_table(&format!("motion_{}", motion_node_id)); + let mut vtable = result.as_virtual_table()?; + + if let Some(name) = &plan.get_motion_alias(motion_node_id)? { + vtable.set_alias(name)?; + } + + Ok(vtable) } /// Calculation ``bucket_id`` function diff --git a/src/executor/engine/cartridge/backend/sql/ir.rs b/src/executor/engine/cartridge/backend/sql/ir.rs index a1a6ad78250986237191152a7c65125fb37940c4..0d9fc14be65ae65cffc1ae24495ecc7e2f64680c 100644 --- a/src/executor/engine/cartridge/backend/sql/ir.rs +++ b/src/executor/engine/cartridge/backend/sql/ir.rs @@ -103,6 +103,14 @@ impl<'e> ExecutionPlan<'e> { }; let ir_plan = self.get_ir_plan(); + + // The starting value of the counter for generating anonymous column names. + // It emulates Tarantool parser (generated by lemon parser generator). + // It traverses the parse tree bottom-up from left to right and increments + // the global counter with the columns that need an auto-generated name. + // As a result each such column would be named like "COLUMN_%d", where "%d" + // is the new global counter value. + let mut anonymous_col_idx_base = 0; for (id, data) in nodes.iter().enumerate() { if let Some(' ' | '(') = sql.chars().last() { } else if need_delim_after(id) { @@ -119,64 +127,75 @@ impl<'e> ExecutionPlan<'e> { SyntaxData::From => sql.push_str("FROM"), SyntaxData::Operator(s) => sql.push_str(s.as_str()), SyntaxData::OpenParenthesis => sql.push('('), - SyntaxData::PlanId(id) => match ir_plan.get_node(*id)? { - Node::Relational(rel) => match rel { - Relational::InnerJoin { .. } => sql.push_str("INNER JOIN"), - Relational::Motion { .. } => { - return Err(QueryPlannerError::CustomError( - "Motion nodes can't be converted to SQL.".into(), - )) - } - Relational::Projection { .. } => sql.push_str("SELECT"), - Relational::ScanRelation { relation, .. } => { - sql.push_str(relation); - } - Relational::ScanSubQuery { .. } => {} - Relational::Selection { .. } => sql.push_str("WHERE"), - Relational::UnionAll { .. } => sql.push_str("UNION ALL"), - }, - Node::Expression(expr) => match expr { - Expression::Alias { .. } - | Expression::Bool { .. } - | Expression::Row { .. } => {} - Expression::Constant { value, .. } => sql.push_str(&format!("{}", value)), - Expression::Reference { position, .. } => { - let rel_id: usize = ir_plan - .get_relational_from_reference_node(*id)? - .into_iter() - .next() - .ok_or_else(|| { - QueryPlannerError::CustomError( - "Reference points to a non-relational node.".into(), - ) - })?; - let rel_node = ir_plan.get_relation_node(rel_id)?; - if let Some(name) = rel_node.scan_name(ir_plan, *position)? { - sql.push_str(&format!( - "{}.{}", - name, - &ir_plan.get_alias_from_reference_node(expr)? - )); - } else { - sql.push_str(&ir_plan.get_alias_from_reference_node(expr)?); + SyntaxData::PlanId(id) => { + let node = ir_plan.get_node(*id)?; + match node { + Node::Relational(rel) => match rel { + Relational::InnerJoin { .. } => sql.push_str("INNER JOIN"), + Relational::Projection { .. } => sql.push_str("SELECT"), + Relational::ScanRelation { relation, .. } => { + sql.push_str(relation); } - } - }, - }, - SyntaxData::VTable(vtable) => { - let mut tuples = Vec::new(); - for tuple in vtable.get_tuples() { - tuples.push(format!( - "({})", - (tuple.iter().map(ToString::to_string)).join(",") - )); + Relational::ScanSubQuery { .. } | Relational::Motion { .. } => {} + Relational::Selection { .. } => sql.push_str("WHERE"), + Relational::UnionAll { .. } => sql.push_str("UNION ALL"), + }, + Node::Expression(expr) => match expr { + Expression::Alias { .. } + | Expression::Bool { .. } + | Expression::Row { .. } => {} + Expression::Constant { value, .. } => { + sql.push_str(&format!("{}", value)); + } + Expression::Reference { position, .. } => { + let rel_id: usize = ir_plan + .get_relational_from_reference_node(*id)? + .into_iter() + .next() + .ok_or_else(|| { + QueryPlannerError::CustomError( + "Reference points to a non-relational node.".into(), + ) + })?; + let rel_node = ir_plan.get_relation_node(rel_id)?; + let alias = &ir_plan.get_alias_from_reference_node(expr)?; + + if let Some(name) = rel_node.scan_name(ir_plan, *position)? { + sql.push_str(&format!("{}.{}", name, alias)); + } else { + sql.push_str(alias); + } + } + }, } + } + SyntaxData::VTable(vtable) => { + // increase base of global counter of anonymous cols + let cols_count = vtable.get_columns().len(); + let rows_count = vtable.get_tuples().len(); + anonymous_col_idx_base += cols_count * rows_count - (cols_count - 1); - sql.push_str(&format!("VALUES {}", tuples.join(","))); + let columns = vtable + .get_columns() + .iter() + .enumerate() + .map(|(i, c)| { + format!("COLUMN_{} as \"{}\"", anonymous_col_idx_base + i, c.name) + }) + .collect::<Vec<String>>() + .join(","); + + let tuples = vtable + .get_tuples() + .iter() + .map(|t| format!("({})", (t.iter().map(ToString::to_string)).join(","))) + .collect::<Vec<String>>() + .join(","); + + sql.push_str(&format!("SELECT {} FROM (VALUES {})", columns, tuples)); } } } - Ok(sql) } } diff --git a/src/executor/engine/cartridge/backend/sql/tree.rs b/src/executor/engine/cartridge/backend/sql/tree.rs index 2f79df3968e9d9001ab538731aa0af339d58ceb6..34fdfab598f85e5cab36a123b92448fac8f1af71 100644 --- a/src/executor/engine/cartridge/backend/sql/tree.rs +++ b/src/executor/engine/cartridge/backend/sql/tree.rs @@ -394,6 +394,7 @@ where #[allow(clippy::too_many_lines)] pub fn add_plan_node(&mut self, id: usize) -> Result<usize, QueryPlannerError> { let ir_plan = self.plan.get_ir_plan(); + match ir_plan.get_node(id)? { Node::Relational(rel) => match rel { Relational::InnerJoin { @@ -494,7 +495,19 @@ where Ok(self.nodes.push_syntax_node(sn)) } Relational::Motion { .. } => { - let sn = SyntaxNode::new_pointer(id, None, &[]); + let vtable = self.plan.get_motion_vtable(id)?; + let mut children = Vec::from([ + self.nodes.push_syntax_node(SyntaxNode::new_open()), + self.nodes + .push_syntax_node(SyntaxNode::new_vtable(vtable.clone())), + self.nodes.push_syntax_node(SyntaxNode::new_close()), + ]); + + if let Some(name) = &vtable.get_alias() { + children.push(self.nodes.push_syntax_node(SyntaxNode::new_alias(name))); + } + + let sn = SyntaxNode::new_pointer(id, None, &children); Ok(self.nodes.push_syntax_node(sn)) } }, @@ -515,17 +528,19 @@ where if let Some(motion_id) = ir_plan.get_motion_from_row(id)? { // Replace motion node to virtual table node let vtable = self.plan.get_motion_vtable(motion_id)?; - let sn = SyntaxNode::new_pointer( - id, - None, - &[ - self.nodes.push_syntax_node(SyntaxNode::new_open()), - self.nodes.push_syntax_node(SyntaxNode::new_vtable(vtable)), - self.nodes.push_syntax_node(SyntaxNode::new_close()), - ], - ); + if vtable.get_alias().is_none() { + let sn = SyntaxNode::new_pointer( + id, + None, + &[ + self.nodes.push_syntax_node(SyntaxNode::new_open()), + self.nodes.push_syntax_node(SyntaxNode::new_vtable(vtable)), + self.nodes.push_syntax_node(SyntaxNode::new_close()), + ], + ); - return Ok(self.nodes.push_syntax_node(sn)); + return Ok(self.nodes.push_syntax_node(sn)); + } } if let Some(sq_id) = ir_plan.get_sub_query_from_row_node(id)? { diff --git a/src/executor/engine/mock.rs b/src/executor/engine/mock.rs index dd8948ccab20001c44e2a3f3bf6279066ccc4c8c..4280872727f0b94cb1468e9a1b1ba16c76b32fc6 100644 --- a/src/executor/engine/mock.rs +++ b/src/executor/engine/mock.rs @@ -7,6 +7,7 @@ use crate::executor::ir::ExecutionPlan; use crate::executor::result::{BoxExecuteFormat, Value}; use crate::executor::vtable::VirtualTable; use crate::executor::Metadata; +use crate::ir::operator::Relational; use crate::ir::relation::{Column, Table, Type}; use crate::ir::value::Value as IrValue; @@ -135,10 +136,20 @@ impl Engine for EngineMock { fn materialize_motion( &self, - _plan: &mut ExecutionPlan, - _motion_node_id: usize, + plan: &mut ExecutionPlan, + motion_node_id: usize, ) -> Result<VirtualTable, QueryPlannerError> { - let mut vtable = VirtualTable::new("test"); + let sq_id = &plan.get_motion_child(motion_node_id)?; + + let mut vtable = VirtualTable::new(); + + if let Relational::ScanSubQuery { alias, .. } = + &plan.get_ir_plan().get_relation_node(*sq_id)? + { + if let Some(name) = alias { + vtable.set_alias(name)?; + } + } vtable.add_column(Column { name: "identification_number".into(), diff --git a/src/executor/ir.rs b/src/executor/ir.rs index b47bd2f7c0ef375cc90acb8acf3882256af1adce..e212dbdfe1a2376a17255e5818c45e0e89d02333 100644 --- a/src/executor/ir.rs +++ b/src/executor/ir.rs @@ -82,6 +82,21 @@ impl<'e> ExecutionPlan<'e> { ))) } + /// Get motion alias name + /// + /// # Errors + /// - node is not valid + pub fn get_motion_alias(&self, node_id: usize) -> Result<Option<String>, QueryPlannerError> { + let sq_id = &self.get_motion_child(node_id)?; + if let Relational::ScanSubQuery { alias, .. } = + self.get_ir_plan().get_relation_node(*sq_id)? + { + return Ok(alias.clone()); + } + + Ok(None) + } + /// Get root from motion sub tree /// /// # Errors @@ -96,7 +111,10 @@ impl<'e> ExecutionPlan<'e> { /// # Errors /// - node is not `Relation` type /// - node does not contain children - fn get_motion_child(&self, node_id: usize) -> Result<usize, QueryPlannerError> { + pub(in crate::executor) fn get_motion_child( + &self, + node_id: usize, + ) -> Result<usize, QueryPlannerError> { let node = self.get_ir_plan().get_relation_node(node_id)?; if !node.is_motion() { return Err(CustomError(format!( diff --git a/src/executor/result.rs b/src/executor/result.rs index 7910c5ffdd4de53096027bddade1bea1253b4f92..cefd59a180bd1885f6b9fb07822abd797447f13b 100644 --- a/src/executor/result.rs +++ b/src/executor/result.rs @@ -111,8 +111,8 @@ impl BoxExecuteFormat { /// # Errors /// - convert to virtual table error #[allow(dead_code)] - pub fn as_virtual_table(&self, name: &str) -> Result<VirtualTable, QueryPlannerError> { - let mut result = VirtualTable::new(name); + pub fn as_virtual_table(&self) -> Result<VirtualTable, QueryPlannerError> { + let mut result = VirtualTable::new(); for col in &self.metadata { result.add_column(col.clone()); diff --git a/src/executor/result/tests.rs b/src/executor/result/tests.rs index a96e40fab6865d6d35e2ca2dd52823d49302217a..f01efa336167f8205abc5b5a05bc4ac6ec117978 100644 --- a/src/executor/result/tests.rs +++ b/src/executor/result/tests.rs @@ -92,7 +92,7 @@ fn convert_to_vtable() { ], }; - let mut excepted = VirtualTable::new("test_vtable"); + let mut excepted = VirtualTable::new(); excepted.add_column(Column { name: "id".into(), @@ -126,5 +126,5 @@ fn convert_to_vtable() { IrValue::number_from_str("2.0").unwrap(), ]); - assert_eq!(excepted, r.as_virtual_table("test_vtable").unwrap()); + assert_eq!(excepted, r.as_virtual_table().unwrap()); } diff --git a/src/executor/shard.rs b/src/executor/shard.rs index e79882173c38951dbfe3af167e7c6649d0f9b47d..f6cf5ba41d3b69bf697bf2a8647cb2f0e9cb47a4 100644 --- a/src/executor/shard.rs +++ b/src/executor/shard.rs @@ -96,7 +96,6 @@ impl<'e> ExecutionPlan<'e> { ir_plan.set_distribution(left_id)?; } let left_dist = ir_plan.get_distribution(left_id)?; - // Gather right constants corresponding to the left keys. if let Distribution::Segment { keys } = left_dist { for key in keys { diff --git a/src/executor/tests.rs b/src/executor/tests.rs index 93ac12e51c17b23563259341a9e2cbb466d8b4e9..e17dc90cd4a2c90427bfc78640411d6cee376ae4 100644 --- a/src/executor/tests.rs +++ b/src/executor/tests.rs @@ -1,7 +1,9 @@ -use super::*; +use pretty_assertions::assert_eq; + use crate::executor::engine::mock::EngineMock; use crate::executor::result::Value; -use pretty_assertions::assert_eq; + +use super::*; #[test] fn shard_query() { @@ -104,7 +106,7 @@ fn linker_test() { "{} {} {}", r#"SELECT "test_space"."FIRST_NAME" as "FIRST_NAME""#, r#"FROM "test_space""#, - r#"WHERE ("test_space"."id") in (VALUES (2),(3))"#, + r#"WHERE ("test_space"."id") in (SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3)))"#, )), ], vec![ @@ -113,7 +115,7 @@ fn linker_test() { "{} {} {}", r#"SELECT "test_space"."FIRST_NAME" as "FIRST_NAME""#, r#"FROM "test_space""#, - r#"WHERE ("test_space"."id") in (VALUES (2),(3))"#, + r#"WHERE ("test_space"."id") in (SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3)))"#, )), ], ]); @@ -159,7 +161,7 @@ fn union_linker_test() { r#"FROM "test_space_hist""#, r#"WHERE ("test_space_hist"."sys_op") > (0)"#, r#") as "t1""#, - r#"WHERE ("t1"."id") in (VALUES (2),(3))"# + r#"WHERE ("t1"."id") in (SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3)))"# ) ) ], @@ -178,11 +180,178 @@ fn union_linker_test() { r#"FROM "test_space_hist""#, r#"WHERE ("test_space_hist"."sys_op") > (0)"#, r#") as "t1""#, - r#"WHERE ("t1"."id") in (VALUES (2),(3))"# + r#"WHERE ("t1"."id") in (SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3)))"# + ) + ) + ], + ]); + + assert_eq!(expected, result) +} + +#[test] +fn join_linker_test() { + let sql = r#"SELECT * +FROM + (SELECT "id", "FIRST_NAME" + FROM "test_space" + WHERE "sys_op" < 0 + AND "sysFrom" >= 0 + UNION ALL + SELECT "id", "FIRST_NAME" + FROM "test_space_hist" + WHERE "sysFrom" <= 0) AS "t3" +INNER JOIN + (SELECT "identification_number" + FROM "hash_testing_hist" + WHERE "sys_op" > 0 + UNION ALL + SELECT "identification_number" + FROM "hash_single_testing_hist" + WHERE "sys_op" <= 0) AS "t8" + ON "t3"."id" = "t8"."identification_number" +WHERE "t3"."id" = 1 AND "t8"."identification_number" = 1"#; + + let engine = EngineMock::new(); + + let mut query = Query::new(engine, sql).unwrap(); + query.optimize().unwrap(); + + let result = query.exec().unwrap(); + + let mut expected = BoxExecuteFormat::new(); + + expected.rows.extend(vec![ + vec![ + Value::String(String::from("query send to [1] shard")), + Value::String( + format!( + "{}, {}, {} {}{} {} {} {} {} {} {}{} {} {}{} {} {}", + r#"SELECT "t3"."id" as "id""#, + r#""t3"."FIRST_NAME" as "FIRST_NAME""#, + r#""t8"."identification_number" as "identification_number""#, + r#"FROM ("#, + r#"SELECT "test_space"."id" as "id", "test_space"."FIRST_NAME" as "FIRST_NAME""#, + r#"FROM "test_space""#, + r#"WHERE ("test_space"."sys_op") < (0) and ("test_space"."sysFrom") >= (0)"#, + r#"UNION ALL"#, + r#"SELECT "test_space_hist"."id" as "id", "test_space_hist"."FIRST_NAME" as "FIRST_NAME""#, + r#"FROM "test_space_hist""#, + r#"WHERE ("test_space_hist"."sysFrom") <= (0)"#, + r#") as "t3""#, + r#"INNER JOIN"#, + r#"(SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3))"#, + r#") as "t8""#, + r#"ON ("t3"."id") = ("t8"."identification_number")"#, + r#"WHERE ("t3"."id") = (1) and ("t8"."identification_number") = (1)"# + ) + ) + ], + vec![ + Value::String(String::from("query send to [2] shard")), + Value::String( + format!( + "{}, {}, {} {}{} {} {} {} {} {} {}{} {} {}{} {} {}", + r#"SELECT "t3"."id" as "id""#, + r#""t3"."FIRST_NAME" as "FIRST_NAME""#, + r#""t8"."identification_number" as "identification_number""#, + r#"FROM ("#, + r#"SELECT "test_space"."id" as "id", "test_space"."FIRST_NAME" as "FIRST_NAME""#, + r#"FROM "test_space""#, + r#"WHERE ("test_space"."sys_op") < (0) and ("test_space"."sysFrom") >= (0)"#, + r#"UNION ALL"#, + r#"SELECT "test_space_hist"."id" as "id", "test_space_hist"."FIRST_NAME" as "FIRST_NAME""#, + r#"FROM "test_space_hist""#, + r#"WHERE ("test_space_hist"."sysFrom") <= (0)"#, + r#") as "t3""#, + r#"INNER JOIN"#, + r#"(SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3))"#, + r#") as "t8""#, + r#"ON ("t3"."id") = ("t8"."identification_number")"#, + r#"WHERE ("t3"."id") = (1) and ("t8"."identification_number") = (1)"# + ) + ) + ], + vec![ + Value::String(String::from("query send to [3] shard")), + Value::String( + format!( + "{}, {}, {} {}{} {} {} {} {} {} {}{} {} {}{} {} {}", + r#"SELECT "t3"."id" as "id""#, + r#""t3"."FIRST_NAME" as "FIRST_NAME""#, + r#""t8"."identification_number" as "identification_number""#, + r#"FROM ("#, + r#"SELECT "test_space"."id" as "id", "test_space"."FIRST_NAME" as "FIRST_NAME""#, + r#"FROM "test_space""#, + r#"WHERE ("test_space"."sys_op") < (0) and ("test_space"."sysFrom") >= (0)"#, + r#"UNION ALL"#, + r#"SELECT "test_space_hist"."id" as "id", "test_space_hist"."FIRST_NAME" as "FIRST_NAME""#, + r#"FROM "test_space_hist""#, + r#"WHERE ("test_space_hist"."sysFrom") <= (0)"#, + r#") as "t3""#, + r#"INNER JOIN"#, + r#"(SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3))"#, + r#") as "t8""#, + r#"ON ("t3"."id") = ("t8"."identification_number")"#, + r#"WHERE ("t3"."id") = (1) and ("t8"."identification_number") = (1)"# ) ) ], ]); + assert_eq!(expected, result) +} + +// select * from "test_1" where "identification_number" in (select COLUMN_2 as "b" from (values (1), (2))) or "identification_number" in (select COLUMN_2 as "c" from (values (3), (4))); +#[test] +fn anonymous_col_index_test() { + let sql = r#"SELECT * FROM "test_space" + WHERE "id" in (SELECT "identification_number" FROM "hash_testing" WHERE "product_units" < 3) + OR "id" in (SELECT "identification_number" FROM "hash_testing" WHERE "product_units" > 5)"#; + + let engine = EngineMock::new(); + + let mut query = Query::new(engine, sql).unwrap(); + query.optimize().unwrap(); + + let result = query.exec().unwrap(); + + let mut expected = BoxExecuteFormat::new(); + expected.rows.extend(vec![ + vec![ + Value::String(String::from("query send to [2] shard")), + Value::String(format!( + "{} {}, {}, {}, {}, {} {} {} {} {} {}", + "SELECT", + r#""test_space"."id" as "id""#, + r#""test_space"."sysFrom" as "sysFrom""#, + r#""test_space"."FIRST_NAME" as "FIRST_NAME""#, + r#""test_space"."sys_op" as "sys_op""#, + r#""test_space"."bucket_id" as "bucket_id""#, + r#"FROM "test_space""#, + r#"WHERE (("test_space"."id") in"#, + r#"(SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3)))"#, + r#"or ("test_space"."id") in"#, + r#"(SELECT COLUMN_4 as "identification_number" FROM (VALUES (2),(3))))"#, + )), + ], + vec![ + Value::String(String::from("query send to [3] shard")), + Value::String(format!( + "{} {}, {}, {}, {}, {} {} {} {} {} {}", + "SELECT", + r#""test_space"."id" as "id""#, + r#""test_space"."sysFrom" as "sysFrom""#, + r#""test_space"."FIRST_NAME" as "FIRST_NAME""#, + r#""test_space"."sys_op" as "sys_op""#, + r#""test_space"."bucket_id" as "bucket_id""#, + r#"FROM "test_space""#, + r#"WHERE (("test_space"."id") in"#, + r#"(SELECT COLUMN_2 as "identification_number" FROM (VALUES (2),(3)))"#, + r#"or ("test_space"."id") in"#, + r#"(SELECT COLUMN_4 as "identification_number" FROM (VALUES (2),(3))))"#, + )), + ], + ]); assert_eq!(expected, result) } diff --git a/src/executor/vtable.rs b/src/executor/vtable.rs index 1f785068bb0b2b837a1a59b3cc660e1ba621c31e..45f04eb276acea54bd4de2783a38785b8afbedb3 100644 --- a/src/executor/vtable.rs +++ b/src/executor/vtable.rs @@ -18,7 +18,7 @@ pub struct VirtualTable { /// "Raw" tuples (list of values) tuples: Vec<VTableTuple>, /// Unique table name (we need to generate it ourselves). - name: String, + name: Option<String>, /// Tuples grouped by the corresponding shards. /// Value `HashSet` contains indexes from the `tuples` attribute hashing: HashMap<String, HashSet<usize>>, @@ -26,11 +26,11 @@ pub struct VirtualTable { impl VirtualTable { #[must_use] - pub fn new(name: &str) -> Self { + pub fn new() -> Self { VirtualTable { columns: vec![], tuples: vec![], - name: String::from(name), + name: None, hashing: HashMap::new(), } } @@ -69,6 +69,12 @@ impl VirtualTable { self.tuples.clone() } + /// Get vtable columns list + #[must_use] + pub fn get_columns(&self) -> Vec<Column> { + self.columns.clone() + } + /// Get tuples was distributed by sharding keys #[must_use] pub fn get_tuple_distribution(&self) -> HashMap<String, HashSet<usize>> { @@ -95,6 +101,23 @@ impl VirtualTable { }; } } + + /// Set vtable alias name + pub fn set_alias(&mut self, name: &str) -> Result<(), QueryPlannerError> { + if name.is_empty() { + return Err(QueryPlannerError::CustomError( + "Can't set empty alias for virtual table".into(), + )); + } + + self.name = Some(String::from(name)); + Ok(()) + } + + /// Get vtable alias name + pub fn get_alias(&self) -> Option<String> { + self.name.clone() + } } #[cfg(test)] diff --git a/src/executor/vtable/tests.rs b/src/executor/vtable/tests.rs index 71363ae392df9d809b9bfa896679d1e255de6ccb..27b67b1d07af7ca9b093d69e1a05852d678064f2 100644 --- a/src/executor/vtable/tests.rs +++ b/src/executor/vtable/tests.rs @@ -4,7 +4,7 @@ use super::*; #[test] fn virtual_table() { - let mut vtable = VirtualTable::new("test"); + let mut vtable = VirtualTable::new(); vtable.add_column(Column { name: "name".into(), @@ -13,13 +13,15 @@ fn virtual_table() { vtable.add_values_tuple(vec![Value::number_from_str("1").unwrap()]); + vtable.set_alias("test").unwrap(); + let expected = VirtualTable { columns: vec![Column { name: "name".into(), r#type: Type::Integer, }], tuples: vec![vec![Value::number_from_str("1").unwrap()]], - name: String::from("test"), + name: Some(String::from("test")), hashing: HashMap::new(), }; diff --git a/src/frontend/sql/ir.rs b/src/frontend/sql/ir.rs index 699ddbaeb57b38c4b04e3d6b0e6d606bf2f781ad..d66bb3984508b2a2604bade6c5b7e155617efe6e 100644 --- a/src/frontend/sql/ir.rs +++ b/src/frontend/sql/ir.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; + use traversal::DftPost; use crate::errors::QueryPlannerError; diff --git a/src/frontend/sql/ir/tests.rs b/src/frontend/sql/ir/tests.rs index d599141cc5aa1c344a9f89c552146d7fd1ddbbaa..3889c984b3557ca1cb17e1ec3e8a87207c4c6586 100644 --- a/src/frontend/sql/ir/tests.rs +++ b/src/frontend/sql/ir/tests.rs @@ -199,7 +199,12 @@ fn inner_join_duplicate_columns() { let ast = AbstractSyntaxTree::new(query).unwrap(); let plan_err = ast.to_ir(metadata).unwrap_err(); - assert_eq!(QueryPlannerError::DuplicateColumn, plan_err); + assert_eq!( + QueryPlannerError::CustomError( + "Row can't be added because `\"sys_op\"` already has an alias".into() + ), + plan_err + ); } #[test] diff --git a/src/ir/expression.rs b/src/ir/expression.rs index 88a18c2bbaaa43d75ac877666ab1511593177c93..578f669215165b9fb6b23f4c45a41d0f0c4d212d 100644 --- a/src/ir/expression.rs +++ b/src/ir/expression.rs @@ -260,7 +260,10 @@ impl Nodes { .ok_or(QueryPlannerError::InvalidNode)? { if !names.insert(String::from(name)) { - return Err(QueryPlannerError::DuplicateColumn); + return Err(QueryPlannerError::CustomError(format!( + "Row can't be added because `{}` already has an alias", + name + ))); } } else { return Err(QueryPlannerError::InvalidRow); diff --git a/src/ir/expression/tests.rs b/src/ir/expression/tests.rs index c41d49d74b12c50583c23ade6a4edecdcf445a1b..d25f296220ed9484f6daa5d68d5d871a61a0d957 100644 --- a/src/ir/expression/tests.rs +++ b/src/ir/expression/tests.rs @@ -1,7 +1,8 @@ +use pretty_assertions::assert_eq; + use crate::ir::relation::*; use crate::ir::value::*; use crate::ir::*; -use pretty_assertions::assert_eq; #[test] fn row_duplicate_column_names() { @@ -12,7 +13,9 @@ fn row_duplicate_column_names() { let c2 = plan.nodes.add_const(Value::number_from_str("2").unwrap()); let c2_alias_a = plan.nodes.add_alias("a", c2).unwrap(); assert_eq!( - QueryPlannerError::DuplicateColumn, + QueryPlannerError::CustomError( + "Row can't be added because `a` already has an alias".into() + ), plan.nodes .add_row_of_aliases(vec![c1_alias_a, c2_alias_a], None) .unwrap_err() diff --git a/src/ir/operator.rs b/src/ir/operator.rs index c10c68563bfac48b6e13bb53e9c6a287c07f875f..b952be3ce7486ffe6bd08cc747deb113ea2d77c4 100644 --- a/src/ir/operator.rs +++ b/src/ir/operator.rs @@ -277,7 +277,8 @@ impl Relational { Relational::ScanSubQuery { alias, .. } => Ok(alias.as_deref()), Relational::Projection { .. } | Relational::Selection { .. } - | Relational::InnerJoin { .. } => { + | Relational::InnerJoin { .. } + | Relational::Motion { .. } => { let output_row = plan.get_expression_node(self.output())?; let list = output_row.extract_row_list()?; let col_id = *list.get(position).ok_or_else(|| { @@ -302,7 +303,7 @@ impl Relational { } Ok(None) } - _ => Ok(None), + Relational::UnionAll { .. } => Ok(None), } } diff --git a/src/ir/operator/tests.rs b/src/ir/operator/tests.rs index 5a9865d2bd83c3da4b9236bedfdcfc14be98185c..4b15e57c9d39d48d09829c3dadfff46fdb82ee9c 100644 --- a/src/ir/operator/tests.rs +++ b/src/ir/operator/tests.rs @@ -413,7 +413,9 @@ fn join_duplicate_columns() { .unwrap(); let condition = plan.nodes.add_bool(a_row, Bool::Eq, d_row).unwrap(); assert_eq!( - QueryPlannerError::DuplicateColumn, + QueryPlannerError::CustomError( + "Row can't be added because `a` already has an alias".into() + ), plan.add_join(scan_t1, scan_t2, condition).unwrap_err() ); } diff --git a/src/ir/relation.rs b/src/ir/relation.rs index 0f9bd6afe39edde751214eea572bc19ec81e8bc7..986406ed34c36ccbac30ca5c2588279fb0c5f265 100644 --- a/src/ir/relation.rs +++ b/src/ir/relation.rs @@ -154,7 +154,9 @@ impl Table { .all(|(pos, col)| matches!(pos_map.insert(&col.name, pos), None)); if !no_duplicates { - return Err(QueryPlannerError::DuplicateColumn); + return Err(QueryPlannerError::CustomError( + "Table has duplicated columns and couldn't be loaded".into(), + )); } let keys = &k; @@ -189,7 +191,9 @@ impl Table { let no_duplicates = cols.iter().all(|col| uniq_cols.insert(&col.name)); if !no_duplicates { - return Err(QueryPlannerError::DuplicateColumn); + return Err(QueryPlannerError::CustomError( + "Table contains duplicate cols and couldn't convert to yaml.".into(), + )); } let in_range = ts.key.positions.iter().all(|pos| *pos < cols.len()); diff --git a/src/ir/relation/tests.rs b/src/ir/relation/tests.rs index be57cd71e4d61128b26ef6c5043bee4f26500d95..468e51e9f9c85bd5dd00b8881512cd2cd2ee2e39 100644 --- a/src/ir/relation/tests.rs +++ b/src/ir/relation/tests.rs @@ -55,7 +55,9 @@ fn table_seg_duplicate_columns() { &["b", "a"], ) .unwrap_err(), - QueryPlannerError::DuplicateColumn + QueryPlannerError::CustomError( + "Table has duplicated columns and couldn't be loaded".into() + ) ); } @@ -114,7 +116,9 @@ fn table_seg_serialized_duplicate_columns() { let s = fs::read_to_string(path).unwrap(); assert_eq!( Table::seg_from_yaml(&s).unwrap_err(), - QueryPlannerError::DuplicateColumn + QueryPlannerError::CustomError( + "Table contains duplicate cols and couldn't convert to yaml.".into() + ) ); } diff --git a/test_app/test/integration/api_test.lua b/test_app/test/integration/api_test.lua index 1d9e92d6920b84dc2b2a0843e139824c41cdf126..b8222a80f91c9b356a2702411946aa03c7d0513a 100644 --- a/test_app/test/integration/api_test.lua +++ b/test_app/test/integration/api_test.lua @@ -1,4 +1,5 @@ local t = require('luatest') +local fiber = require('fiber') local g = t.group('integration_api') local helper = require('test.helper') @@ -225,3 +226,62 @@ g.test_null_col_result = function() }, }) end + +g.test_join_motion_query = function() + local api = cluster:server("api-1").net_box + + local r, err = api:call("query", { [[SELECT "t3"."id", "t3"."name", "t8"."product_units" +FROM + (SELECT "id", "name" + FROM "space_simple_shard_key" + WHERE "sysOp" > 0 + UNION ALL + SELECT "id", "name" + FROM "space_simple_shard_key_hist" + WHERE "sysOp" > 0) AS "t3" +INNER JOIN + (SELECT "id" as "id1", "product_units" + FROM "testing_space" + WHERE "product_units" < 0 + UNION ALL + SELECT "id" as "id1", "product_units" + FROM "testing_space_hist" + WHERE "product_units" > 0) AS "t8" + ON "t3"."id" = "t8"."id1" +WHERE "t3"."id" = 1]] }) + + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "integer"}, + {name = "name", type = "string"}, + {name = "product_units", type = "integer"}, + }, + rows = { + { 1, "ok", 5 }, + { 1, "ok_hist", 5 } + }, + }) +end + +g.test_anonymous_cols_naming = function() + local api = cluster:server("api-1").net_box + + local r, err = api:call("query", { [[SELECT * FROM "testing_space" + WHERE "id" in (SELECT "id" FROM "space_simple_shard_key_hist" WHERE "sysOp" > 0) + OR "id" in (SELECT "id" FROM "space_simple_shard_key_hist" WHERE "sysOp" > 0) + ]] }) + + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "integer"}, + {name = "name", type = "string"}, + {name = "product_units", type = "integer"}, + {name = "bucket_id", type = "unsigned"}, + }, + rows = { + {1, "123", 1, 360} + }, + }) +end \ No newline at end of file