From 189a73f46b1933d6a5b60f0c51cf91ec2f2d16fe Mon Sep 17 00:00:00 2001 From: Emir Vildanov <e.vildanov@picodata.io> Date: Fri, 22 Sep 2023 06:19:47 +0000 Subject: [PATCH] feat: support DROP USER --- doc/sql/query.ebnf | 4 +- sbroad-cartridge/src/api/exec_query.rs | 3 + .../test_app/test/integration/acl_test.lua | 31 +++++++ sbroad-core/src/backend/sql/ir.rs | 6 ++ sbroad-core/src/backend/sql/tree.rs | 6 ++ sbroad-core/src/executor.rs | 12 ++- sbroad-core/src/executor/ir.rs | 4 + sbroad-core/src/frontend/sql.rs | 30 +++++++ sbroad-core/src/frontend/sql/ast.rs | 6 ++ sbroad-core/src/frontend/sql/query.pest | 8 +- sbroad-core/src/ir.rs | 41 +++++++--- sbroad-core/src/ir/acl.rs | 80 +++++++++++++++++++ sbroad-core/src/ir/api/parameter.rs | 4 +- sbroad-core/src/ir/distribution.rs | 4 + sbroad-core/src/ir/expression/types.rs | 4 + sbroad-core/src/ir/tree/expression.rs | 3 +- sbroad-core/src/ir/tree/relation.rs | 3 +- sbroad-core/src/ir/tree/subtree.rs | 2 +- 18 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 sbroad-cartridge/test_app/test/integration/acl_test.lua create mode 100644 sbroad-core/src/ir/acl.rs diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index 3c0e9d5bcd..a0907cee04 100644 --- a/doc/sql/query.ebnf +++ b/doc/sql/query.ebnf @@ -1,13 +1,15 @@ -STATEMENT ::= EXPLAIN | DDL | DML | DQL +STATEMENT ::= EXPLAIN | DDL | DML | DQL | ACL EXPLAIN ::= 'EXPLAIN' ( DML | DQL ) DQL ::= (SELECT | SELECT UNION ALL SELECT | SELECT (EXCEPT 'DISTINCT'? ) SELECT ) Options? DML ::= (DELETE | INSERT) Options? DDL ::= CreateTable | DropTable +ACL ::= DropUser CreateTable ::= 'create table' table '(' Column (',' Column)* ',' PrimaryKey ')' ('using' ('memtx' | 'vinyl'))? Distribution ('option' '(' ('timeout' '=' DOUBLE)')')? Column ::= name ('Bool' | 'Decimal' | 'Double' | 'Int' | 'Number' | 'Scalar' | 'String' | 'Text' | 'Unsigned' | 'Varchar') ('null' | 'not null')? PrimaryKey ::= 'primary key' '(' name (',' name)* ')' Distribution ::= 'global' | ('distributed by' '(' name (',' name)* ')') DropTable ::= 'drop table' table ('option' '(' ('timeout' '=' DOUBLE)')')? +DropUser ::= 'drop user' user ('option' '(' ('timeout' '=' DOUBLE)')')? Options ::= 'option' '(' ('vtable_max_rows' '=' UNSIGNED)? (',' 'sql_vdbe_max_steps' '=' UNSIGNED)? ')' SELECT ::= 'SELECT' ((column (',' column)*) ) 'FROM' (table ('AS' name)? | '(' (SELECT | VALUES) ')' ('AS' name)?) ( ('INNER')? 'JOIN' (table ('AS' name)? | ('(' (SELECT | VALUES) ')' ('AS' name)?)) 'ON' expression )? ( 'WHERE' expression )? ( 'GROUP BY' expression(',' expression)* )? VALUES ::= 'VALUES' '(' row (',' row)* ')' diff --git a/sbroad-cartridge/src/api/exec_query.rs b/sbroad-cartridge/src/api/exec_query.rs index 332d15edce..c0e4d8f145 100644 --- a/sbroad-cartridge/src/api/exec_query.rs +++ b/sbroad-cartridge/src/api/exec_query.rs @@ -60,6 +60,9 @@ pub extern "C" fn dispatch_query(f_ctx: FunctionCtx, args: FunctionArgs) -> c_in if let Ok(true) = query.is_ddl() { return tarantool_error("DDL queries are not supported"); } + if let Ok(true) = query.is_acl() { + return tarantool_error("ACL queries are not supported"); + } match query.dispatch() { Ok(result) => child_span("\"tarantool.tuple.return\"", || { diff --git a/sbroad-cartridge/test_app/test/integration/acl_test.lua b/sbroad-cartridge/test_app/test/integration/acl_test.lua new file mode 100644 index 0000000000..d9256d69f8 --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/acl_test.lua @@ -0,0 +1,31 @@ +local t = require('luatest') +local g = t.group('sbroad_with_acl') + +local helper = require('test.helper.cluster_no_replication') +local cluster = nil + +g.before_all( + function() + helper.start_test_cluster(helper.cluster_config) + cluster = helper.cluster + end +) + +g.after_all( + function() + helper.stop_test_cluster() + end +) + +g.test_drop_user = function() + local api = cluster:server("api-1").net_box + + local _, err = api:call( + "sbroad.execute", + { [[ DROP USER user ]], {} } + ) + t.assert_equals( + string.format("%s", err), + [[Sbroad Error: ACL queries are not supported]] + ) +end \ No newline at end of file diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs index ed80342e25..2cc2959859 100644 --- a/sbroad-core/src/backend/sql/ir.rs +++ b/sbroad-core/src/backend/sql/ir.rs @@ -293,6 +293,12 @@ impl ExecutionPlan { Some("DDL nodes are not supported in the generated SQL".into()), )); } + Node::Acl(_) => { + return Err(SbroadError::Unsupported( + Entity::Node, + Some("ACL nodes are not supported in the generated SQL".into()), + )); + } Node::Parameter => { return Err(SbroadError::Unsupported( Entity::Node, diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs index 428f3da23a..62ba584ac2 100644 --- a/sbroad-core/src/backend/sql/tree.rs +++ b/sbroad-core/src/backend/sql/tree.rs @@ -649,6 +649,12 @@ impl<'p> SyntaxPlan<'p> { "DDL node {node:?} is not supported in the syntax plan" )), )), + Node::Acl(..) => Err(SbroadError::Invalid( + Entity::SyntaxPlan, + Some(format!( + "ACL node {node:?} is not supported in the syntax plan" + )), + )), Node::Parameter => { let sn = SyntaxNode::new_parameter(id); Ok(self.nodes.push_syntax_node(sn)) diff --git a/sbroad-core/src/executor.rs b/sbroad-core/src/executor.rs index 4b2843ed15..342c950609 100644 --- a/sbroad-core/src/executor.rs +++ b/sbroad-core/src/executor.rs @@ -123,11 +123,11 @@ where } plan.version_map = table_version_map; } - if !plan.is_ddl()? { + if !plan.is_ddl()? && !plan.is_acl()? { cache.put(key, plan.clone())?; } } - if !plan.is_ddl()? { + if !plan.is_ddl()? && !plan.is_acl()? { plan.bind_params(params)?; plan.apply_options()?; plan.optimize()?; @@ -269,6 +269,14 @@ where pub fn is_ddl(&self) -> Result<bool, SbroadError> { self.exec_plan.get_ir_plan().is_ddl() } + + /// Checks that query is ACL. + /// + /// # Errors + /// - Plan is invalid + pub fn is_acl(&self) -> Result<bool, SbroadError> { + self.exec_plan.get_ir_plan().is_acl() + } } #[cfg(test)] diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs index d59e4f2bc3..d646319e98 100644 --- a/sbroad-core/src/executor/ir.rs +++ b/sbroad-core/src/executor/ir.rs @@ -565,6 +565,10 @@ impl ExecutionPlan { Entity::SubTree, Some("DDL node".to_string()), ))?, + Node::Acl { .. } => Err(SbroadError::Invalid( + Entity::SubTree, + Some("ACL node".to_string()), + ))?, } new_plan.nodes.push(node); translation.insert(node_id, next_id); diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 546e09d41e..a43b63a429 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -30,6 +30,7 @@ use crate::ir::{Node, OptionKind, OptionSpec, Plan}; use crate::otm::child_span; use crate::errors::Entity::AST; +use crate::ir::acl::Acl; use crate::ir::aggregates::AggregateKind; use crate::ir::helpers::RepeatableState; use crate::ir::transformation::redistribution::ColumnPosition; @@ -1831,6 +1832,34 @@ impl Ast for AbstractSyntaxTree { let plan_id = plan.nodes.push(Node::Ddl(drop_table)); map.add(id, plan_id); } + Type::DropUser => { + let user_name_id = node.children.first().ok_or_else(|| { + SbroadError::Invalid( + Entity::ParseNode, + Some(String::from("UserName expected under DropUser node")), + ) + })?; + let user_name_node = self.nodes.get_node(*user_name_id)?; + let user_name = normalize_name_for_space_api( + user_name_node.value.as_ref().ok_or_else(|| { + SbroadError::NotFound( + Entity::Node, + "user name in the drop user AST".into(), + ) + })?, + ); + + let mut timeout = default_timeout; + if let Some(timeout_child_id) = node.children.get(1) { + timeout = get_timeout(self, *timeout_child_id)?; + } + let drop_user = Acl::DropUser { + name: user_name, + timeout, + }; + let plan_id = plan.nodes.push(Node::Acl(drop_user)); + map.add(id, plan_id); + } Type::Add | Type::AliasName | Type::Columns @@ -1889,6 +1918,7 @@ impl Ast for AbstractSyntaxTree { | Type::TypeVarchar | Type::UpdateList | Type::UpdateItem + | Type::UserName | Type::Vinyl => {} rule => { return Err(SbroadError::NotImplemented( diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs index b4323840f2..30ae5d52cd 100644 --- a/sbroad-core/src/frontend/sql/ast.rs +++ b/sbroad-core/src/frontend/sql/ast.rs @@ -58,6 +58,7 @@ pub enum Type { Divide, Double, DropTable, + DropUser, Duration, Engine, Eq, @@ -138,6 +139,7 @@ pub enum Type { UpdateList, UpdateItem, Unsigned, + UserName, Value, Values, ValuesRow, @@ -180,6 +182,7 @@ impl Type { Rule::Divide => Ok(Type::Divide), Rule::Double => Ok(Type::Double), Rule::DropTable => Ok(Type::DropTable), + Rule::DropUser => Ok(Type::DropUser), Rule::Duration => Ok(Type::Duration), Rule::DoReplace => Ok(Type::DoReplace), Rule::DoNothing => Ok(Type::DoNothing), @@ -262,6 +265,7 @@ impl Type { Rule::UpdateList => Ok(Type::UpdateList), Rule::UpdateItem => Ok(Type::UpdateItem), Rule::Unsigned => Ok(Type::Unsigned), + Rule::UserName => Ok(Type::UserName), Rule::Value => Ok(Type::Value), Rule::Values => Ok(Type::Values), Rule::ValuesRow => Ok(Type::ValuesRow), @@ -311,6 +315,7 @@ impl fmt::Display for Type { Type::Divide => "Divide".to_string(), Type::Double => "Double".to_string(), Type::DropTable => "DropTable".to_string(), + Type::DropUser => "DropUser".to_string(), Type::Duration => "Duration".to_string(), Type::DoReplace => "DoUpdateStrategy".to_string(), Type::DoNothing => "DoNothingStrategy".to_string(), @@ -388,6 +393,7 @@ impl fmt::Display for Type { Type::Update => "Update".to_string(), Type::UpdateItem => "UpdateItem".to_string(), Type::UpdateList => "UpdateList".to_string(), + Type::UserName => "UserName".to_string(), Type::Unsigned => "Unsigned".to_string(), Type::Value => "Value".to_string(), Type::Values => "Values".to_string(), diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 6bff739cd3..315c6f8e17 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -1,4 +1,8 @@ -Command = _{ SOI ~ (ExplainQuery | Query | DDL) ~ EOF } +Command = _{ SOI ~ (ExplainQuery | Query | DDL | ACL) ~ EOF } + +ACL = _{ DropUser } + DropUser = { ^"drop" ~ ^"user" ~ UserName ~ Option? } + UserName = @{ Name } DDL = _{ CreateTable | DropTable } CreateTable = { @@ -82,7 +86,7 @@ Query = { (Except | UnionAll | Select | Values | Insert | Update | Delete) ~ Opt ValuesRow = { Row } Option = _{ ^"option" ~ "(" ~ OptionParam ~ ("," ~ OptionParam)* ~ ")" } OptionParam = _{ Timeout | SqlVdbeMaxSteps | VTableMaxRows } - Timeout = { ^"timeout" ~ "=" ~ Duration} + Timeout = { ^"timeout" ~ "=" ~ Duration } Duration = @{ Unsigned ~ ("." ~ Unsigned)? } SqlVdbeMaxSteps = { ^"sql_vdbe_max_steps" ~ "=" ~ (Unsigned | Parameter) } VTableMaxRows = { ^"vtable_max_rows" ~ "=" ~ (Unsigned | Parameter) } diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs index 98b53b46c2..528ec874fb 100644 --- a/sbroad-core/src/ir.rs +++ b/sbroad-core/src/ir.rs @@ -11,6 +11,7 @@ use std::fmt::{Display, Formatter}; use std::slice::Iter; use tarantool::tlua; +use acl::Acl; use ddl::Ddl; use expression::Expression; use operator::{Arithmetic, Relational}; @@ -32,6 +33,7 @@ use crate::{collection, error, warn}; use self::parameters::Parameters; use self::relation::Relations; +pub mod acl; pub mod aggregates; pub mod ddl; pub mod distribution; @@ -63,6 +65,7 @@ const DEFAULT_VDBE_MAX_STEPS: u64 = 45000; /// dispatching and its performance penalties. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Node { + Acl(Acl), Ddl(Ddl), Expression(Expression), Relational(Relational), @@ -751,6 +754,15 @@ impl Plan { Ok(matches!(self.get_node(top_id)?, Node::Ddl(..))) } + /// Checks that plan is ACL query. + /// + /// # Errors + /// - top node doesn't exist in the plan or is invalid. + pub fn is_acl(&self) -> Result<bool, SbroadError> { + let top_id = self.get_top()?; + Ok(matches!(self.get_node(top_id)?, Node::Acl(..))) + } + /// Set top node of plan /// # Errors /// - top node doesn't exist in the plan. @@ -769,10 +781,12 @@ impl Plan { let node = self.get_node(node_id)?; match node { Node::Relational(rel) => Ok(rel), - Node::Expression(_) | Node::Parameter | Node::Ddl(..) => Err(SbroadError::Invalid( - Entity::Node, - Some(format!("node is not Relational type: {node:?}")), - )), + Node::Expression(_) | Node::Parameter | Node::Ddl(..) | Node::Acl(..) => { + Err(SbroadError::Invalid( + Entity::Node, + Some(format!("node is not Relational type: {node:?}")), + )) + } } } @@ -787,10 +801,9 @@ impl Plan { ) -> Result<&mut Relational, SbroadError> { match self.get_mut_node(node_id)? { Node::Relational(rel) => Ok(rel), - Node::Expression(_) | Node::Parameter | Node::Ddl(..) => Err(SbroadError::Invalid( - Entity::Node, - Some("Node is not relational".into()), - )), + Node::Expression(_) | Node::Parameter | Node::Ddl(..) | Node::Acl(..) => Err( + SbroadError::Invalid(Entity::Node, Some("Node is not relational".into())), + ), } } @@ -813,7 +826,7 @@ impl Plan { )) } } - Node::Relational(_) | Node::Ddl(..) => Err(SbroadError::Invalid( + Node::Relational(_) | Node::Ddl(..) | Node::Acl(..) => Err(SbroadError::Invalid( Entity::Node, Some("node is not Expression type".into()), )), @@ -832,10 +845,12 @@ impl Plan { let node = self.get_mut_node(node_id)?; match node { Node::Expression(exp) => Ok(exp), - Node::Relational(_) | Node::Parameter | Node::Ddl(..) => Err(SbroadError::Invalid( - Entity::Node, - Some(format!("node ({node_id}) is not expression type: {node:?}")), - )), + Node::Relational(_) | Node::Parameter | Node::Ddl(..) | Node::Acl(..) => { + Err(SbroadError::Invalid( + Entity::Node, + Some(format!("node ({node_id}) is not expression type: {node:?}")), + )) + } } } diff --git a/sbroad-core/src/ir/acl.rs b/sbroad-core/src/ir/acl.rs new file mode 100644 index 0000000000..53a702b62b --- /dev/null +++ b/sbroad-core/src/ir/acl.rs @@ -0,0 +1,80 @@ +use crate::ir::{Entity, Node, Plan, SbroadError}; +use serde::{Deserialize, Serialize}; +use tarantool::decimal::Decimal; + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +pub enum Acl { + DropUser { name: String, timeout: Decimal }, +} + +impl Acl { + /// Return ACL node timeout. + /// + /// # Errors + /// - timeout parsing error + pub fn timeout(&self) -> Result<f64, SbroadError> { + match self { + Acl::DropUser { ref timeout, .. } => timeout, + } + .to_string() + .parse() + .map_err(|e| { + SbroadError::Invalid( + Entity::SpaceMetadata, + Some(format!("timeout parsing error {e:?}")), + ) + }) + } +} + +impl Plan { + /// Get ACL node from the plan arena. + /// + /// # Errors + /// - the node index is absent in arena + /// - current node is not of ACL type + pub fn get_acl_node(&self, node_id: usize) -> Result<&Acl, SbroadError> { + let node = self.get_node(node_id)?; + match node { + Node::Acl(acl) => Ok(acl), + _ => Err(SbroadError::Invalid( + Entity::Node, + Some(format!("node is not ACL type: {node:?}")), + )), + } + } + + /// Get a mutable ACL node from the plan arena. + /// + /// # Errors + /// - the node index is absent in arena + /// - current node is not of ACL type + pub fn get_mut_acl_node(&mut self, node_id: usize) -> Result<&mut Acl, SbroadError> { + let node = self.get_mut_node(node_id)?; + match node { + Node::Acl(acl) => Ok(acl), + _ => Err(SbroadError::Invalid( + Entity::Node, + Some(format!("node is not ACL type: {node:?}")), + )), + } + } + + /// Take ACL node from the plan arena and replace it with parameter node. + /// + /// # Errors + /// - current node is not of ACL type + pub fn take_acl_node(&mut self, node_id: usize) -> Result<Acl, SbroadError> { + // Check that node is ACL type (before making any distructive operations). + let _ = self.get_acl_node(node_id)?; + // Replace ACL with parameter node. + let node = std::mem::replace(self.get_mut_node(node_id)?, Node::Parameter); + match node { + Node::Acl(acl) => Ok(acl), + _ => Err(SbroadError::Invalid( + Entity::Node, + Some(format!("node is not ACL type: {node:?}")), + )), + } + } +} diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs index a57212a629..9180092d77 100644 --- a/sbroad-core/src/ir/api/parameter.rs +++ b/sbroad-core/src/ir/api/parameter.rs @@ -174,7 +174,7 @@ impl Plan { } Expression::Constant { .. } | Expression::CountAsterisk => {} }, - Node::Parameter | Node::Ddl(..) => {} + Node::Parameter | Node::Ddl(..) | Node::Acl(..) => {} } } @@ -281,7 +281,7 @@ impl Plan { } Expression::Constant { .. } | Expression::CountAsterisk => {} }, - Node::Parameter | Node::Ddl(..) => {} + Node::Parameter | Node::Ddl(..) | Node::Acl(..) => {} } } diff --git a/sbroad-core/src/ir/distribution.rs b/sbroad-core/src/ir/distribution.rs index 43dcb5282c..b60278171b 100644 --- a/sbroad-core/src/ir/distribution.rs +++ b/sbroad-core/src/ir/distribution.rs @@ -650,6 +650,10 @@ impl Plan { Entity::Distribution, Some("Failed to get distribution for a DDL node.".to_string()), )), + Node::Acl(_) => Err(SbroadError::Invalid( + Entity::Distribution, + Some("Failed to get distribution for a ACL node.".to_string()), + )), } } diff --git a/sbroad-core/src/ir/expression/types.rs b/sbroad-core/src/ir/expression/types.rs index 72f1357f25..c850880af7 100644 --- a/sbroad-core/src/ir/expression/types.rs +++ b/sbroad-core/src/ir/expression/types.rs @@ -18,6 +18,10 @@ impl Plan { Entity::Node, Some("DDL node has no type".to_string()), )), + Node::Acl(_) => Err(SbroadError::Invalid( + Entity::Node, + Some("ACL node has no type".to_string()), + )), } } } diff --git a/sbroad-core/src/ir/tree/expression.rs b/sbroad-core/src/ir/tree/expression.rs index 6f612fdf8e..d31efa4446 100644 --- a/sbroad-core/src/ir/tree/expression.rs +++ b/sbroad-core/src/ir/tree/expression.rs @@ -180,7 +180,8 @@ fn expression_next<'nodes>( ) | Node::Relational(_) | Node::Parameter - | Node::Ddl(_), + | Node::Ddl(_) + | Node::Acl(_), ) | None => None, } diff --git a/sbroad-core/src/ir/tree/relation.rs b/sbroad-core/src/ir/tree/relation.rs index e7296406e5..06441067f1 100644 --- a/sbroad-core/src/ir/tree/relation.rs +++ b/sbroad-core/src/ir/tree/relation.rs @@ -89,7 +89,8 @@ fn relational_next<'nodes>( Node::Relational(Relational::ScanRelation { .. }) | Node::Expression(_) | Node::Parameter - | Node::Ddl(_), + | Node::Ddl(_) + | Node::Acl(_), ) | None => None, } diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs index a9434a6330..ad2a43002c 100644 --- a/sbroad-core/src/ir/tree/subtree.rs +++ b/sbroad-core/src/ir/tree/subtree.rs @@ -193,7 +193,7 @@ fn subtree_next<'plan>( ) -> Option<&'plan usize> { if let Some(child) = iter.get_nodes().arena.get(iter.get_current()) { return match child { - Node::Parameter | Node::Ddl(..) => None, + Node::Parameter | Node::Ddl(..) | Node::Acl(..) => None, Node::Expression(exp) => match exp { Expression::Alias { child, .. } | Expression::Cast { child, .. } -- GitLab