diff --git a/sbroad-cartridge/src/cartridge/router.rs b/sbroad-cartridge/src/cartridge/router.rs index 56bf31fc72ad92ed66602fe410cc939a7e5f287d..f33f484345a6a5b175bb39d7babbf4f259b5c8cb 100644 --- a/sbroad-cartridge/src/cartridge/router.rs +++ b/sbroad-cartridge/src/cartridge/router.rs @@ -6,6 +6,7 @@ use std::any::Any; use std::cell::{Ref, RefCell}; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; + use std::rc::Rc; use tarantool::tlua::LuaFunction; @@ -219,7 +220,7 @@ impl RouterRuntime { return Err(SbroadError::Invalid( Entity::Buckets, Some(format!("Expected Buckets::Filtered, got {buckets:?}")), - )); + )) }; let random_bucket = self.get_random_bucket(); let buckets = if bucket_set.is_empty() { @@ -596,7 +597,7 @@ fn group(buckets: &Buckets) -> Result<HashMap<String, Vec<u64>>, SbroadError> { Buckets::All => { return Err(SbroadError::Unsupported( Entity::Buckets, - Some("grouping buckets is not supported for all buckets".into()), + Some("grouping buckets is not supported for Buckets::All".into()), )) } Buckets::Filtered(list) => list.iter().copied().collect(), diff --git a/sbroad-cartridge/src/cartridge/storage.rs b/sbroad-cartridge/src/cartridge/storage.rs index d952c29312f8e67fe4e6107d74fdf593ff2e0447..b440b026f6aa860998c5ebff53c0ca8cf3ca1a07 100644 --- a/sbroad-cartridge/src/cartridge/storage.rs +++ b/sbroad-cartridge/src/cartridge/storage.rs @@ -207,7 +207,7 @@ impl StorageRuntime { )); } - let (pattern_with_params, tmp_spaces) = compile_encoded_optional(raw_optional)?; + let (pattern_with_params, _tmp_spaces) = compile_encoded_optional(raw_optional)?; debug!( Option::from("execute"), &format!( @@ -220,9 +220,7 @@ impl StorageRuntime { } else { read_unprepared(&pattern_with_params.pattern, &pattern_with_params.params) }; - for space in tmp_spaces { - drop(space); - } + result } @@ -272,7 +270,7 @@ impl StorageRuntime { &format!("Failed to find a plan (id {plan_id}) in the cache."), ); - let (pattern_with_params, tmp_spaces) = compile_encoded_optional(raw_optional)?; + let (pattern_with_params, _tmp_spaces) = compile_encoded_optional(raw_optional)?; let result = match prepare(&pattern_with_params.pattern) { Ok(stmt) => { let stmt_id = stmt.id()?; @@ -331,9 +329,7 @@ impl StorageRuntime { } } }; - for space in tmp_spaces { - drop(space); - } + result } } diff --git a/sbroad-cartridge/test_app/test/data/config.yml b/sbroad-cartridge/test_app/test/data/config.yml index 34580bed506176b245de7215e0dac0268c366d41..7444abd1ea2e633c13bdb31ef717b9696a612bf0 100644 --- a/sbroad-cartridge/test_app/test/data/config.yml +++ b/sbroad-cartridge/test_app/test/data/config.yml @@ -1093,4 +1093,3 @@ schema: engine: memtx sharding_key: - id - diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua index 16d3b0546c4d556c20977032f29441c94dd317b2..95bf11e55a16d0d4f713a7175d09229dc8f79637 100644 --- a/sbroad-cartridge/test_app/test/integration/api_test.lua +++ b/sbroad-cartridge/test_app/test/integration/api_test.lua @@ -198,7 +198,7 @@ g.test_query_errored = function() -- luacheck: max line length 140 local _, err = api:call("sbroad.execute", { [[SELECT "NotFoundColumn" FROM "testing_space"]], {} }) - t.assert_equals(tostring(err), "Sbroad Error: column with name [\"\\\"NotFoundColumn\\\"\"] not found") + t.assert_equals(tostring(err), "Sbroad Error: column with name \"NotFoundColumn\" not found") local invalid_type_param = datetime.new{ nsec = 123456789, diff --git a/sbroad-cartridge/test_app/test/integration/groupby_test.lua b/sbroad-cartridge/test_app/test/integration/groupby_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..5ef8f64d205efd1ce7933d33b5aa1efe8c92c6ca --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/groupby_test.lua @@ -0,0 +1,760 @@ +local t = require('luatest') +local groupby_queries = t.group('groupby_queries') + +local helper = require('test.helper.cluster_no_replication') +local cluster = nil + +groupby_queries.before_all( + function() + helper.start_test_cluster(helper.cluster_config) + cluster = helper.cluster + + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { + [[INSERT INTO "testing_space" ("id", "name", "product_units") VALUES + (?, ?, ?), + (?, ?, ?), + (?, ?, ?), + (?, ?, ?), + (?, ?, ?), + (?, ?, ?) + ]], + { + 1, "123", 1, + 2, "1", 1, + 3, "1", 1, + 4, "2", 2, + 5, "123", 2, + 6, "2", 4 + } + }) + t.assert_equals(err, nil) + t.assert_equals(r, {row_count = 6}) + r, err = api:call("sbroad.execute", { + [[ + INSERT INTO "arithmetic_space" + ("id", "a", "b", "c", "d", "e", "f", "boolean_col", "string_col", "number_col") + VALUES (?,?,?,?,?,?,?,?,?,?), + (?,?,?,?,?,?,?,?,?,?), + (?,?,?,?,?,?,?,?,?,?), + (?,?,?,?,?,?,?,?,?,?) + ]], + { + 1, 1, 1, 1, 1, 2, 2, true, "a", 3.14, + 2, 1, 2, 1, 2, 2, 2, true, "a", 2, + 3, 2, 3, 1, 2, 2, 2, true, "c", 3.14, + 4, 2, 3, 1, 1, 2, 2, true, "c", 2.14 + } + }) + + t.assert_equals(err, nil) + t.assert_equals(r, {row_count = 4}) + r, err = api:call("sbroad.execute", { + [[ + INSERT INTO "arithmetic_space2" + ("id", "a", "b", "c", "d", "e", "f", "boolean_col", "string_col", "number_col") + VALUES (?,?,?,?,?,?,?,?,?,?), + (?,?,?,?,?,?,?,?,?,?), + (?,?,?,?,?,?,?,?,?,?), + (?,?,?,?,?,?,?,?,?,?) + ]], + { + 1, 2, 1, 1, 1, 2, 2, true, "a", 3.1415, + 2, 2, 2, 1, 3, 2, 2, false, "a", 3.1415, + 3, 1, 1, 1, 1, 2, 2, false, "b", 2.718, + 4, 1, 1, 1, 1, 2, 2, true, "b", 2.717, + } + }) + + t.assert_equals(err, nil) + t.assert_equals(r, {row_count = 4}) + end +) + +groupby_queries.after_all(function() + local storage1 = cluster:server("storage-1-1").net_box + storage1:call("box.execute", { [[TRUNCATE TABLE "testing_space"]] }) + storage1:call("box.execute", { [[TRUNCATE TABLE "arithmetic_space"]] }) + storage1:call("box.execute", { [[TRUNCATE TABLE "arithmetic_space2"]] }) + + local storage2 = cluster:server("storage-2-1").net_box + storage2:call("box.execute", { [[TRUNCATE TABLE "testing_space"]] }) + storage2:call("box.execute", { [[TRUNCATE TABLE "arithmetic_space"]] }) + storage2:call("box.execute", { [[TRUNCATE TABLE "arithmetic_space2"]] }) + + helper.stop_test_cluster() +end) + +groupby_queries.test_grouping = function() + local api = cluster:server("api-1").net_box + + -- with GROUP BY + local r, err = api:call("sbroad.execute", { [[ + SELECT "name" + FROM "testing_space" + GROUP BY "name" +]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "name", type = "string" }, + }) + t.assert_items_equals(r.rows, { + { "123" }, + { "1" }, + { "2" } + }) + + -- without GROUP BY + local r, err = api:call("sbroad.execute", { [[ + SELECT "name" + FROM "testing_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "name", type = "string" }, + }) + t.assert_items_equals(r.rows, { + { "123" }, + { "123" }, + { "1" }, + { "1" }, + { "2" }, + { "2" } + }) +end + +groupby_queries.expr_in_proj = function() + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { [[ + SELECT "name" || 'p' AS "name" + FROM "testing_space" + GROUP BY "name" +]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "name", type = "string" }, + }) + t.assert_items_equals(r.rows, { + { "123p" }, + { "1p" }, + { "2p" } + }) + + local r, err = api:call("sbroad.execute", { [[ + SELECT "a" + "b" AS e1, "a" / "b" AS e2 + FROM "arithmetic_space" + GROUP BY "a", "b" +]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "E1", type = "integer" }, + { name = "E2", type = "integer" }, + }) + t.assert_items_equals(r.rows, { + {3, 0}, {5, 0}, {2, 1} + }) + +end + +groupby_queries.different_column_types = function() + local api = cluster:server("api-1").net_box + + -- DECIMAL + local r, err = api:call("sbroad.execute", { [[ + SELECT * + FROM (SELECT cast("number_col" AS decimal) AS col FROM "arithmetic_space") + GROUP BY col +]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "COL", type = "decimal" }, + }) + t.assert_items_equals(r.rows, { + { 2 }, + { 2.14 }, + { 3.14 } + }) + + -- integer, boolean, STRING + r, err = api:call("sbroad.execute", { [[ + SELECT "f", "boolean_col", "string_col" + FROM "arithmetic_space" + GROUP BY "f", "boolean_col", "string_col" +]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + {name = "f", type = "integer"}, + {name = "boolean_col", type = "boolean"}, + {name = "string_col", type = "string"}, + }) + t.assert_items_equals(r.rows, { + { 2, true, "a" }, + { 2, true, "c" }, + }) + + -- SCALAR + r, err = api:call("sbroad.execute", { [[ + SELECT * + FROM ( + SELECT CAST("number_col" AS SCALAR) AS u FROM "arithmetic_space" + UNION ALL + SELECT * FROM ( + SELECT CAST("boolean_col" AS SCALAR) FROM "arithmetic_space" + UNION ALL + SELECT CAST("string_col" AS STRING) FROM "arithmetic_space" + ) + ) + GROUP BY u +]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + {name = "U", type = "scalar"}, + }) + t.assert_items_equals(r.rows, { + {2}, {true}, {2.14}, {3.14}, {"a"}, {"c"} + }) + + -- double, UNSIGNED + r, err = api:call("sbroad.execute", { [[ + SELECT d, u + FROM ( + SELECT CAST("number_col" AS DOUBLE) AS d, CAST("number_col" AS UNSIGNED) AS u FROM "arithmetic_space2" + ) + GROUP BY d, u +]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + {name = "D", type = "double"}, + {name = "U", type = "unsigned"}, + }) + t.assert_items_equals(r.rows, { + {2.717, 2}, {3.1415, 3}, {2.718, 2}, + }) +end + +groupby_queries.invalid = function() + local api = cluster:server("api-1").net_box + + local _, err = api:call("sbroad.execute", { [[ + SELECT "name" FROM "testing_space" groupBY "name" + ]], {} }) + t.assert_str_contains(tostring(err), "rule parsing error") + + local _, err = api:call("sbroad.execute", { [[ + SELECT "id" + "product_units" FROM "testing_space" GROUP BY "id" + ]], {} }) + t.assert_str_contains(tostring(err), "Invalid projection with GROUP BY clause") + + local _, err = api:call("sbroad.execute", { [[ + SELECT "name", "product_units" FROM "testing_space" GROUP BY "name" + ]], {} }) + t.assert_str_contains(tostring(err), "Invalid projection with GROUP BY clause") + + local _, err = api:call("sbroad.execute", { [[ + SELECT "name" AS "q" FROM "testing_space" GROUP BY "q" + ]], {} }) + t.assert_str_contains(tostring(err), "column with name \"q\" not found") + + local _, err = api:call("sbroad.execute", { [[ + SELECT "name", "product_units" FROM "testing_space" GROUP BY "name" "product_units" + ]], {} }) + t.assert_str_contains(tostring(err), "rule parsing error") + + local _, err = api:call("sbroad.execute", { [[ + SELECT "product_units" FROM "testing_space" GROUP BY "name" + ]], {} }) + t.assert_str_contains(tostring(err), "Invalid projection with GROUP BY clause") +end + +groupby_queries.test_two_col = function() + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { [[ + SELECT "product_units", "name" + FROM "testing_space" + GROUP BY "product_units", "name" + ]], {} }) + + local expected = { + { 1, "123" }, + { 1, "1" }, + { 2, "2" }, + { 2, "123" }, + { 4, "2" }, + }; + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + {name = "product_units", type = "integer"}, + {name = "name", type = "string"}, + }) + t.assert_items_equals(r.rows, expected) + + r, err = api:call("sbroad.execute", { [[ + SELECT "product_units", "name" + FROM "testing_space" + GROUP BY "name", "product_units" + ]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + {name = "product_units", type = "integer"}, + {name = "name", type = "string"}, + }) + t.assert_items_equals(r.rows, expected) +end + +groupby_queries.test_with_selection = function () + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { [[ + SELECT "product_units", "name" + FROM "testing_space" + WHERE "product_units" > ? + GROUP BY "product_units", "name" + ]], {1} }) + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + {name = "product_units", type = "integer"}, + {name = "name", type = "string"}, + }) + t.assert_items_equals(r.rows, { + { 2, "2" }, + { 2, "123" }, + { 4, "2" }, + }) +end + +groupby_queries.test_with_JOIN = function () + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { [[ + SELECT "id", "id2" + FROM "arithmetic_space" + INNER JOIN + (SELECT "id" as "id2", "a" as "a2" from "arithmetic_space2") as t + ON "arithmetic_space"."id" = t."a2" + GROUP BY "id", "id2" +]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "id", type = "integer" }, + { name = "id2", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1, 3 }, + { 1, 4 }, + { 2, 1 }, + { 2, 2 }, + }) +end + + +groupby_queries.test_with_join2 = function () + local api = cluster:server("api-1").net_box + -- with groupBY + local r, err = api:call("sbroad.execute", { [[ + SELECT "c", q.a1 + FROM "arithmetic_space" + INNER JOIN + (SELECT "b" AS b1, "a" AS a1 FROM "arithmetic_space2") AS q + ON "arithmetic_space"."c" = q.a1 + GROUP BY "c", a1 +]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "c", type = "integer" }, + { name = "A1", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1, 1 }, + }) + + -- without groupBY + r, err = api:call("sbroad.execute", { [[ + SELECT "c", q.a1 + FROM "arithmetic_space" + INNER JOIN + (SELECT "b" AS b1, "a" AS a1 FROM "arithmetic_space2") AS q + ON "arithmetic_space"."c" = q.a1 +]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "arithmetic_space.c", type = "integer" }, + { name = "Q.A1", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1} + }) +end + + +groupby_queries.test_with_join3 = function () + local api = cluster:server("api-1").net_box + local r, err = api:call("sbroad.execute", { [[ + SELECT r."i", q."b" + FROM (SELECT "a" AS "i" FROM "arithmetic_space2" GROUP BY "a") AS r + INNER JOIN + (SELECT "c", "b" FROM "arithmetic_space" GROUP BY "c", "b") AS q + ON r."i" = q."b" + GROUP BY r."i", q."b" +]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "i", type = "integer" }, + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1, 1 }, + { 2, 2 }, + }) + + -- without GROUP BY + r, err = api:call("sbroad.execute", { [[ + SELECT r."i", q."b" + FROM (SELECT "a" AS "i" FROM "arithmetic_space2") AS r + INNER JOIN + (SELECT "c", "b" FROM "arithmetic_space") AS q + ON r."i" = q."b" +]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "R.i", type = "integer" }, + { name = "Q.b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + {2, 2}, {2, 2}, {1, 1}, {1, 1} + }) + +end + + +groupby_queries.test_with_UNION = function () + local api = cluster:server("api-1").net_box + local r, err = api:call("sbroad.execute", { + [[SELECT "a" FROM "arithmetic_space" GROUP BY "a" UNION ALL SELECT "a" FROM "arithmetic_space2"]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "a", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1 }, + { 2 }, + { 2 }, + { 2 }, + { 1 }, + { 1 }, + }) + + r, err = api:call("sbroad.execute", { + [[ + SELECT "a" FROM "arithmetic_space" GROUP BY "a" UNION ALL SELECT "a" FROM "arithmetic_space2" GROUP BY "a" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "a", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1 }, + { 2 }, + { 1 }, + { 2 }, + }) + + + r, err = api:call("sbroad.execute", { + [[ + SELECT "a" FROM ( + SELECT "a" FROM "arithmetic_space" GROUP BY "a" UNION ALL SELECT "a" FROM "arithmetic_space2" GROUP BY "a" + ) GROUP BY "a"]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "a", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1 }, + { 2 }, + }) +end + +groupby_queries.test_with_EXCEPT = function () + local api = cluster:server("api-1").net_box + local r, err = api:call("sbroad.execute", { + [[ + SELECT "b" FROM "arithmetic_space" GROUP BY "b" + EXCEPT + SELECT "b" FROM "arithmetic_space2" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 3 }, + }) + + r, err = api:call("sbroad.execute", { + [[ + SELECT * FROM ( + SELECT "a", "b" FROM "arithmetic_space" GROUP BY "a", "b" + UNION ALL SELECT * FROM ( + SELECT "c", "d" FROM "arithmetic_space" + EXCEPT + SELECT "c", "d" FROM "arithmetic_space2" GROUP BY "c", "d") + ) GROUP BY "a", "b" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "a", type = "integer" }, + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1, 1 }, + { 1, 2 }, + { 2, 3 }, + }) + + r, err = api:call("sbroad.execute", { + [[ + SELECT "b" FROM "arithmetic_space" + EXCEPT + SELECT "b" FROM "arithmetic_space2" + GROUP BY "b" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 3 }, + }) +end + +groupby_queries.test_with_subquery_1 = function () + local api = cluster:server("api-1").net_box + + -- with GROUP BY + local r, err = api:call("sbroad.execute", { + [[ + SELECT * FROM ( + SELECT "a", "b" FROM "arithmetic_space2" + GROUP BY "a", "b" + ) + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "a", type = "integer" }, + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 2, 1 }, + { 2, 2 }, + { 1, 1 }, + }) + + -- without GROUP BY + r, err = api:call("sbroad.execute", { + [[ + SELECT * FROM ( + SELECT "a", "b" FROM "arithmetic_space2" + ) + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "a", type = "integer" }, + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 2, 1 }, + { 2, 2 }, + { 1, 1 }, + { 1, 1 }, + }) +end + + +groupby_queries.test_with_subquery_2 = function () + local api = cluster:server("api-1").net_box + local r, err = api:call("sbroad.execute", { + [[ + SELECT cast("number_col" AS integer) AS k FROM "arithmetic_space" GROUP BY "number_col" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "K", type = "integer" }, + }) + t.assert_items_equals(r.rows, { + { 2 }, + { 2 }, + { 3 }, + }) + + r, err = api:call("sbroad.execute", { + [[ + SELECT "f" FROM "arithmetic_space2" + WHERE "id" in (SELECT cast("number_col" AS integer) FROM "arithmetic_space" GROUP BY "number_col") + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "f", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 2 }, + { 2 }, + }) + + r, err = api:call("sbroad.execute", { + [[ + SELECT "f" FROM "arithmetic_space2" + WHERE "id" in (SELECT cast("number_col" AS integer) FROM "arithmetic_space" GROUP BY "number_col") + GROUP BY "f" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "f", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 2 }, + }) +end + +groupby_queries.test_with_subquery_3 = function () + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { + [[ + SELECT "b", "string_col" FROM + (SELECT "b", "string_col" FROM "arithmetic_space2" GROUP BY "b", "string_col") AS t1 + INNER JOIN + (SELECT "id" FROM "testing_space" WHERE "id" in (SELECT "a" FROM "arithmetic_space" GROUP BY "a")) AS t2 + on t2."id" = t1."b" + WHERE "b" in (SELECT "c" FROM "arithmetic_space" GROUP BY "c") + GROUP BY "b", "string_col" + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "b", type = "integer" }, + { name = "string_col", type = "string" }, + }) + + t.assert_items_equals(r.rows, { + { 1, "a" }, + { 1, "b" }, + }) +end + +groupby_queries.test_complex_1 = function () + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { + [[ + SELECT * FROM ( + SELECT "b" FROM "arithmetic_space" + WHERE "a" in + (SELECT "a" FROM "arithmetic_space2" WHERE "a" in + (SELECT "b" FROM "arithmetic_space" GROUP BY "b") + GROUP BY "a") + GROUP BY "b" + UNION ALL SELECT * FROM ( + SELECT "b" FROM "arithmetic_space2" + EXCEPT + SELECT "a" FROM "arithmetic_space" + ) + ) + ]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1 }, + { 3 }, + { 2 } + }) +end + +groupby_queries.test_complex_2 = function () + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { + [[ + SELECT * FROM ( + SELECT "b" FROM "arithmetic_space" + WHERE "c" in + (SELECT "id" FROM "arithmetic_space2" WHERE "id" in + (SELECT "b" FROM "arithmetic_space" GROUP BY "b") + GROUP BY "id") + GROUP BY "b" + UNION ALL + SELECT * FROM ( + SELECT "c" FROM "arithmetic_space2" + WHERE "id" = ? or "b" = ? + GROUP BY "c" + EXCEPT + (SELECT "a" FROM "arithmetic_space" GROUP BY "a")) + ) + ]], {2, 1} + }) + + t.assert_equals(err, nil) + t.assert_equals(r.metadata, { + { name = "b", type = "integer" }, + }) + + t.assert_items_equals(r.rows, { + { 1 }, + { 3 }, + { 2 }, + }) +end + diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs index 35754c64ded0589156b05e7a923f89dd7b7cb762..4d3b61e01df664b480958dc2d1994f2138fe0b09 100644 --- a/sbroad-core/src/backend/sql/ir.rs +++ b/sbroad-core/src/backend/sql/ir.rs @@ -1,6 +1,7 @@ use ahash::AHashMap; use opentelemetry::Context; use serde::{Deserialize, Serialize}; +use std::collections::hash_map::IntoIter; use std::collections::HashMap; use std::fmt::Write as _; use tarantool::tlua::{self, Push}; @@ -137,15 +138,17 @@ impl From<PatternWithParams> for String { } } +#[derive(Debug)] pub struct TmpSpaceMap { inner: AHashMap<String, TmpSpace>, } -impl Iterator for TmpSpaceMap { - type Item = TmpSpace; +impl IntoIterator for TmpSpaceMap { + type Item = (String, TmpSpace); + type IntoIter = IntoIter<String, TmpSpace>; - fn next(&mut self) -> Option<Self::Item> { - self.inner.drain().next().map(|(_, v)| v) + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() } } @@ -291,6 +294,7 @@ impl ExecutionPlan { } Node::Relational(rel) => match rel { Relational::Except { .. } => sql.push_str("EXCEPT"), + Relational::GroupBy { .. } => sql.push_str("GROUP BY"), Relational::Insert { relation, .. } => { sql.push_str("INSERT INTO "); sql.push_str(relation.as_str()); diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs index 0b945fc315d860ea0e3d824fbdb8b8f4a2b9b838..6ceaba088a07407f43f2f8728f53eff5e22caa9f 100644 --- a/sbroad-core/src/backend/sql/tree.rs +++ b/sbroad-core/src/backend/sql/tree.rs @@ -1,9 +1,9 @@ use ahash::RandomState; +use serde::{Deserialize, Serialize}; + use std::collections::HashMap; use std::mem::take; -use serde::{Deserialize, Serialize}; - use crate::errors::{Action, Entity, SbroadError}; use crate::executor::ir::ExecutionPlan; use crate::ir::expression::Expression; @@ -174,6 +174,51 @@ pub struct SyntaxNodes { map: HashMap<usize, usize, RandomState>, } +#[derive(Debug)] +pub struct SyntaxIterator<'n> { + current: usize, + child: usize, + nodes: &'n SyntaxNodes, +} + +impl<'n> SyntaxNodes { + #[must_use] + pub fn iter(&'n self, current: usize) -> SyntaxIterator<'n> { + SyntaxIterator { + current, + child: 0, + nodes: self, + } + } +} + +impl<'n> Iterator for SyntaxIterator<'n> { + type Item = usize; + + fn next(&mut self) -> Option<Self::Item> { + syntax_next(self).copied() + } +} + +fn syntax_next<'nodes>(iter: &mut SyntaxIterator<'nodes>) -> Option<&'nodes usize> { + match iter.nodes.arena.get(iter.current) { + Some(SyntaxNode { left, right, .. }) => { + if iter.child == 0 { + iter.child += 1; + if let Some(left_id) = left { + return Some(left_id); + } + } + let right_idx = iter.child - 1; + if right_idx < right.len() { + iter.child += 1; + return Some(&right[right_idx]); + } + None + } + None => None, + } +} impl SyntaxNodes { /// Add sub-query syntax node /// @@ -311,16 +356,129 @@ struct Select { selection: Option<usize>, /// Join syntax node join: Option<usize>, + /// GroupBy syntax node + groupby: Option<usize>, } +type NodeAdder = fn(&mut Select, usize, &SyntaxPlan) -> Result<bool, SbroadError>; impl Select { + fn add_one_of( + id: usize, + select: &mut Select, + sp: &SyntaxPlan, + adders: &[NodeAdder], + ) -> Result<bool, SbroadError> { + for add in adders { + if add(select, id, sp)? { + return Ok(true); + } + } + Ok(false) + } + + fn add_inner_join( + select: &mut Select, + id: usize, + sp: &SyntaxPlan, + ) -> Result<bool, SbroadError> { + let sn = sp.nodes.get_syntax_node(id)?; + let left_id = sn.left_id_or_err()?; + let sn_left = sp.nodes.get_syntax_node(left_id)?; + let plan_node_left = sp.plan_node_or_err(&sn_left.data)?; + if let Node::Relational(Relational::InnerJoin { .. }) = sp.plan_node_or_err(&sn.data)? { + select.join = Some(id); + if let Node::Relational( + Relational::ScanRelation { .. } + | Relational::ScanSubQuery { .. } + | Relational::Motion { .. }, + ) = plan_node_left + { + select.scan = left_id; + return Ok(true); + } + return Err(SbroadError::Invalid( + Entity::SyntaxPlan, + Some(format!( + "Expected a scan or motion after InnerJoin. Got: {plan_node_left:?}" + )), + )); + } + Ok(false) + } + + fn add_selection(select: &mut Select, id: usize, sp: &SyntaxPlan) -> Result<bool, SbroadError> { + let sn = sp.nodes.get_syntax_node(id)?; + if let Node::Relational(Relational::Selection { .. }) = sp.plan_node_or_err(&sn.data)? { + select.selection = Some(id); + let left_id = sn.left_id_or_err()?; + let sn_left = sp.nodes.get_syntax_node(left_id)?; + let plan_node_left = sp.plan_node_or_err(&sn_left.data)?; + if let Node::Relational( + Relational::ScanRelation { .. } | Relational::ScanSubQuery { .. }, + ) = plan_node_left + { + select.scan = left_id; + return Ok(true); + } + if !Select::add_one_of(left_id, select, sp, &[Select::add_inner_join])? { + return Err(SbroadError::Invalid( + Entity::SyntaxPlan, + Some(format!( + "expected InnerJoin or Scan after Selection. Got {plan_node_left:?}" + )), + )); + } + Ok(true) + } else { + Ok(false) + } + } + + fn add_groupby(select: &mut Select, id: usize, sp: &SyntaxPlan) -> Result<bool, SbroadError> { + let sn = sp.nodes.get_syntax_node(id)?; + if let Node::Relational(Relational::GroupBy { .. }) = sp.plan_node_or_err(&sn.data)? { + select.groupby = Some(id); + let left_id = sn.left_id_or_err()?; + let sn_left = sp.nodes.get_syntax_node(left_id)?; + let plan_node_left = sp.plan_node_or_err(&sn_left.data)?; + if let Node::Relational( + Relational::ScanRelation { .. } + | Relational::ScanSubQuery { .. } + | Relational::Motion { .. }, + ) = plan_node_left + { + select.scan = left_id; + return Ok(true); + } + if !Select::add_one_of( + left_id, + select, + sp, + &[Select::add_selection, Select::add_inner_join], + )? { + return Err(SbroadError::Invalid( + Entity::SyntaxPlan, + Some(format!( + "expected Scan or InnerJoin, or Selection after GroupBy. Got {plan_node_left:?}" + )))); + } + Ok(true) + } else { + Ok(false) + } + } + /// Constructor. /// - /// There are four valid combinations of the `SELECT` command: + /// There are several valid combinations of the `SELECT` command: /// - projection -> selection -> join -> scan + /// - projection -> groupby -> selection -> join -> scan /// - projection -> join -> scan + /// - projection -> groupby -> join -> scan /// - projection -> selection -> scan + /// - projection -> groupby -> selection -> scan /// - projection -> scan + /// - projection -> groupby -> scan fn new( sp: &SyntaxPlan, parent: Option<usize>, @@ -328,122 +486,45 @@ impl Select { id: usize, ) -> Result<Option<Select>, SbroadError> { let sn = sp.nodes.get_syntax_node(id)?; - // Expecting projection - // projection -> ... if let Some(Node::Relational(Relational::Projection { .. })) = sp.get_plan_node(&sn.data)? { - } else { - return Ok(None); - } - let left_id = sn.left_id_or_err()?; - let sn_left = sp.nodes.get_syntax_node(left_id)?; - let plan_node_left = sp.plan_node_or_err(&sn_left.data)?; - - match plan_node_left { - // Expecting projection over selection - // projection -> selection -> ... - Node::Relational(Relational::Selection { .. }) => { - let next_left_id = sn_left.left_id_or_err()?; - let sn_next_left = sp.nodes.get_syntax_node(next_left_id)?; - let plan_node_next_left = sp.plan_node_or_err(&sn_next_left.data)?; - - match plan_node_next_left { - // Expecting selection over join - // projection -> selection -> join -> ... - Node::Relational(Relational::InnerJoin { .. }) => { - let next_next_left_id = sn_next_left.left_id_or_err()?; - let sn_next_next_left = sp.nodes.get_syntax_node(next_next_left_id)?; - let plan_node_next_next_left = - sp.plan_node_or_err(&sn_next_next_left.data)?; - - // Expecting join over scan - // projection -> selection -> join -> scan - if let Node::Relational( - Relational::ScanRelation { .. } | Relational::ScanSubQuery { .. }, - ) = plan_node_next_next_left - { - let select = Select { - parent, - branch, - proj: id, - scan: next_next_left_id, - selection: Some(left_id), - join: Some(next_left_id), - }; - return Ok(Some(select)); - } - } - // Expecting selection over scan - // projection -> selection -> scan - Node::Relational( - Relational::ScanRelation { .. } | Relational::ScanSubQuery { .. }, - ) => { - return Ok(Some(Select { - parent, - branch, - proj: id, - scan: next_left_id, - selection: Some(left_id), - join: None, - })); - } - _ => { - return Err(SbroadError::Invalid( - Entity::Plan, - Some( - "current node must be InnerJoin, ScanSubQuery or ScanRelation" - .into(), - ), - )); - } - } - } - // Expecting projection over scan - // projection -> scan - Node::Relational(Relational::ScanRelation { .. } | Relational::ScanSubQuery { .. }) => { - return Ok(Some(Select { - parent, - branch, - proj: id, - scan: left_id, - selection: None, - join: None, - })); - } - // Expecting projection over inner join - // projection -> join -> ... - Node::Relational(Relational::InnerJoin { .. }) => { - let next_left_id = sn_left.left_id_or_err()?; - let sn_next_left = sp.nodes.get_syntax_node(next_left_id)?; - let plan_node_next_left = sp.plan_node_or_err(&sn_next_left.data)?; - - // Expecting join over scan - // projection -> join -> scan - if let Node::Relational( - Relational::ScanRelation { .. } | Relational::ScanSubQuery { .. }, - ) = plan_node_next_left - { - let select = Select { - parent, - branch, - proj: id, - scan: next_left_id, - selection: None, - join: Some(left_id), - }; - return Ok(Some(select)); - } - } - _ => { + let mut select = Select { + parent, + branch, + proj: id, + scan: 0, + selection: None, + join: None, + groupby: None, + }; + let left_id = sn.left_id_or_err()?; + let sn_left = sp.nodes.get_syntax_node(left_id)?; + let plan_node_left = sp.plan_node_or_err(&sn_left.data)?; + if let Node::Relational( + Relational::ScanRelation { .. } | Relational::ScanSubQuery { .. }, + ) = plan_node_left + { + select.scan = left_id; + } else if !Select::add_one_of( + left_id, + &mut select, + sp, + &[ + Select::add_selection, + Select::add_inner_join, + Select::add_groupby, + ], + )? { return Err(SbroadError::Invalid( - Entity::Plan, - Some("current node must be Selection, ScanRelation, or InnerJoin".into()), - )) + Entity::SyntaxPlan, + Some(format!( + "expected Scan, InnerJoin, Selection, GroupBy after Projection. Got {plan_node_left:?}" + )))); + } + if select.scan != 0 { + return Ok(Some(select)); } } - Err(SbroadError::Invalid( - Entity::Plan, - Some("invalid combination of the select command".into()), - )) + Ok(None) } } @@ -597,6 +678,27 @@ impl<'p> SyntaxPlan<'p> { Err(SbroadError::Invalid(Entity::Node, None)) } Relational::ScanSubQuery { .. } => self.nodes.add_sq(rel, id), + Relational::GroupBy { + children, gr_cols, .. + } => { + let left_id = *children.first().ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues("GroupBy has no children.".into()) + })?; + let mut right: Vec<usize> = Vec::with_capacity(gr_cols.len() * 2); + if let Some((last, other)) = gr_cols.split_last() { + for col_id in other { + right.push(self.nodes.get_syntax_node_id(*col_id)?); + right.push(self.nodes.push_syntax_node(SyntaxNode::new_comma())); + } + right.push(self.nodes.get_syntax_node_id(*last)?); + } + let sn = SyntaxNode::new_pointer( + id, + Some(self.nodes.get_syntax_node_id(left_id)?), + right, + ); + Ok(self.nodes.push_syntax_node(sn)) + } Relational::Selection { children, filter, .. } => { @@ -659,7 +761,10 @@ impl<'p> SyntaxPlan<'p> { ]); if let Some(name) = vtable_alias { - children.push(self.nodes.push_syntax_node(SyntaxNode::new_alias(name))); + if !name.is_empty() { + children + .push(self.nodes.push_syntax_node(SyntaxNode::new_alias(name))); + } } let sn = SyntaxNode::new_pointer(id, None, children); return Ok(self.nodes.push_syntax_node(sn)); @@ -939,7 +1044,14 @@ impl<'p> SyntaxPlan<'p> { fn gather_selects(&self) -> Result<Option<Vec<Select>>, SbroadError> { let mut selects: Vec<Select> = Vec::new(); let top = self.get_top()?; - for (pos, node) in self.nodes.arena.iter().enumerate() { + let mut dfs = PostOrder::with_capacity( + |node| self.nodes.iter(node), + self.plan.get_ir_plan().nodes.len(), + ); + dfs.populate_nodes(top); + let nodes = dfs.take_nodes(); + for (_, pos) in nodes { + let node = self.nodes.get_syntax_node(pos)?; if pos == top { let select = Select::new(self, None, None, pos)?; if let Some(s) = select { @@ -1036,59 +1148,31 @@ impl<'p> SyntaxPlan<'p> { Ok(sp) } - /// Reorder `SELECT` chain to: - /// - /// parent (if some) -branch-> selection (if some) -left-> - /// join (if some) -left-> scan -left-> projection - /// - /// # Errors - /// - select nodes (parent, scan, projection, selection) are invalid fn reorder(&mut self, select: &Select) -> Result<(), SbroadError> { // Move projection under scan. let mut proj = self.nodes.get_mut_syntax_node(select.proj)?; + let new_top = proj.left.ok_or_else(|| { + SbroadError::Invalid( + Entity::SyntaxPlan, + Some("Proj syntax node does not have left child!".into()), + ) + })?; proj.left = None; let mut scan = self.nodes.get_mut_syntax_node(select.scan)?; scan.left = Some(select.proj); - let mut top = select.scan; - - if let Some(id) = select.selection { - let mut selection = self.nodes.get_mut_syntax_node(id)?; - - match select.join { - // Try to move join under selection. - Some(join_id) => { - selection.left = Some(join_id); - // Try to move scan under join. - let mut join = self.nodes.get_mut_syntax_node(join_id)?; - join.left = Some(top); - } - // Try to move scan under selection. - None => { - selection.left = Some(top); - } - } - top = id; - } else { - // Try to move scan under join. - if let Some(join_id) = select.join { - let mut join = self.nodes.get_mut_syntax_node(join_id)?; - join.left = Some(top); - top = join_id; - } - } // Try to move new top under parent. if let Some(id) = select.parent { let mut parent = self.nodes.get_mut_syntax_node(id)?; match select.branch { Some(Branch::Left) => { - parent.left = Some(top); + parent.left = Some(new_top); } Some(Branch::Right) => { let mut found: bool = false; for child in &mut parent.right { if child == &select.proj { - *child = top; + *child = new_top; found = true; } } @@ -1113,7 +1197,7 @@ impl<'p> SyntaxPlan<'p> { // Update the syntax plan top if it was current projection if self.get_top()? == select.proj { - self.set_top(top)?; + self.set_top(new_top)?; } Ok(()) diff --git a/sbroad-core/src/executor/bucket.rs b/sbroad-core/src/executor/bucket.rs index 85b93a474fbd59885e034f6977dd96ad959a0677..39a832057854f0498873d2c5a0f3fddb93097789 100644 --- a/sbroad-core/src/executor/bucket.rs +++ b/sbroad-core/src/executor/bucket.rs @@ -262,6 +262,9 @@ where | Relational::Projection { children, output, .. } + | Relational::GroupBy { + children, output, .. + } | Relational::ScanSubQuery { children, output, .. } => { diff --git a/sbroad-core/src/executor/bucket/tests.rs b/sbroad-core/src/executor/bucket/tests.rs index af7d43fd410ece1ae82e23c175e03170efa03641..8a3df1bfa7875f871fb7b9cfc2df147748ed27de 100644 --- a/sbroad-core/src/executor/bucket/tests.rs +++ b/sbroad-core/src/executor/bucket/tests.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use crate::executor::bucket::Buckets; use crate::executor::engine::mock::RouterRuntimeMock; use crate::executor::engine::Coordinator; + use crate::executor::Query; use crate::ir::helpers::RepeatableState; use crate::ir::value::Value; diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs index 99eee4361f48f6d33d5be4baa6d9f8b4ba0f8c09..89bdb826ea316c060053e89d96e7bb83798b2a3b 100644 --- a/sbroad-core/src/executor/ir.rs +++ b/sbroad-core/src/executor/ir.rs @@ -143,6 +143,7 @@ impl ExecutionPlan { match rel { Relational::ScanSubQuery { .. } => self.get_subquery_child(*top_id), Relational::Except { .. } + | Relational::GroupBy { .. } | Relational::InnerJoin { .. } | Relational::Projection { .. } | Relational::ScanRelation { .. } @@ -312,6 +313,21 @@ impl ExecutionPlan { } } + if let Relational::GroupBy { gr_cols, .. } = rel { + let mut new_cols: Vec<usize> = Vec::with_capacity(gr_cols.len()); + for col_id in gr_cols.iter() { + let new_col_id = *translation.get(col_id).ok_or_else(|| { + SbroadError::NotFound( + Entity::Node, + format!("grouping column {col_id} in translation map"), + ) + })?; + new_plan.replace_parent_in_subtree(new_col_id, None, Some(next_id))?; + new_cols.push(new_col_id); + } + *gr_cols = new_cols; + } + let output = rel.output(); *rel.mut_output() = *translation.get(&output).ok_or_else(|| { SbroadError::NotFound( diff --git a/sbroad-core/src/executor/tests.rs b/sbroad-core/src/executor/tests.rs index e474d55af3f52a83081a4752abc28ab9b45b52fb..47fc7cde8f84e5f24441165b5fe5bc7475020da9 100644 --- a/sbroad-core/src/executor/tests.rs +++ b/sbroad-core/src/executor/tests.rs @@ -1,12 +1,14 @@ use pretty_assertions::assert_eq; use crate::backend::sql::ir::PatternWithParams; + use crate::executor::engine::mock::RouterRuntimeMock; use crate::executor::result::ProducerResult; use crate::executor::vtable::VirtualTable; use crate::ir::operator::Relational; use crate::ir::relation::{Column, ColumnRole, Type}; use crate::ir::transformation::redistribution::{DataGeneration, MotionPolicy}; + use crate::ir::value::{EncodedValue, Value}; use super::*; @@ -1521,6 +1523,75 @@ pub(crate) fn broadcast_check(sql: &str, pattern: &str, params: Vec<Value>) { assert_eq!(expected, result); } +#[test] +fn groupby_linker_test() { + let sql = r#"SELECT t1."id" as "ii" FROM "test_space" as t1 group by t1."id""#; + + let coordinator = RouterRuntimeMock::new(); + + let mut query = Query::new(&coordinator, sql, vec![]).unwrap(); + + let motion_id = *query + .exec_plan + .get_ir_plan() + .clone_slices() + .slice(0) + .unwrap() + .position(0) + .unwrap(); + let top_id = query.exec_plan.get_motion_subtree_root(motion_id).unwrap(); + if Buckets::All != query.bucket_discovery(top_id).unwrap() { + panic!("Expected Buckets::All for local groupby") + }; + let mut virtual_t1 = VirtualTable::new(); + virtual_t1.add_column(Column { + name: "id".into(), + r#type: Type::Integer, + role: ColumnRole::User, + }); + + let mut buckets: Vec<u64> = vec![]; + let tuples: Vec<Vec<Value>> = vec![vec![Value::from(1_u64)], vec![Value::from(2_u64)]]; + + for tuple in tuples.iter() { + virtual_t1.add_tuple(tuple.clone()); + let mut ref_tuple: Vec<&Value> = Vec::with_capacity(tuple.len()); + for v in tuple.iter() { + ref_tuple.push(v); + } + buckets.push(coordinator.determine_bucket_id(&ref_tuple)); + } + + virtual_t1.set_alias("").unwrap(); + + query.coordinator.add_virtual_table(motion_id, virtual_t1); + + let result = *query + .dispatch() + .unwrap() + .downcast::<ProducerResult>() + .unwrap(); + + let mut expected = ProducerResult::new(); + for buc in buckets { + expected.rows.extend(vec![vec![ + EncodedValue::String(format!("Execute query on a bucket [{buc}]")), + EncodedValue::String(String::from(PatternWithParams::new( + format!( + "{} {} {}", + r#"SELECT "id" as "ii" FROM (SELECT"#, + r#""id" FROM "TMP_test_37")"#, + r#"GROUP BY "T1"."id""#, + ), + vec![], + ))), + ]]); + } + + expected.rows.sort_by_key(|k| k[0].to_string()); + assert_eq!(expected, result); +} + #[cfg(test)] mod between; diff --git a/sbroad-core/src/executor/tests/subtree.rs b/sbroad-core/src/executor/tests/subtree.rs index 807dd886ea732824f42269953470c2b3decd0ccb..a50b6592275f08dc8150e1a06cc0ccdc059eb229 100644 --- a/sbroad-core/src/executor/tests/subtree.rs +++ b/sbroad-core/src/executor/tests/subtree.rs @@ -65,3 +65,155 @@ fn exec_plan_subtree_test() { vec![] )); } + +#[test] +fn exec_plan_subtree_two_stage_groupby_test() { + let sql = r#"SELECT t1."FIRST_NAME" FROM "test_space" as t1 group by t1."FIRST_NAME""#; + let coordinator = RouterRuntimeMock::new(); + + let mut query = Query::new(&coordinator, sql, vec![]).unwrap(); + let motion_id = *query + .exec_plan + .get_ir_plan() + .clone_slices() + .slice(0) + .unwrap() + .position(0) + .unwrap(); + + let mut virtual_table = VirtualTable::new(); + virtual_table.add_column(Column { + name: "FIRST_NAME".into(), + r#type: Type::String, + role: ColumnRole::User, + }); + virtual_table.set_alias("").unwrap(); + + if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id) + { + query + .reshard_vtable(&mut virtual_table, key, &DataGeneration::None) + .unwrap(); + } + + let mut vtables: HashMap<usize, Rc<VirtualTable>> = HashMap::new(); + vtables.insert(motion_id, Rc::new(virtual_table)); + + let exec_plan = query.get_mut_exec_plan(); + exec_plan.set_vtables(vtables); + let top_id = exec_plan.get_ir_plan().get_top().unwrap(); + let motion_child_id = exec_plan.get_motion_subtree_root(motion_id).unwrap(); + if let MotionPolicy::Segment(_) = exec_plan.get_motion_policy(motion_id).unwrap() { + } else { + panic!("Expected MotionPolicy::Segment for local aggregation stage"); + }; + + // Check groupby local stage + let subplan1 = exec_plan.take_subtree(motion_child_id).unwrap(); + let subplan1_top_id = subplan1.get_ir_plan().get_top().unwrap(); + let sp = SyntaxPlan::new(&subplan1, subplan1_top_id, Snapshot::Oldest).unwrap(); + let ordered = OrderedSyntaxNodes::try_from(sp).unwrap(); + let nodes = ordered.to_syntax_data().unwrap(); + let (sql, _) = subplan1.to_sql(&nodes, &Buckets::All, "test").unwrap(); + assert_eq!( + sql, + PatternWithParams::new( + r#"SELECT "T1"."FIRST_NAME" FROM "test_space" as "T1" GROUP BY "T1"."FIRST_NAME""# + .to_string(), + vec![] + ) + ); + + // Check main query + let subplan2 = exec_plan.take_subtree(top_id).unwrap(); + let subplan2_top_id = subplan2.get_ir_plan().get_top().unwrap(); + let sp = SyntaxPlan::new(&subplan2, subplan2_top_id, Snapshot::Oldest).unwrap(); + let ordered = OrderedSyntaxNodes::try_from(sp).unwrap(); + let nodes = ordered.to_syntax_data().unwrap(); + let (sql, _) = subplan2.to_sql(&nodes, &Buckets::All, "test").unwrap(); + assert_eq!( + sql, + PatternWithParams::new( + r#"SELECT "FIRST_NAME" FROM (SELECT "FIRST_NAME" FROM "TMP_test_6") GROUP BY "FIRST_NAME""#.to_string(), + vec![] + )); +} + +#[test] +fn exec_plan_subtree_two_stage_groupby_test_2() { + let sql = r#"SELECT t1."FIRST_NAME" as i1, t1."sys_op" as i2 FROM "test_space" as t1 group by t1."FIRST_NAME", t1."sys_op", t1."sysFrom""#; + let coordinator = RouterRuntimeMock::new(); + + let mut query = Query::new(&coordinator, sql, vec![]).unwrap(); + let motion_id = *query + .exec_plan + .get_ir_plan() + .clone_slices() + .slice(0) + .unwrap() + .position(0) + .unwrap(); + let mut virtual_table = VirtualTable::new(); + virtual_table.add_column(Column { + name: "FIRST_NAME".into(), + r#type: Type::String, + role: ColumnRole::User, + }); + virtual_table.add_column(Column { + name: "sys_op".into(), + r#type: Type::Integer, + role: ColumnRole::User, + }); + virtual_table.add_column(Column { + name: "sysFrom".into(), + r#type: Type::Integer, + role: ColumnRole::User, + }); + virtual_table.set_alias("").unwrap(); + if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id) + { + query + .reshard_vtable(&mut virtual_table, key, &DataGeneration::None) + .unwrap(); + } + + let mut vtables: HashMap<usize, Rc<VirtualTable>> = HashMap::new(); + vtables.insert(motion_id, Rc::new(virtual_table)); + + let exec_plan = query.get_mut_exec_plan(); + exec_plan.set_vtables(vtables); + let top_id = exec_plan.get_ir_plan().get_top().unwrap(); + let motion_child_id = exec_plan.get_motion_subtree_root(motion_id).unwrap(); + + // Check groupby local stage + let subplan1 = exec_plan.take_subtree(motion_child_id).unwrap(); + let subplan1_top_id = subplan1.get_ir_plan().get_top().unwrap(); + let sp = SyntaxPlan::new(&subplan1, subplan1_top_id, Snapshot::Oldest).unwrap(); + let ordered = OrderedSyntaxNodes::try_from(sp).unwrap(); + let nodes = ordered.to_syntax_data().unwrap(); + let (sql, _) = subplan1.to_sql(&nodes, &Buckets::All, "test").unwrap(); + if let MotionPolicy::Segment(_) = exec_plan.get_motion_policy(motion_id).unwrap() { + } else { + panic!("Expected MotionPolicy::Segment for local aggregation stage"); + }; + assert_eq!( + sql, + PatternWithParams::new( + r#"SELECT "T1"."FIRST_NAME", "T1"."sys_op", "T1"."sysFrom" FROM "test_space" as "T1" GROUP BY "T1"."FIRST_NAME", "T1"."sys_op", "T1"."sysFrom""#.to_string(), + vec![] + )); + + // Check main query + let subplan2 = exec_plan.take_subtree(top_id).unwrap(); + let subplan2_top_id = subplan2.get_ir_plan().get_top().unwrap(); + let sp = SyntaxPlan::new(&subplan2, subplan2_top_id, Snapshot::Oldest).unwrap(); + let ordered = OrderedSyntaxNodes::try_from(sp).unwrap(); + let nodes = ordered.to_syntax_data().unwrap(); + let (sql, _) = subplan2.to_sql(&nodes, &Buckets::All, "test").unwrap(); + assert_eq!( + sql, + PatternWithParams::new( + r#"SELECT "FIRST_NAME" as "I1", "sys_op" as "I2" FROM (SELECT "FIRST_NAME","sys_op","sysFrom" FROM "TMP_test_12") GROUP BY "FIRST_NAME", "sys_op", "sysFrom""#.to_string(), + vec![] + )); +} diff --git a/sbroad-core/src/executor/vtable.rs b/sbroad-core/src/executor/vtable.rs index b1b2a8de065ef86efb9e8014bb9265fb0376bf19..d4fb28c795eded8c56a89071420321c352976da7 100644 --- a/sbroad-core/src/executor/vtable.rs +++ b/sbroad-core/src/executor/vtable.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::fmt::{Display, Formatter}; use std::rc::Rc; use std::vec; @@ -59,6 +60,19 @@ impl Default for VirtualTable { } } +impl Display for VirtualTable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for col in &self.columns { + write!(f, "{col:?}, ")?; + } + writeln!(f)?; + for row in &self.tuples { + writeln!(f, "{row:?}")?; + } + writeln!(f) + } +} + impl VirtualTable { #[must_use] pub fn new() -> Self { @@ -195,13 +209,6 @@ impl VirtualTable { /// # Errors /// - Try to set an empty alias name to the virtual table. pub fn set_alias(&mut self, name: &str) -> Result<(), SbroadError> { - if name.is_empty() { - return Err(SbroadError::Invalid( - Entity::Value, - Some("can't set empty alias for virtual table".into()), - )); - } - self.name = Some(String::from(name)); Ok(()) } diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 1cb30778ab1baaa40bc50af917614c1449a46e5b..9fa9511f354eb08c76bfdc769bd9345cd3c071c8 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -87,6 +87,7 @@ impl Ast for AbstractSyntaxTree { // Update parent's node children list ast.nodes.add_child(stack_node.parent, node)?; + // Clean parent values (only leafs should contain data) if let Some(parent) = stack_node.parent { ast.nodes.update_value(parent, None)?; @@ -128,6 +129,10 @@ impl Ast for AbstractSyntaxTree { let mut rows: HashSet<usize> = HashSet::with_capacity(self.nodes.next_id()); let mut col_idx: usize = 0; + let mut groupby_nodes: Vec<usize> = Vec::new(); + let mut scan_nodes: Vec<usize> = Vec::new(); + let mut sq_nodes: Vec<usize> = Vec::new(); + let mut betweens: Vec<Between> = Vec::new(); let mut arithmetic_expression_ids: Vec<usize> = Vec::new(); @@ -239,6 +244,7 @@ impl Ast for AbstractSyntaxTree { let t = metadata.get_table_segment(table)?; plan.add_rel(t); let scan_id = plan.add_scan(&normalize_name_from_sql(table), None)?; + scan_nodes.push(scan_id); map.add(id, scan_id); } else { return Err(SbroadError::Invalid( @@ -272,6 +278,7 @@ impl Ast for AbstractSyntaxTree { None }; let plan_sq_id = plan.add_sub_query(plan_child_id, alias_name.as_deref())?; + sq_nodes.push(plan_sq_id); map.add(id, plan_sq_id); } Type::Reference => { @@ -726,6 +733,21 @@ impl Ast for AbstractSyntaxTree { )); } } + Type::GroupBy => { + if node.children.len() < 2 { + return Err(SbroadError::UnexpectedNumberOfValues( + "Group by must have at least 2 children.".into(), + )); + } + let mut children: Vec<usize> = Vec::with_capacity(node.children.len()); + for ast_column_id in &node.children { + let plan_column_id = map.get(*ast_column_id)?; + children.push(plan_column_id); + } + let groupby_id = plan.add_groupby(&children)?; + groupby_nodes.push(groupby_id); + map.add(id, groupby_id); + } Type::InnerJoin => { let ast_left_id = node.children.first().ok_or_else(|| { SbroadError::UnexpectedNumberOfValues("Join has no children.".into()) @@ -861,6 +883,9 @@ impl Ast for AbstractSyntaxTree { } } let projection_id = plan.add_proj_internal(plan_child_id, &columns)?; + if let Some(groupby_id) = groupby_nodes.pop() { + plan.add_two_stage_aggregation(groupby_id)?; + } map.add(id, projection_id); } Type::Multiplication | Type::Addition => { diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs index 8c9afc082508c885c0582cdeafc9fbd19973ef72..00fe9dbd19d0581d223996b1aeb0c073c2746e85 100644 --- a/sbroad-core/src/frontend/sql/ast.rs +++ b/sbroad-core/src/frontend/sql/ast.rs @@ -51,6 +51,8 @@ pub enum Type { FunctionName, Gt, GtEq, + GroupBy, + GroupingElement, In, InnerJoin, Insert, @@ -130,6 +132,8 @@ impl Type { Rule::False => Ok(Type::False), Rule::Function => Ok(Type::Function), Rule::FunctionName => Ok(Type::FunctionName), + Rule::GroupBy => Ok(Type::GroupBy), + Rule::GroupingElement => Ok(Type::GroupingElement), Rule::Gt => Ok(Type::Gt), Rule::GtEq => Ok(Type::GtEq), Rule::In => Ok(Type::In), @@ -266,6 +270,8 @@ impl fmt::Display for Type { Type::Value => "Value".to_string(), Type::Values => "Values".to_string(), Type::ValuesRow => "ValuesRow".to_string(), + Type::GroupBy => "GroupBy".to_string(), + Type::GroupingElement => "GroupingElement".to_string(), }; write!(f, "{p}") } @@ -335,6 +341,40 @@ impl ParseNodes { id } + /// Push `child_id` to the front of `node_id` children + /// + /// # Errors + /// - Failed to get node from arena + pub fn push_front_child(&mut self, node_id: usize, child_id: usize) -> Result<(), SbroadError> { + let node = self.get_mut_node(node_id)?; + node.children.insert(0, child_id); + Ok(()) + } + + /// Push `child_id` to the back of `node_id` children + /// + /// # Errors + /// - Failed to get node from arena + pub fn push_back_child(&mut self, node_id: usize, child_id: usize) -> Result<(), SbroadError> { + let node = self.get_mut_node(node_id)?; + node.children.push(child_id); + Ok(()) + } + + /// Sets node children to given children + /// + /// # Errors + /// - failed to get node from arena + pub fn set_children( + &mut self, + node_id: usize, + new_children: Vec<usize>, + ) -> Result<(), SbroadError> { + let node = self.get_mut_node(node_id)?; + node.children = new_children; + Ok(()) + } + /// Get next node id #[must_use] pub fn next_id(&self) -> usize { @@ -410,6 +450,22 @@ impl PartialEq for AbstractSyntaxTree { } } +/// Helper function to extract i-th element of array, when we sure it is safe +/// But we don't want to panic if future changes break something, so we +/// bubble out with error. +/// +/// Supposed to be used only in `transform_select_X` methods! +#[inline] +fn get_or_err(arr: &[usize], idx: usize) -> Result<usize, SbroadError> { + arr.get(idx) + .ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues(format!( + "AST children array: {arr:?}. Requested index: {idx}" + )) + }) + .map(|v| *v) +} + #[allow(dead_code)] impl AbstractSyntaxTree { /// Set the top of AST. @@ -467,6 +523,7 @@ impl AbstractSyntaxTree { 3 => self.transform_select_3(*node, &children)?, 4 => self.transform_select_4(*node, &children)?, 5 => self.transform_select_5(*node, &children)?, + 6 => self.transform_select_6(*node, &children)?, _ => return Err(SbroadError::Invalid(Entity::AST, None)), } } @@ -507,343 +564,195 @@ impl AbstractSyntaxTree { Ok(()) } - /// Transforms `Select` with `Projection` and `Scan` - fn transform_select_2( - &mut self, - select_id: usize, - children: &[usize], + fn check<const N: usize, const M: usize>( + &self, + allowed: &[[Type; N]; M], + select_children: &[usize], ) -> Result<(), SbroadError> { - if children.len() != 2 { + let allowed_len = if let Some(seq) = allowed.first() { + seq.len() + } else { + return Err(SbroadError::UnexpectedNumberOfValues( + "Expected at least one sequence to check select children".into(), + )); + }; + if select_children.len() != allowed_len { return Err(SbroadError::UnexpectedNumberOfValues(format!( - "expect children list len 2, got {}", - children.len() + "Expected select {allowed_len} children, got {}", + select_children.len() ))); } - - // Check that the second child is `Scan`. - let scan_id: usize = *children.get(1).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 1".into()) - })?; - let scan = self.nodes.get_node(scan_id)?; - if scan.rule != Type::Scan { - return Err(SbroadError::Invalid( - Entity::AST, - Some("scan.rule is not Scan type".into()), - )); + let mut is_match = false; + for seq in allowed { + let mut all_types_matched = true; + for (child, expected_type) in select_children.iter().zip(seq) { + let node = self.nodes.get_node(*child)?; + if node.rule != *expected_type { + all_types_matched = false; + break; + } + } + if all_types_matched { + is_match = true; + break; + } } - - // Check that the first child is `Projection`. - let proj_id: usize = *children.first().ok_or_else(|| { - SbroadError::UnexpectedNumberOfValues("children list is empty".into()) - })?; - let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {proj_id}"), - ) - })?; - if proj.rule != Type::Projection { + if !is_match { return Err(SbroadError::Invalid( Entity::AST, - Some("proj.rule is not Projection type".into()), + Some("Could not match select children to any expected sequence".into()), )); } + Ok(()) + } - // Append `Scan` to the `Projection` children (zero position) - proj.children.insert(0, scan_id); - - // Leave `Projection` the only child of `Select`. - let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {select_id}"), - ) - })?; - select.children = vec![proj_id]; - + fn transform_select_2( + &mut self, + select_id: usize, + children: &[usize], + ) -> Result<(), SbroadError> { + let allowed = [[Type::Projection, Type::Scan]]; + self.check(&allowed, children)?; + self.nodes + .push_front_child(get_or_err(children, 0)?, get_or_err(children, 1)?)?; + self.nodes.set_children(select_id, vec![children[0]])?; Ok(()) } - /// Transforms `Select` with `Projection`, `Scan` and `Selection`. fn transform_select_3( &mut self, select_id: usize, children: &[usize], ) -> Result<(), SbroadError> { - if children.len() != 3 { - return Err(SbroadError::UnexpectedNumberOfValues(format!( - "expect children list len 3, got {}", - children.len() - ))); - } - - // Check that the second child is `Scan`. - let scan_id: usize = *children.get(1).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 1".into()) - })?; - let scan = self.nodes.get_node(scan_id)?; - if scan.rule != Type::Scan { - return Err(SbroadError::Invalid( - Entity::AST, - Some("scan.rule is not Scan type".into()), - )); - } - - // Check that the third child is `Selection`. - let selection_id: usize = *children.get(2).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 2".into()) - })?; - let selection = self.nodes.arena.get_mut(selection_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {selection_id}"), - ) - })?; - if selection.rule != Type::Selection { - return Err(SbroadError::Invalid( - Entity::AST, - Some("selection.rule is not Selection type".into()), - )); - } - - // Append `Scan` to the `Selection` children (zero position) - selection.children.insert(0, scan_id); - - // Check that the first child is `Projection`. - let proj_id: usize = *children.first().ok_or_else(|| { - SbroadError::UnexpectedNumberOfValues("children list is empty".into()) - })?; - let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {proj_id}"), - ) - })?; - if proj.rule != Type::Projection { - return Err(SbroadError::Invalid( - Entity::AST, - Some("proj.rule is not Projection type".into()), - )); - } - - // Append `Selection` to the `Projection` children (zero position) - proj.children.insert(0, selection_id); - - // Leave `Projection` the only child of `Select`. - let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {select_id}"), - ) - })?; - select.children = vec![proj_id]; - + let allowed = [ + [Type::Projection, Type::Scan, Type::GroupBy], + [Type::Projection, Type::Scan, Type::Selection], + ]; + self.check(&allowed, children)?; + self.nodes + .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?; + self.nodes + .push_front_child(get_or_err(children, 0)?, get_or_err(children, 2)?)?; + self.nodes.set_children(select_id, vec![children[0]])?; Ok(()) } - /// Transforms `Select` with `Projection`, `Scan`, `InnerJoin` and `Condition` fn transform_select_4( &mut self, select_id: usize, children: &[usize], ) -> Result<(), SbroadError> { - if children.len() != 4 { - return Err(SbroadError::UnexpectedNumberOfValues(format!( - "expect children list len 4, got {}", - children.len() - ))); - } - - // Check that the second child is `Scan`. - let scan_id: usize = *children.get(1).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 1".into()) - })?; - let scan = self.nodes.get_node(scan_id)?; - if scan.rule != Type::Scan { - return Err(SbroadError::Invalid( - Entity::AST, - Some("scan.rule is not Scan type".into()), - )); - } - - // Check that the forth child is `Condition`. - let cond_id: usize = *children.get(3).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 3".into()) - })?; - let cond = self.nodes.get_node(cond_id)?; - if cond.rule != Type::Condition { - return Err(SbroadError::Invalid( - Entity::AST, - Some("cond.rule is not Condition type".into()), - )); - } - - // Check that the third child is `InnerJoin`. - let join_id: usize = *children - .get(2) - .ok_or_else(|| SbroadError::NotFound(Entity::Node, "with index 2".into()))?; - let join = self.nodes.arena.get_mut(join_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {join_id}"), - ) - })?; - if join.rule != Type::InnerJoin { - return Err(SbroadError::Invalid( - Entity::AST, - Some("join.rule is not InnerJoin type".into()), - )); - } - - // Push `Condition` (forth child) to the end of th `InnerJoin` children list. - join.children.push(cond_id); - - // Append `Scan` to the `InnerJoin` children (zero position) - join.children.insert(0, scan_id); - - // Check that the first child is `Projection`. - let proj_id: usize = *children.first().ok_or_else(|| { - SbroadError::UnexpectedNumberOfValues("children list is empty".into()) - })?; - let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {proj_id}"), - ) - })?; - if proj.rule != Type::Projection { - return Err(SbroadError::Invalid( - Entity::AST, - Some("proj.rule is not Projection type".into()), - )); + let allowed = [ + [ + Type::Projection, + Type::Scan, + Type::InnerJoin, + Type::Condition, + ], + [Type::Projection, Type::Scan, Type::Selection, Type::GroupBy], + ]; + self.check(&allowed, children)?; + match self.nodes.get_node(children[2])?.rule { + Type::InnerJoin => { + // insert Scan as first child of InnerJoin + self.nodes + .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?; + // push Condition as last child of InnerJoin + self.nodes + .push_back_child(get_or_err(children, 2)?, get_or_err(children, 3)?)?; + // insert InnerJoin as first child of Projection + self.nodes + .push_front_child(get_or_err(children, 0)?, get_or_err(children, 2)?)?; + } + Type::Selection => { + // insert Selection as first child of GroupBy + self.nodes + .push_front_child(get_or_err(children, 3)?, get_or_err(children, 2)?)?; + // insert Scan as first child of Selection + self.nodes + .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?; + // insert GroupBy as first child of Projection + self.nodes + .push_front_child(get_or_err(children, 0)?, get_or_err(children, 3)?)?; + } + _ => return Err(SbroadError::Invalid(Entity::AST, None)), } - - // Append `InnerJoin` to the `Projection` children (zero position) - proj.children.insert(0, join_id); - - // Leave `Projection` the only child of `Select`. - let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {select_id}"), - ) - })?; - select.children = vec![proj_id]; - + self.nodes.set_children(select_id, vec![children[0]])?; Ok(()) } - /// Transforms `Select` with `Projection`, `Scan`, `InnerJoin`, `Condition` and `Selection` fn transform_select_5( &mut self, select_id: usize, children: &[usize], ) -> Result<(), SbroadError> { - if children.len() != 5 { - return Err(SbroadError::UnexpectedNumberOfValues(format!( - "expect children list len 5, got {}", - children.len() - ))); - } - - // Check that the second child is `Scan`. - let scan_id: usize = *children.get(1).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 1".into()) - })?; - let scan = self.nodes.get_node(scan_id)?; - if scan.rule != Type::Scan { - return Err(SbroadError::Invalid( - Entity::AST, - Some("scan.rule is not Scan type".into()), - )); - } - - // Check that the forth child is `Condition`. - let cond_id: usize = *children.get(3).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 3".into()) - })?; - let cond = self.nodes.get_node(cond_id)?; - if cond.rule != Type::Condition { - return Err(SbroadError::Invalid( - Entity::AST, - Some("cond.rule is not Condition type".into()), - )); - } - - // Check that the third child is `InnerJoin`. - let join_id: usize = *children.get(2).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 2".into()) - })?; - let join = self.nodes.arena.get_mut(join_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {join_id}"), - ) - })?; - if join.rule != Type::InnerJoin { - return Err(SbroadError::Invalid( - Entity::AST, - Some("join.rule is not InnerJoin type".into()), - )); - } - - // Push `Condition` (forth child) to the end of the `InnerJoin` children list. - join.children.push(cond_id); - - // Append `Scan` to the `InnerJoin` children (zero position) - join.children.insert(0, scan_id); - - // Check that the fifth child is `Selection`. - let selection_id: usize = *children.get(4).ok_or_else(|| { - SbroadError::NotFound(Entity::Node, "from children list with index 4".into()) - })?; - let selection = self.nodes.arena.get_mut(selection_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {selection_id}"), - ) - })?; - if selection.rule != Type::Selection { - return Err(SbroadError::Invalid( - Entity::AST, - Some("selection.rule is not Selection type".into()), - )); - } - - // Append `InnerJoin` to the `Selection` children (zero position) - selection.children.insert(0, join_id); - - // Check that the first child is `Projection`. - let proj_id: usize = *children.first().ok_or_else(|| { - SbroadError::UnexpectedNumberOfValues("children list is empty".into()) - })?; - let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {proj_id}"), - ) - })?; - if proj.rule != Type::Projection { - return Err(SbroadError::Invalid( - Entity::AST, - Some("proj.rule is not Projection type".into()), - )); - } - - // Append `Selection` to the `Projection` children (zero position) - proj.children.insert(0, selection_id); + let allowed = [ + [ + Type::Projection, + Type::Scan, + Type::InnerJoin, + Type::Condition, + Type::Selection, + ], + [ + Type::Projection, + Type::Scan, + Type::InnerJoin, + Type::Condition, + Type::GroupBy, + ], + ]; + self.check(&allowed, children)?; + // insert InnerJoin as first child of Selection | GroupBy + self.nodes + .push_front_child(get_or_err(children, 4)?, get_or_err(children, 2)?)?; + // insert Scan as first child of InnerJoin + self.nodes + .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?; + // push back condition as last child of InnerJoin + self.nodes + .push_back_child(get_or_err(children, 2)?, get_or_err(children, 3)?)?; + // insert GroupBy | Selection as first child of Projection + self.nodes + .push_front_child(get_or_err(children, 0)?, get_or_err(children, 4)?)?; + self.nodes.set_children(select_id, vec![children[0]])?; + Ok(()) + } - // Leave `Projection` the only child of `Select`. - let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - format!("(mutable) from arena with index {select_id}"), - ) - })?; - select.children = vec![proj_id]; + fn transform_select_6( + &mut self, + select_id: usize, + children: &[usize], + ) -> Result<(), SbroadError> { + let allowed = [[ + Type::Projection, + Type::Scan, + Type::InnerJoin, + Type::Condition, + Type::Selection, + Type::GroupBy, + ]]; + self.check(&allowed, children)?; + // insert Selection as first child of GroupBy + self.nodes + .push_front_child(get_or_err(children, 5)?, get_or_err(children, 4)?)?; + // insert InnerJoin as first child of Selection + self.nodes + .push_front_child(get_or_err(children, 4)?, get_or_err(children, 2)?)?; + // insert Scan as first child fo InnerJoin + self.nodes + .push_front_child(get_or_err(children, 2)?, get_or_err(children, 1)?)?; + // push back Condition as last child of InnerJoin + self.nodes + .push_back_child(get_or_err(children, 2)?, get_or_err(children, 3)?)?; + // insert GroupBy as first child of Projection + self.nodes + .push_front_child(get_or_err(children, 0)?, get_or_err(children, 5)?)?; + self.nodes.set_children(select_id, vec![children[0]])?; Ok(()) } + #[allow(clippy::too_many_lines)] /// Add aliases to projection columns. /// /// # Errors @@ -931,6 +840,7 @@ impl AbstractSyntaxTree { Ok(()) } + #[allow(clippy::too_many_lines)] /// Map references to the corresponding relational nodes. /// /// # Errors @@ -1020,6 +930,25 @@ impl AbstractSyntaxTree { } } } + Type::GroupBy => { + let rel_id = rel_node.children.first().ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues( + "AST group by doesn't have any children.".into(), + ) + })?; + for top in rel_node.children.iter().skip(1) { + let mut subtree = + PostOrder::with_capacity(|node| self.nodes.ast_iter(node), capacity); + for (_, id) in subtree.iter(*top) { + let node = self.nodes.get_node(id)?; + if let Type::Reference = node.rule { + if let Entry::Vacant(entry) = map.entry(id) { + entry.insert(vec![*rel_id]); + } + } + } + } + } _ => continue, } } diff --git a/sbroad-core/src/frontend/sql/ast/tests.rs b/sbroad-core/src/frontend/sql/ast/tests.rs index f4f3c9fa2a53d7f26ffe0d841ac14a5b5958e655..663714d953adae938c71a8317e9269cc71fa5ce4 100644 --- a/sbroad-core/src/frontend/sql/ast/tests.rs +++ b/sbroad-core/src/frontend/sql/ast/tests.rs @@ -55,6 +55,21 @@ fn transform_select_3() { assert_eq!(expected, ast); } +#[test] +fn transform_select_3_group_by() { + let query = r#"select a from t group by a"#; + let ast = AbstractSyntaxTree::new(query).unwrap(); + let path = Path::new("") + .join("tests") + .join("artifactory") + .join("frontend") + .join("sql") + .join("transform_select_3_group_by.yaml"); + let s = fs::read_to_string(path).unwrap(); + let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap(); + assert_eq!(expected, ast); +} + #[test] fn transform_select_4() { let query = r#"select * from t1 inner join t2 on t1.a = t2.a"#; @@ -70,6 +85,21 @@ fn transform_select_4() { assert_eq!(expected, ast); } +#[test] +fn transform_select_4_1() { + let query = r#"select a, b from t1 where a > 1 group by a, b"#; + let ast = AbstractSyntaxTree::new(query).unwrap(); + let path = Path::new("") + .join("tests") + .join("artifactory") + .join("frontend") + .join("sql") + .join("transform_select_4_1.yaml"); + let s = fs::read_to_string(path).unwrap(); + let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap(); + assert_eq!(expected, ast); +} + #[test] fn transform_select_5() { let query = r#"select * from t1 inner join t2 on t1.a = t2.a where t1.a > 0"#; @@ -85,6 +115,36 @@ fn transform_select_5() { assert_eq!(expected, ast); } +#[test] +fn transform_select_5_1() { + let query = r#"select t1.a, t2.b from t1 inner join t2 on t1.a = t2.a group by t1.a, t2.b"#; + let ast = AbstractSyntaxTree::new(query).unwrap(); + let path = Path::new("") + .join("tests") + .join("artifactory") + .join("frontend") + .join("sql") + .join("transform_select_5_1.yaml"); + let s = fs::read_to_string(path).unwrap(); + let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap(); + assert_eq!(expected, ast); +} + +#[test] +fn transform_select_6() { + let query = r#"select t1.a, t2.b from t1 inner join t2 on t1.a = t2.a where t1.a > 1 group by t1.a, t2.b"#; + let ast = AbstractSyntaxTree::new(query).unwrap(); + let path = Path::new("") + .join("tests") + .join("artifactory") + .join("frontend") + .join("sql") + .join("transform_select_6.yaml"); + let s = fs::read_to_string(path).unwrap(); + let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap(); + assert_eq!(expected, ast); +} + #[test] fn traversal() { let query = r#"select a from t where a = 1"#; diff --git a/sbroad-core/src/frontend/sql/ir.rs b/sbroad-core/src/frontend/sql/ir.rs index e126b4ec8655f0ff73709cf4c800ab7da441620f..aa792d2461e42cfe05ea180fdadaca6539bd9790 100644 --- a/sbroad-core/src/frontend/sql/ir.rs +++ b/sbroad-core/src/frontend/sql/ir.rs @@ -352,7 +352,7 @@ impl Plan { Ok(()) } - fn clone_expr_subtree(&mut self, top_id: usize) -> Result<usize, SbroadError> { + pub(crate) fn clone_expr_subtree(&mut self, top_id: usize) -> Result<usize, SbroadError> { let mut map = HashMap::new(); let mut subtree = PostOrder::with_capacity(|node| self.nodes.expr_iter(node, false), EXPR_CAPACITY); diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs index 3c22ff39093ba35c301ab8c5d13272e8edcf6bc7..6f8d0bf45858ab6216243333d1f53a04f9a77baf 100644 --- a/sbroad-core/src/frontend/sql/ir/tests.rs +++ b/sbroad-core/src/frontend/sql/ir/tests.rs @@ -1,3 +1,6 @@ +use crate::executor::engine::mock::RouterConfigurationMock; +use crate::frontend::sql::ast::AbstractSyntaxTree; +use crate::frontend::Ast; use crate::ir::transformation::helpers::sql_to_optimized_ir; use pretty_assertions::assert_eq; @@ -359,6 +362,170 @@ fn front_sql20() { assert_eq!(expected_explain, plan.as_explain().unwrap()); } +#[test] +fn front_sql_groupby() { + let input = r#"SELECT "identification_number", "product_code" FROM "hash_testing" group by "identification_number", "product_code""#; + + let plan = sql_to_optimized_ir(input, vec![]); + let expected_explain = String::from( + r#"projection ("identification_number" -> "identification_number", "product_code" -> "product_code") + group by ("identification_number" -> "identification_number", "product_code" -> "product_code") + motion [policy: segment([ref("identification_number"), ref("product_code")]), generation: none] + scan + projection ("hash_testing"."identification_number" -> "identification_number", "hash_testing"."product_code" -> "product_code") + group by ("hash_testing"."identification_number" -> "identification_number", "hash_testing"."product_code" -> "product_code") + scan "hash_testing" +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_sql_groupby_less_cols_in_proj() { + // check case when we specify less columns than in groupby clause + let input = r#"SELECT "identification_number" FROM "hash_testing" + GROUP BY "identification_number", "product_units" + "#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"projection ("identification_number" -> "identification_number") + group by ("identification_number" -> "identification_number", "product_units" -> "product_units") + motion [policy: segment([ref("identification_number"), ref("product_units")]), generation: none] + scan + projection ("hash_testing"."identification_number" -> "identification_number", "hash_testing"."product_units" -> "product_units") + group by ("hash_testing"."identification_number" -> "identification_number", "hash_testing"."product_units" -> "product_units") + scan "hash_testing" +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_sql_groupby_union_1() { + let input = r#"SELECT "identification_number" FROM "hash_testing" + GROUP BY "identification_number" + UNION ALL + SELECT "identification_number" FROM "hash_testing""#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"union all + projection ("identification_number" -> "identification_number") + group by ("identification_number" -> "identification_number") + motion [policy: segment([ref("identification_number")]), generation: none] + scan + projection ("hash_testing"."identification_number" -> "identification_number") + group by ("hash_testing"."identification_number" -> "identification_number") + scan "hash_testing" + projection ("hash_testing"."identification_number" -> "identification_number") + scan "hash_testing" +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_sql_groupby_union_2() { + let input = r#"SELECT "identification_number" FROM "hash_testing" UNION ALL + SELECT * FROM (SELECT "identification_number" FROM "hash_testing" + GROUP BY "identification_number" + UNION ALL + SELECT "identification_number" FROM "hash_testing")"#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"union all + projection ("hash_testing"."identification_number" -> "identification_number") + scan "hash_testing" + projection ("identification_number" -> "identification_number") + scan + union all + projection ("identification_number" -> "identification_number") + group by ("identification_number" -> "identification_number") + motion [policy: segment([ref("identification_number")]), generation: none] + scan + projection ("hash_testing"."identification_number" -> "identification_number") + group by ("hash_testing"."identification_number" -> "identification_number") + scan "hash_testing" + projection ("hash_testing"."identification_number" -> "identification_number") + scan "hash_testing" +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_sql_groupby_join_1() { + // inner select is a kostyl because tables have the col sys_op + let input = r#"SELECT "product_code", "product_units" FROM (SELECT "product_units", "product_code", "identification_number" FROM "hash_testing") as t2 + INNER JOIN (SELECT "id" from "test_space") as t + ON t2."identification_number" = t."id" + group by t2."product_code", t2."product_units" + "#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"projection ("product_code" -> "product_code", "product_units" -> "product_units") + group by ("product_code" -> "product_code", "product_units" -> "product_units") + motion [policy: segment([ref("product_code"), ref("product_units")]), generation: none] + scan + projection ("T2"."product_code" -> "product_code", "T2"."product_units" -> "product_units") + group by ("T2"."product_code" -> "product_code", "T2"."product_units" -> "product_units") + join on ROW("T2"."identification_number") = ROW("T"."id") + scan "T2" + projection ("hash_testing"."product_units" -> "product_units", "hash_testing"."product_code" -> "product_code", "hash_testing"."identification_number" -> "identification_number") + scan "hash_testing" + motion [policy: full, generation: none] + scan "T" + projection ("test_space"."id" -> "id") + scan "test_space" +"#, + ); + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_sql_groupby_insert() { + let input = r#"INSERT INTO "t" ("a", "c") SELECT "b", "d" FROM "t" group by "b", "d""#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"insert "t" + motion [policy: segment([ref("b"), value(NULL)]), generation: sharding_column] + projection ("b" -> "b", "d" -> "d") + group by ("b" -> "b", "d" -> "d") + motion [policy: segment([ref("b"), ref("d")]), generation: none] + scan + projection ("t"."b" -> "b", "t"."d" -> "d") + group by ("t"."b" -> "b", "t"."d" -> "d") + scan "t" +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + +#[test] +fn front_sql_groupby_invalid() { + let input = r#"select "b", "a" from "t" group by "b""#; + + let metadata = &RouterConfigurationMock::new(); + let ast = AbstractSyntaxTree::new(input).unwrap(); + let plan = ast.resolve_metadata(metadata); + + assert_eq!(true, plan.is_err()); +} + #[test] fn front_sql_nested_subqueries() { let input = r#"SELECT "a" FROM "t" diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 981e6ef9061c1f581f23f96f2af16c976a346bb8..30df0dfb621dbfe2d01f51b4fdf15b5b29c192f3 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -7,7 +7,8 @@ Query = _{ Except | UnionAll | Select | Values | Insert } Select = { ^"select" ~ Projection ~ ^"from" ~ Scan ~ (((^"inner" ~ ^"join") | ^"join") ~ InnerJoin ~ - ^"on" ~ Condition)? ~ (^"where" ~ Selection)? + ^"on" ~ Condition)? ~ (^"where" ~ Selection)? ~ + (^"group" ~ ^"by" ~ GroupBy)? } Projection = { (Asterisk | ArithmeticExprAlias | Column) ~ ("," ~ (Asterisk | ArithmeticExprAlias | Column))*? } Column = { Alias | Value } @@ -24,6 +25,7 @@ Query = _{ Except | UnionAll | Select | Values | Insert } Table = @{ Name } InnerJoin = { Scan } Condition = { Expr } + GroupBy = { GroupingElement ~ ("," ~ GroupingElement)* } UnionAll = { (SubQuery | Select) ~ ^"union" ~ ^"all" ~ (SubQuery | Select) } Except = { (SubQuery | Select) ~ ((^"except" ~ ^"distinct") | ^"except") ~ (SubQuery | Select) } SubQuery = { "(" ~ (Except | UnionAll | Select | Values) ~ ")" } @@ -113,6 +115,7 @@ Concat = { ConcatLeft ~ ^"||" ~ ConcatRight } ConcatLeft = _{ SingleQuotedString | Cast | Function | Reference } ConcatRight = _{ Concat | ConcatLeft } +GroupingElement = _{ Concat | Cast | Function | Reference } NameLetters = _{ ('Ð' .. 'Я' | 'а' .. 'Ñ' | 'A' .. 'Z' | 'a'..'z' | "-" | "_") } NameString = @{ !(WHITESPACE* ~ Keyword ~ WHITESPACE) ~ ((NameLetters ~ (NameLetters | ASCII_DIGIT)+) | NameLetters+) } diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs index b1967797f77c93f6f44a23b3e536e15f0fac5418..2d4a9db0ac5719bc423108a171e9bc2be0088c81 100644 --- a/sbroad-core/src/ir.rs +++ b/sbroad-core/src/ir.rs @@ -4,6 +4,7 @@ use base64ct::{Base64, Encoding}; use serde::{Deserialize, Serialize}; + use std::slice::Iter; use expression::Expression; @@ -511,6 +512,35 @@ impl Plan { } } + /// Gets list of `Row` children ids + /// + /// # Errors + /// - supplied id does not correspond to `Row` node + pub fn get_row_list(&self, row_id: usize) -> Result<&[usize], SbroadError> { + self.get_expression_node(row_id)?.get_row_list() + } + + /// Gets `GroupBy` column by idx + /// + /// # Errors + /// - supplied index is out of range + /// - node is not `GroupBy` + pub fn get_groupby_col(&self, groupby_id: usize, col_idx: usize) -> Result<usize, SbroadError> { + let node = self.get_relation_node(groupby_id)?; + if let Relational::GroupBy { gr_cols, .. } = node { + let col_id = gr_cols.get(col_idx).ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues(format!( + "groupby column index out of range. Node: {node:?}" + )) + })?; + return Ok(*col_id); + } + Err(SbroadError::Invalid( + Entity::Node, + Some(format!("Expected GroupBy node. Got: {node:?}")), + )) + } + /// Get alias string for `Reference` node /// /// # Errors diff --git a/sbroad-core/src/ir/distribution.rs b/sbroad-core/src/ir/distribution.rs index 0f28a343c2d935e1bce0f0d7a034dfb3e575510f..834f0fef34784530e2351ed1a62b24b633c600cb 100644 --- a/sbroad-core/src/ir/distribution.rs +++ b/sbroad-core/src/ir/distribution.rs @@ -81,6 +81,7 @@ pub enum Distribution { impl Distribution { /// Calculate a new distribution for the `Except` and `UnionAll` output tuple. + /// Single fn union_except(left: &Distribution, right: &Distribution) -> Distribution { match (left, right) { (Distribution::Any, _) | (_, Distribution::Any) => Distribution::Any, @@ -407,16 +408,17 @@ impl Plan { child_pos_map: &AHashMap<ChildColumnReference, ParentColumnPosition>, ) -> Result<Distribution, SbroadError> { if let Node::Relational(relational_op) = self.get_node(child_rel_node)? { + let node = self.get_node(relational_op.output())?; if let Node::Expression(Expression::Row { distribution: child_dist, .. - }) = self.get_node(relational_op.output())? + }) = node { match child_dist { None => { return Err(SbroadError::Invalid( Entity::Distribution, - Some("distribution is uninitialized".into()), + Some("distribution is uninitialized".to_string()), )) } Some(Distribution::Any) => return Ok(Distribution::Any), @@ -458,13 +460,21 @@ impl Plan { /// # Errors /// - Node is not of a row type. pub fn set_const_dist(&mut self, row_id: usize) -> Result<(), SbroadError> { + self.set_dist(row_id, Distribution::Replicated) + } + + /// Sets the `Distribution` of row to given one + /// + /// # Errors + /// - supplied node is `Row` + pub fn set_dist(&mut self, row_id: usize, dist: Distribution) -> Result<(), SbroadError> { if let Expression::Row { ref mut distribution, .. } = self.get_mut_expression_node(row_id)? { if distribution.is_none() { - *distribution = Some(Distribution::Replicated); + *distribution = Some(dist); } return Ok(()); } diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs index 6dda5e42dd87ca3cbf27266462e84a7d84868c0b..b969df9671ead0bcccb88370f481892a62ecad52 100644 --- a/sbroad-core/src/ir/explain.rs +++ b/sbroad-core/src/ir/explain.rs @@ -233,6 +233,44 @@ impl Display for Projection { } } +#[derive(Debug, Serialize)] +struct GroupBy { + /// List of colums in sql query + cols: Vec<Col>, +} + +impl GroupBy { + #[allow(dead_code)] + fn new(plan: &Plan, output_id: usize) -> Result<Self, SbroadError> { + let mut result = GroupBy { cols: vec![] }; + + let alias_list = plan.get_expression_node(output_id)?; + + for col_node_id in alias_list.get_row_list()? { + let col = Col::new(plan, *col_node_id)?; + + result.cols.push(col); + } + Ok(result) + } +} + +impl Display for GroupBy { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut s = "group by ".to_string(); + + let cols = &self + .cols + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(", "); + + write!(s, "({cols})")?; + write!(f, "{s}") + } +} + #[derive(Debug, Serialize)] struct Scan { /// Table name @@ -587,6 +625,7 @@ impl Display for InnerJoin { #[allow(dead_code)] enum ExplainNode { Except, + GroupBy(GroupBy), InnerJoin(InnerJoin), ValueRow(Row), Value, @@ -608,6 +647,7 @@ impl Display for ExplainNode { ExplainNode::Value => "values".to_string(), ExplainNode::Insert(s) => format!("insert {s}"), ExplainNode::Projection(e) => e.to_string(), + ExplainNode::GroupBy(p) => p.to_string(), ExplainNode::Scan(s) => s.to_string(), ExplainNode::Selection(s) => format!("selection {s}"), ExplainNode::UnionAll => "union all".to_string(), @@ -714,6 +754,16 @@ impl FullExplain { } Some(ExplainNode::Except) } + Relational::GroupBy { output, .. } => { + let child = stack.pop().ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues( + "Groupby node must have at least one child".into(), + ) + })?; + current_node.children.push(child); + let p = GroupBy::new(ir, *output)?; + Some(ExplainNode::GroupBy(p)) + } Relational::Projection { output, .. } => { // TODO: change this logic when we'll enable sub-queries in projection let child = stack.pop().ok_or_else(|| { diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs index 976e1bf70efe3c3ef303cfa6f17786d1c40b9da5..f64006281aafc6326e38d23dcef1ee6f073c121b 100644 --- a/sbroad-core/src/ir/expression.rs +++ b/sbroad-core/src/ir/expression.rs @@ -671,9 +671,18 @@ impl Plan { }) }); if !all_found { + // If the child is a group by then the parent is projection (will be false when we add `Having` node). + // If this reference is inside arithmetic expression it would be better to throw an + // error like `select a + b group by a is wrong!`, but we can't check it here. + if let Relational::GroupBy { .. } = self.get_relation_node(child_node)? { + return Err(SbroadError::Invalid( + Entity::Query, + Some(format!("Invalid projection with group by clause: columns {} are not found in grouping columns!", col_names.join(", "))) + )); + } return Err(SbroadError::NotFound( Entity::Column, - format!("with name {col_names:?}"), + format!("with name {}", col_names.join(", ")), )); } diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs index fc5d1c826cbb950740e8f442c8e32a78f6728bec..9b10427d6fc865d46e894e59ebcc7eb2254f9173 100644 --- a/sbroad-core/src/ir/operator.rs +++ b/sbroad-core/src/ir/operator.rs @@ -3,6 +3,7 @@ //! Contains operator nodes that transform the tuples in IR tree. use ahash::RandomState; + use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; @@ -251,6 +252,13 @@ pub enum Relational { /// Outputs tuple node index in the plan node arena. output: usize, }, + GroupBy { + /// The first child is a relational operator before group by + children: Vec<usize>, + gr_cols: Vec<usize>, + output: usize, + is_final: bool, + }, UnionAll { /// Contains exactly two elements: left and right node indexes /// from the plan node arena. @@ -327,6 +335,7 @@ impl Relational { pub fn output(&self) -> usize { match self { Relational::Except { output, .. } + | Relational::GroupBy { output, .. } | Relational::InnerJoin { output, .. } | Relational::Insert { output, .. } | Relational::Motion { output, .. } @@ -345,6 +354,7 @@ impl Relational { pub fn mut_output(&mut self) -> &mut usize { match self { Relational::Except { output, .. } + | Relational::GroupBy { output, .. } | Relational::InnerJoin { output, .. } | Relational::Insert { output, .. } | Relational::Motion { output, .. } @@ -363,6 +373,7 @@ impl Relational { pub fn children(&self) -> Option<&[usize]> { match self { Relational::Except { children, .. } + | Relational::GroupBy { children, .. } | Relational::InnerJoin { children, .. } | Relational::Insert { children, .. } | Relational::Motion { children, .. } @@ -383,6 +394,9 @@ impl Relational { Relational::Except { ref mut children, .. } + | Relational::GroupBy { + ref mut children, .. + } | Relational::InnerJoin { ref mut children, .. } @@ -474,6 +488,10 @@ impl Relational { children: ref mut old, .. } + | Relational::GroupBy { + children: ref mut old, + .. + } | Relational::ValuesRow { children: ref mut old, .. @@ -503,6 +521,7 @@ impl Relational { alias, relation, .. } => Ok(alias.as_deref().or(Some(relation.as_str()))), Relational::Projection { .. } + | Relational::GroupBy { .. } | Relational::Selection { .. } | Relational::InnerJoin { .. } => { let output_row = plan.get_expression_node(self.output())?; @@ -532,7 +551,12 @@ impl Relational { Ok(None) } Relational::ScanSubQuery { alias, .. } | Relational::Motion { alias, .. } => { - Ok(alias.as_deref()) + if let Some(name) = alias.as_ref() { + if !name.is_empty() { + return Ok(alias.as_deref()); + } + } + Ok(None) } Relational::Except { .. } | Relational::UnionAll { .. } @@ -880,6 +904,276 @@ impl Plan { Ok(proj_id) } + /// Adds `GroupBy` node to local stage of 2-stage aggregation + /// + /// # Errors: + /// - Node is not `GroupBy` node + /// - `GroupBy` node has unexpected number of children + /// - failed to create output or grouping cols for local `GroupBy` + fn add_local_groupby(&mut self, final_id: usize) -> Result<usize, SbroadError> { + let (final_children, final_cols, final_output) = if let Relational::GroupBy { + children, + gr_cols, + output, + .. + } = self.get_relation_node(final_id)? + { + (children.clone(), gr_cols.clone(), *output) + } else { + return Err(SbroadError::Invalid( + Entity::Node, + Some(format!( + "add_local_groupby: expected groupby node on id: {final_id}" + )), + )); + }; + + if final_children.len() != 1 { + return Err(SbroadError::UnexpectedNumberOfValues( + "Expected groupby node to have exactly one child".into(), + )); + } + let mut local_cols: Vec<usize> = Vec::with_capacity(final_cols.len()); + for col in &final_cols { + // When an aggregate is added, we transform expressions by adding aggregates + // from `HAVING` and `SELECT` clauses. Then aggregates are transformed to the MAP stage. + let new_col = self.clone_expr_subtree(*col)?; + local_cols.push(new_col); + } + let local_output = self.clone_expr_subtree(final_output)?; + let local_id = self.nodes.next_id(); + for col in &local_cols { + self.replace_parent_in_subtree(*col, Some(final_id), Some(local_id))?; + } + let local_groupby = Relational::GroupBy { + children: final_children, + gr_cols: local_cols, + output: local_output, + is_final: false, + }; + self.nodes.push(Node::Relational(local_groupby)); + self.replace_parent_in_subtree(local_output, Some(final_id), Some(local_id))?; + Ok(local_id) + } + + fn change_groupby_child_to( + &mut self, + new_child_id: usize, + groupby_id: usize, + ) -> Result<(), SbroadError> { + let (gr_cols_len, output) = if let Relational::GroupBy { + gr_cols, output, .. + } = self.get_relation_node(groupby_id)? + { + (gr_cols.len(), *output) + } else { + return Err(SbroadError::Invalid( + Entity::Node, + Some("change_groupby_child: expected GroupBy node".into()), + )); + }; + let map = self + .get_relation_node(new_child_id)? + .output_alias_position_map(&self.nodes)? + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect::<HashMap<String, usize>>(); + // Update references in grouping columns + for i in 0..gr_cols_len { + let col_id = self.get_groupby_col(groupby_id, i)?; + let reference = self.get_expression_node(col_id)?; + let col_name = self.get_alias_from_reference_node(reference)?.to_string(); + let new_pos = *map + .get(&col_name) + .ok_or_else(|| SbroadError::NotFound(Entity::Node, String::new()))?; + if let Expression::Reference { + position, parent, .. + } = self.get_mut_expression_node(col_id)? + { + *position = new_pos; + *parent = Some(new_child_id); + } else { + return Err(SbroadError::NotFound( + Entity::Expression, + "Reference node".into(), + )); + } + } + // Update output + for i in 0..self.get_row_list(output)?.len() { + let alias_id = self.get_row_list(output)?.get(i).ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues("row list's size has changed!".into()) + })?; + let (child, name) = + if let Expression::Alias { child, name } = self.get_expression_node(*alias_id)? { + (*child, name.clone()) + } else { + return Err(SbroadError::Invalid(Entity::Node, None)); + }; + let new_pos = map + .get(name.as_str()) + .ok_or_else(|| SbroadError::NotFound(Entity::Node, String::new()))?; + if let Expression::Reference { position, .. } = self.get_mut_expression_node(child)? { + *position = *new_pos; + } else { + return Err(SbroadError::NotFound( + Entity::Expression, + "Reference node".into(), + )); + } + } + // Update children list + if let Relational::GroupBy { children, .. } = self.get_mut_relation_node(groupby_id)? { + children[0] = new_child_id; + } + Ok(()) + } + + fn add_local_projection(&mut self, local_groupby_id: usize) -> Result<usize, SbroadError> { + { + // Check input node + let node = self.get_relation_node(local_groupby_id)?; + if !matches!(node, Relational::GroupBy { .. }) { + return Err(SbroadError::Invalid(Entity::Node, Some( + format!("add_local_projection: expected Relational::GroupBy node on id: {local_groupby_id}, got: {node:?}")))); + } + } + + let local_output = self.get_relational_output(local_groupby_id)?; + let proj_output = self.clone_expr_subtree(local_output)?; + let proj = Relational::Projection { + output: proj_output, + children: vec![local_groupby_id], + }; + let proj_id = self.nodes.push(Node::Relational(proj)); + self.replace_parent_in_subtree(proj_output, Some(local_groupby_id), Some(proj_id))?; + // Because the local group by is a child of a projection, + // position in parent's output reference is the same as index in the row list. + for pos in 0..self.get_row_list(proj_output)?.len() { + let alias_id = self.get_row_list(proj_output)?.get(pos).ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues("row list's size has changed!".into()) + })?; + let alias_node = self.get_expression_node(*alias_id)?; + let ref_id = if let Expression::Alias { child: ref_id, .. } = alias_node { + *ref_id + } else { + return Err(SbroadError::Invalid( + Entity::Node, + Some(format!("add_local_proj: expected projection output ({proj_output}) to consist of aliases. Got id: {alias_id}, {alias_node:?}")) + )); + }; + let ref_node = self.get_mut_expression_node(ref_id)?; + if let Expression::Reference { position, .. } = ref_node { + *position = pos; + } else { + return Err(SbroadError::Invalid( + Entity::Node, + Some(format!("add_local_proj: expected projection output alias to have Reference child ({ref_id}), got: {ref_node:?}")))); + } + } + Ok(proj_id) + } + + /// Adds local stage for aggregation + /// + /// # Errors + /// - failed to create local `GroupBy` node + /// - failed to create local `Projection` node + /// - failed to create `SQ` node + /// - failed to change final `GroupBy` child to `SQ` + pub fn add_two_stage_aggregation(&mut self, final_id: usize) -> Result<(), SbroadError> { + let local_id = self.add_local_groupby(final_id)?; + let proj_id = self.add_local_projection(local_id)?; + // If we generate an alias using uuid (like we do for tmp spaces) the penalty would be redundant + // verbosity in the column names. We can't set an alias `None` here as well, because then the frontend + // would not generate parentheses for a subquery while building sql. + let sq_id = self.add_sub_query(proj_id, Some(""))?; + self.change_groupby_child_to(sq_id, final_id)?; + Ok(()) + } + + /// Creates output `Row` for final `GroupBy` node + /// + /// # Errors + /// - child node output is not `Row` + /// - expressions used in group by are not column references + /// - column references are invalid + pub fn add_output_groupby( + &mut self, + child_id: usize, + cols_ids: &[usize], + ) -> Result<usize, SbroadError> { + // For each reference we will need an alias + let mut row_list: Vec<usize> = Vec::with_capacity(cols_ids.len()); + for col_id in cols_ids { + let child_output = self.get_row_list(self.get_relational_output(child_id)?)?; + let column_name = if let Expression::Reference { position, .. } = + self.get_expression_node(*col_id)? + { + let alias_id = *child_output.get(*position).ok_or_else(|| { + SbroadError::Invalid( + Entity::Node, + Some("Reference have invalid position".into()), + ) + })?; + if let Expression::Alias { name, .. } = self.get_expression_node(alias_id)? { + name.clone() + } else { + return Err(SbroadError::Invalid( + Entity::Node, + Some(format!("Expected alias on id: {alias_id}")), + )); + } + } else { + return Err(SbroadError::FailedTo( + Action::Create, + Some(Entity::Node), + "output for groupby".into(), + )); + }; + let ref_node = self.get_node(*col_id)?; + let output_ref_id = self.nodes.push(ref_node.clone()); + row_list.push(self.nodes.add_alias(&column_name, output_ref_id)?); + } + let output = self.nodes.add_row_of_aliases(row_list, None)?; + + Ok(output) + } + + /// Adds final `GroupBy` node to `Plan` + /// + /// # Errors + /// - invalid children count + /// - failed to create output for `GroupBy` + pub fn add_groupby(&mut self, children: &[usize]) -> Result<usize, SbroadError> { + if children.len() < 2 { + return Err(SbroadError::Invalid( + Entity::Relational, + Some("Expected GroupBy to have at least one child".into()), + )); + } + + let Some((first_child, other)) = children.split_first() else { + return Err(SbroadError::UnexpectedNumberOfValues("GroupBy ast has no children".into())) + }; + let final_output = self.add_output_groupby(*first_child, other)?; + let groupby = Relational::GroupBy { + children: [*first_child].to_vec(), + gr_cols: other.to_vec(), + output: final_output, + is_final: true, + }; + + let groupby_id = self.nodes.push(Node::Relational(groupby)); + + self.replace_parent_in_subtree(final_output, None, Some(groupby_id))?; + for col in children.iter().skip(1) { + self.replace_parent_in_subtree(*col, None, Some(groupby_id))?; + } + + Ok(groupby_id) + } + /// Adds selection node /// /// # Errors @@ -934,14 +1228,7 @@ impl Plan { child: usize, alias: Option<&str>, ) -> Result<usize, SbroadError> { - let name: Option<String> = if let Some(name) = alias { - if name.is_empty() { - return Err(SbroadError::Invalid(Entity::Name, None)); - } - Some(String::from(name)) - } else { - None - }; + let name: Option<String> = alias.map(String::from); let output = self.add_row_for_output(child, &[], true)?; let sq = Relational::ScanSubQuery { diff --git a/sbroad-core/src/ir/operator/tests.rs b/sbroad-core/src/ir/operator/tests.rs index cd8c0638a830005f17da6b48a0a2d41521f8fedf..f7510aa1adb7b750fd8b34ef9ddedcafa484e0c3 100644 --- a/sbroad-core/src/ir/operator/tests.rs +++ b/sbroad-core/src/ir/operator/tests.rs @@ -106,7 +106,7 @@ fn projection() { // Invalid alias names in the output assert_eq!( - SbroadError::NotFound(Entity::Column, r#"with name ["a", "e"]"#.into()), + SbroadError::NotFound(Entity::Column, r#"with name a, e"#.into()), plan.add_proj(scan_id, &["a", "e"]).unwrap_err() ); @@ -398,12 +398,6 @@ fn sub_query() { SbroadError::Invalid(Entity::Node, Some("node is not Relational type".into())), plan.add_sub_query(a, Some("sq")).unwrap_err() ); - - // Invalid name - assert_eq!( - SbroadError::Invalid(Entity::Name, None), - plan.add_sub_query(scan_id, Some("")).unwrap_err() - ); } #[test] diff --git a/sbroad-core/src/ir/transformation/redistribution.rs b/sbroad-core/src/ir/transformation/redistribution.rs index 3b25c80b7defee367bd2ab830595c3112c01e4c0..cd3f8e0f90ba0e18361b63fd7b368dee3e64e79d 100644 --- a/sbroad-core/src/ir/transformation/redistribution.rs +++ b/sbroad-core/src/ir/transformation/redistribution.rs @@ -11,6 +11,7 @@ use crate::ir::distribution::{Distribution, Key}; use crate::ir::expression::Expression; use crate::ir::operator::{Bool, Relational}; use crate::ir::relation::Column; + use crate::ir::tree::traversal::{BreadthFirst, PostOrder, EXPR_CAPACITY, REL_CAPACITY}; use crate::ir::value::Value; use crate::ir::{Node, Plan}; @@ -754,6 +755,31 @@ impl Plan { } } + #[allow(clippy::unused_self)] + #[must_use] + pub fn resolve_groupby_conflicts( + &self, + children: &[usize], + grouping_cols: &[usize], + ) -> Strategy { + let mut strategy: Strategy = HashMap::new(); + + // first columns of groupby output are grouping columns + let mut targets: Vec<Target> = Vec::with_capacity(grouping_cols.len()); + for pos in 0..grouping_cols.len() { + targets.push(Target::Reference(pos)); + } + + strategy.insert( + children[0], + ( + MotionPolicy::Segment(MotionKey { targets }), + DataGeneration::None, + ), + ); + strategy + } + /// Derive the motion policy for the inner child and sub-queries in the join node. /// /// # Errors @@ -765,11 +791,13 @@ impl Plan { expr_id: usize, ) -> Result<Strategy, SbroadError> { // First, we need to set the motion policy for each boolean expression in the join condition. - let nodes = self.get_bool_nodes_with_row_children(expr_id)?; - for node in &nodes { - let bool_op = BoolOp::from_expr(self, *node)?; - self.set_distribution(bool_op.left)?; - self.set_distribution(bool_op.right)?; + { + let nodes = self.get_bool_nodes_with_row_children(expr_id)?; + for node in &nodes { + let bool_op = BoolOp::from_expr(self, *node)?; + self.set_distribution(bool_op.left)?; + self.set_distribution(bool_op.right)?; + } } // Init the strategy (motion policy map) for all the join children except the outer child. @@ -1077,8 +1105,8 @@ impl Plan { Relational::Projection { output, .. } | Relational::ScanRelation { output, .. } | Relational::ScanSubQuery { output, .. } - | Relational::UnionAll { output, .. } | Relational::Values { output, .. } + | Relational::UnionAll { output, .. } | Relational::ValuesRow { output, .. } => { self.set_distribution(output)?; } @@ -1094,6 +1122,18 @@ impl Plan { let strategy = self.resolve_sub_query_conflicts(*id, filter)?; self.create_motion_nodes(*id, &strategy)?; } + Relational::GroupBy { + output, + children, + is_final, + gr_cols, + } => { + self.set_distribution(output)?; + if is_final { + let strategy = self.resolve_groupby_conflicts(&children, &gr_cols); + self.create_motion_nodes(*id, &strategy)?; + } + } Relational::InnerJoin { output, condition, .. } => { diff --git a/sbroad-core/src/ir/tree/relation.rs b/sbroad-core/src/ir/tree/relation.rs index 543118d8179a86a966c522f944dc6d3f4f7cf0f1..81596d3d3370de15e086d46127d5463a3b7409ce 100644 --- a/sbroad-core/src/ir/tree/relation.rs +++ b/sbroad-core/src/ir/tree/relation.rs @@ -74,6 +74,14 @@ fn relational_next<'nodes>( } None } + Some(Node::Relational(Relational::GroupBy { children, .. })) => { + let step = *iter.get_child().borrow(); + if step == 0 { + *iter.get_child().borrow_mut() += 1; + return children.get(step); + } + None + } Some( Node::Relational(Relational::ScanRelation { .. }) | Node::Expression(_) diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs index eb3013c5e0fc188b6e4d82eaa04f4f660f489182..581f0ce23784af6e28633394724abe152363cfdc 100644 --- a/sbroad-core/src/ir/tree/subtree.rs +++ b/sbroad-core/src/ir/tree/subtree.rs @@ -330,6 +330,28 @@ fn subtree_next<'plan>( } None } + Relational::GroupBy { + children, + output, + gr_cols, + .. + } => { + let step = *iter.get_child().borrow(); + if step == 0 { + *iter.get_child().borrow_mut() += 1; + return children.get(step); + } + let col_idx = step - 1; + if col_idx < gr_cols.len() { + *iter.get_child().borrow_mut() += 1; + return gr_cols.get(col_idx); + } + if iter.need_output() && col_idx == gr_cols.len() { + *iter.get_child().borrow_mut() += 1; + return Some(output); + } + None + } Relational::Motion { children, output, .. } => { diff --git a/sbroad-core/src/ir/value.rs b/sbroad-core/src/ir/value.rs index d702f37c9fd37d386f6883fcebc975172cad73d7..2389397b5bb95b9e2c859d09a984bea56aee99cb 100644 --- a/sbroad-core/src/ir/value.rs +++ b/sbroad-core/src/ir/value.rs @@ -14,7 +14,7 @@ use crate::executor::hash::ToHashString; use crate::ir::value::double::Double; #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)] -pub struct Tuple(Vec<Value>); +pub struct Tuple(pub(crate) Vec<Value>); impl Display for Tuple { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/sbroad-core/src/lib.rs b/sbroad-core/src/lib.rs index a6e3500035b15fab484b91260bdf039e2c9c091e..bdfb3600a8b07ca7935a8b63d66afc1089a79eb5 100644 --- a/sbroad-core/src/lib.rs +++ b/sbroad-core/src/lib.rs @@ -4,6 +4,7 @@ extern crate lazy_static; #[macro_use] extern crate pest_derive; +extern crate core; pub mod backend; pub mod errors; diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_group_by.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_group_by.yaml new file mode 100644 index 0000000000000000000000000000000000000000..41af60b2e12c75b494bd647b6acc308a925c6ecb --- /dev/null +++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_3_group_by.yaml @@ -0,0 +1,56 @@ +--- +nodes: + arena: + - children: + - 6 + rule: Select + value: ~ + - children: + - 4 + - 2 + rule: GroupBy + value: ~ + - children: + - 3 + rule: Reference + value: ~ + - children: [] + rule: ColumnName + value: a + - children: + - 5 + rule: Scan + value: ~ + - children: [] + rule: Table + value: t + - children: + - 1 + - 7 + rule: Projection + value: ~ + - children: + - 11 + rule: Column + value: ~ + - children: + - 9 + rule: Reference + value: ~ + - children: [] + rule: ColumnName + value: a + - children: [] + rule: AliasName + value: a + - children: + - 8 + - 10 + rule: Alias + value: ~ +top: 6 +map: + 2: + - 4 + 8: + - 1 diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_1.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..19f67cc759700fea459bb00cc537e34a94d7273e --- /dev/null +++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_4_1.yaml @@ -0,0 +1,110 @@ +--- +nodes: + arena: + - children: #0 + - 13 + rule: Select + value: ~ + - children: #1 + - 6 + - 4 + - 2 + rule: GroupBy + value: ~ + - children: #2 + - 3 + rule: Reference + value: ~ + - children: [] #3 + rule: ColumnName + value: b + - children: #4 + - 5 + rule: Reference + value: ~ + - children: [] #5 + rule: ColumnName + value: a + - children: #6 + - 11 + - 7 + rule: Selection + value: ~ + - children: #7 + - 9 + - 8 + rule: Gt + value: ~ + - children: [] #8 + rule: Unsigned + value: "1" + - children: #9 + - 10 + rule: Reference + value: ~ + - children: [] #10 + rule: ColumnName + value: a + - children: #11 + - 12 + rule: Scan + value: ~ + - children: [] #12 + rule: Table + value: t1 + - children: #13 + - 1 + - 17 + - 14 + rule: Projection + value: ~ + - children: #14 + - 23 + rule: Column + value: ~ + - children: #15 + - 16 + rule: Reference + value: ~ + - children: [] #16 + rule: ColumnName + value: b + - children: #17 + - 21 + rule: Column + value: ~ + - children: #18 + - 19 + rule: Reference + value: ~ + - children: [] #19 + rule: ColumnName + value: a + - children: [] #20 + rule: AliasName + value: a + - children: #21 + - 18 + - 20 + rule: Alias + value: ~ + - children: [] #22 + rule: AliasName + value: b + - children: #23 + - 15 + - 22 + rule: Alias + value: ~ +top: 13 +map: + 2: + - 6 + 4: + - 6 + 9: + - 11 + 15: + - 1 + 18: + - 1 diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_1.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8e05f74b93e1cfcb7c347f4fc080a27855a15752 --- /dev/null +++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_5_1.yaml @@ -0,0 +1,154 @@ +--- +nodes: + arena: + - children: #0 + - 21 + rule: Select + value: ~ + - children: #1 + - 16 + - 5 + - 2 + rule: GroupBy + value: ~ + - children: #2 + - 4 + - 3 + rule: Reference + value: ~ + - children: [] #3 + rule: ColumnName + value: b + - children: [] #4 + rule: ScanName + value: t2 + - children: #5 + - 7 + - 6 + rule: Reference + value: ~ + - children: [] #6 + rule: ColumnName + value: a + - children: [] #7 + rule: ScanName + value: t1 + - children: #8 + - 9 + rule: Condition + value: ~ + - children: #9 + - 13 + - 10 + rule: Eq + value: ~ + - children: #10 + - 12 + - 11 + rule: Reference + value: ~ + - children: [] #11 + rule: ColumnName + value: a + - children: [] #12 + rule: ScanName + value: t2 + - children: #13 + - 15 + - 14 + rule: Reference + value: ~ + - children: [] #14 + rule: ColumnName + value: a + - children: [] #15 + rule: ScanName + value: t1 + - children: #16 + - 19 + - 17 + - 8 + rule: InnerJoin + value: ~ + - children: #17 + - 18 + rule: Scan + value: ~ + - children: [] #18 + rule: Table + value: t2 + - children: #19 + - 20 + rule: Scan + value: ~ + - children: [] #20 + rule: Table + value: t1 + - children: #21 + - 1 + - 26 + - 22 + rule: Projection + value: ~ + - children: #22 + - 33 + rule: Column + value: ~ + - children: #23 + - 25 + - 24 + rule: Reference + value: ~ + - children: [] #24 + rule: ColumnName + value: b + - children: [] #25 + rule: ScanName + value: t2 + - children: #26 + - 31 + rule: Column + value: ~ + - children: #27 + - 29 + - 28 + rule: Reference + value: ~ + - children: [] #28 + rule: ColumnName + value: a + - children: [] #29 + rule: ScanName + value: t1 + - children: [] #30 + rule: AliasName + value: a + - children: #31 + - 27 + - 30 + rule: Alias + value: ~ + - children: [] #32 + rule: AliasName + value: b + - children: #33 + - 23 + - 32 + rule: Alias + value: ~ +top: 21 +map: + 23: + - 1 + 10: + - 19 + - 17 + 2: + - 16 + 27: + - 1 + 5: + - 16 + 13: + - 19 + - 17 diff --git a/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml b/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml new file mode 100644 index 0000000000000000000000000000000000000000..aed12a7fcd3aa2f62a115dd1d25b576f1b4b5159 --- /dev/null +++ b/sbroad-core/tests/artifactory/frontend/sql/transform_select_6.yaml @@ -0,0 +1,180 @@ +--- +nodes: + arena: + - children: #0 + - 27 + rule: Select + value: ~ + - children: #1 + - 8 + - 5 + - 2 + rule: GroupBy + value: ~ + - children: #2 + - 4 + - 3 + rule: Reference + value: ~ + - children: [] #3 + rule: ColumnName + value: b + - children: [] #4 + rule: ScanName + value: t2 + - children: #5 + - 7 + - 6 + rule: Reference + value: ~ + - children: [] #6 + rule: ColumnName + value: a + - children: [] #7 + rule: ScanName + value: t1 + - children: #8 + - 22 + - 9 + rule: Selection + value: ~ + - children: #9 + - 11 + - 10 + rule: Gt + value: ~ + - children: [] #10 + rule: Unsigned + value: "1" + - children: #11 + - 13 + - 12 + rule: Reference + value: ~ + - children: [] #12 + rule: ColumnName + value: a + - children: [] #13 + rule: ScanName + value: t1 + - children: #14 + - 15 + rule: Condition + value: ~ + - children: #15 + - 19 + - 16 + rule: Eq + value: ~ + - children: #16 + - 18 + - 17 + rule: Reference + value: ~ + - children: [] #17 + rule: ColumnName + value: a + - children: [] #18 + rule: ScanName + value: t2 + - children: #19 + - 21 + - 20 + rule: Reference + value: ~ + - children: [] #20 + rule: ColumnName + value: a + - children: [] #21 + rule: ScanName + value: t1 + - children: #22 + - 25 + - 23 + - 14 + rule: InnerJoin + value: ~ + - children: #23 + - 24 + rule: Scan + value: ~ + - children: [] #24 + rule: Table + value: t2 + - children: #25 + - 26 + rule: Scan + value: ~ + - children: [] #26 + rule: Table + value: t1 + - children: #27 + - 1 + - 32 + - 28 + rule: Projection + value: ~ + - children: #28 + - 39 + rule: Column + value: ~ + - children: #29 + - 31 + - 30 + rule: Reference + value: ~ + - children: [] #30 + rule: ColumnName + value: b + - children: [] #31 + rule: ScanName + value: t2 + - children: #32 + - 37 + rule: Column + value: ~ + - children: #33 + - 35 + - 34 + rule: Reference + value: ~ + - children: [] #34 + rule: ColumnName + value: a + - children: [] #35 + rule: ScanName + value: t1 + - children: [] #36 + rule: AliasName + value: a + - children: #37 + - 33 + - 36 + rule: Alias + value: ~ + - children: [] #38 + rule: AliasName + value: b + - children: #39 + - 29 + - 38 + rule: Alias + value: ~ +top: 27 +map: + 33: + - 1 + 2: + - 8 + 16: + - 25 + - 23 + 29: + - 1 + 5: + - 8 + 19: + - 25 + - 23 + 11: + - 22