From 7dad8474215945c3815327194318bfca7e9412b1 Mon Sep 17 00:00:00 2001 From: Kaitmazian Maksim <m.kaitmazian@picodata.io> Date: Tue, 10 Sep 2024 12:40:20 +0300 Subject: [PATCH] feat: support empty queries --- .../test/integration/empty_queries.lua | 33 +++++++++++++++++++ sbroad-core/src/executor.rs | 19 +++++++++++ sbroad-core/src/executor/engine/helpers.rs | 17 ++++++++++ sbroad-core/src/frontend/sql.rs | 15 +++++++++ sbroad-core/src/frontend/sql/query.pest | 4 ++- sbroad-core/src/ir.rs | 7 +++- 6 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 sbroad-cartridge/test_app/test/integration/empty_queries.lua diff --git a/sbroad-cartridge/test_app/test/integration/empty_queries.lua b/sbroad-cartridge/test_app/test/integration/empty_queries.lua new file mode 100644 index 000000000..b97c9b267 --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/empty_queries.lua @@ -0,0 +1,33 @@ +local t = require('luatest') +local g = t.group('integration_api.empty_queries') + +local helper = require('test.helper.cluster_no_replication') + +g.after_all(function() + helper.stop_test_cluster() +end) + +g.test_empty_queries = function() + local r, err + local api = helper.cluster:server("api-1").net_box + + r, err = api:call("sbroad.execute", { [[]] }) + t.assert_equals(err, nil) + t.assert_equals(r["row_count"], 0) + + r, err = api:call("sbroad.execute", { [[ ]] }) + t.assert_equals(err, nil) + t.assert_equals(r["row_count"], 0) + + r, err = api:call("sbroad.execute", { [[;]] }) + t.assert_equals(err, nil) + t.assert_equals(r["row_count"], 0) + + r, err = api:call("sbroad.execute", { [[ ; ]] }) + t.assert_equals(err, nil) + t.assert_equals(r["row_count"], 0) + + r, err = api:call("sbroad.execute", { [[; ; ;]] }) + t.assert_equals(err, nil) + t.assert_equals(r["row_count"], 0) +end diff --git a/sbroad-core/src/executor.rs b/sbroad-core/src/executor.rs index 7b944a796..94dc17ccd 100644 --- a/sbroad-core/src/executor.rs +++ b/sbroad-core/src/executor.rs @@ -126,6 +126,11 @@ where if plan.is_empty() { let metadata = coordinator.metadata().lock(); plan = C::ParseTree::transform_into_plan(sql, &*metadata)?; + // Empty query. + if plan.is_empty() { + return Ok(Query::empty(coordinator)); + } + if coordinator.provides_versions() { let mut table_version_map = TableVersionMap::with_capacity(plan.relations.tables.len()); @@ -159,6 +164,15 @@ where Ok(query) } + fn empty(coordinator: &'a C) -> Self { + Self { + is_explain: false, + exec_plan: Plan::empty().into(), + coordinator, + bucket_map: Default::default(), + } + } + /// Get the execution plan of the query. #[must_use] pub fn get_exec_plan(&self) -> &ExecutionPlan { @@ -326,6 +340,11 @@ where pub fn is_acl(&self) -> Result<bool, SbroadError> { self.exec_plan.get_ir_plan().is_acl() } + + /// Checks that query is an empty query. + pub fn is_empty(&self) -> bool { + self.exec_plan.get_ir_plan().is_empty() + } } #[cfg(test)] diff --git a/sbroad-core/src/executor/engine/helpers.rs b/sbroad-core/src/executor/engine/helpers.rs index 1d1e2a0b4..934367ed9 100644 --- a/sbroad-core/src/executor/engine/helpers.rs +++ b/sbroad-core/src/executor/engine/helpers.rs @@ -826,6 +826,18 @@ fn has_zero_limit_clause(plan: &ExecutionPlan) -> Result<bool, SbroadError> { Ok(false) } +fn empty_query_response() -> Result<Box<dyn Any>, SbroadError> { + let res = ConsumerResult { row_count: 0 }; + match Tuple::new(&[res]) { + Ok(t) => Ok(Box::new(t)), + Err(e) => Err(SbroadError::FailedTo( + Action::Create, + Some(Entity::Tuple), + format_smolstr!("{e}"), + )), + } +} + /// A helper function to dispatch the execution plan from the router to the storages. /// /// # Errors @@ -841,6 +853,11 @@ pub fn dispatch_impl( Option::from("dispatch"), &format!("dispatching plan: {plan:?}") ); + + if plan.get_ir_plan().is_empty() { + return empty_query_response(); + } + let sub_plan = plan.take_subtree(top_id)?; let tier = { diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 8bcbb6b74..c2690a53a 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -2638,6 +2638,10 @@ impl AbstractSyntaxTree { } } + fn is_empty(&self) -> bool { + self == &Self::empty() + } + /// Constructor. /// Builds a tree (nodes are in postorder reverse). /// Builds abstract syntax tree (AST) from SQL query. @@ -2663,6 +2667,12 @@ impl AbstractSyntaxTree { let top_pair = command_pair .next() .expect("Query expected as a first parsing tree child."); + + if let Rule::EmptyQuery = top_pair.as_rule() { + *self = Self::empty(); + return Ok(()); + } + let top = StackParseNode::new(top_pair, None); let mut stack: Vec<StackParseNode> = vec![top]; @@ -3848,6 +3858,11 @@ impl Ast for AbstractSyntaxTree { let mut pos_to_ast_id: SelectChildPairTranslation = HashMap::new(); let mut ast = AbstractSyntaxTree::empty(); ast.fill(query, &mut ast_id_to_pairs_map, &mut pos_to_ast_id)?; + // Empty query. + if ast.is_empty() { + return Ok(Plan::empty()); + } + ast.resolve_metadata(metadata, &mut ast_id_to_pairs_map, &mut pos_to_ast_id) } } diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 9cfe1cb97..2c397badd 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -1,4 +1,4 @@ -Command = _{ SOI ~ (Query | ExplainQuery | Block | DDL | ACL) ~ EOF } +Command = _{ SOI ~ (Query | ExplainQuery | Block | DDL | ACL | EmptyQuery) ~ EOF } // Helper rule to denote we have to update plan relations from metadata // (with Table which name corresponds to current node). @@ -261,6 +261,8 @@ Identifier = @{ DelimitedIdentifier | RegularIdentifier } | ^"limit" } +EmptyQuery = { WHITESPACE* } + Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } ExprInfixOp = _{ Between | ArithInfixOp | CmpInfixOp | ConcatInfixOp | And | Or } Between = { NotFlag? ~ ^"between" } diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs index 765399f43..743e97d76 100644 --- a/sbroad-core/src/ir.rs +++ b/sbroad-core/src/ir.rs @@ -854,7 +854,12 @@ impl Plan { /// Constructor for an empty plan structure. #[must_use] pub fn new() -> Self { - Plan { + Self::empty() + } + + /// Construct an empty plan. + pub fn empty() -> Self { + Self { nodes: Nodes { arena32: Vec::new(), arena64: Vec::new(), -- GitLab