From 2859cbdb94a101bf577f9241aad7bd99856fff65 Mon Sep 17 00:00:00 2001 From: godzie44 <godzie@yandex.ru> Date: Fri, 19 Jul 2024 10:30:05 +0300 Subject: [PATCH] feature(plugin): add plugin SQL --- doc/sql/query.ebnf | 38 +- sbroad-cartridge/src/api/exec_query.rs | 3 + sbroad-core/src/backend/sql/ir.rs | 6 + sbroad-core/src/backend/sql/tree.rs | 1 + sbroad-core/src/executor.rs | 12 +- sbroad-core/src/executor/ir.rs | 1 + sbroad-core/src/frontend/sql.rs | 288 ++++++++++++- sbroad-core/src/frontend/sql/ast.rs | 24 ++ sbroad-core/src/frontend/sql/query.pest | 22 +- sbroad-core/src/ir.rs | 52 ++- sbroad-core/src/ir/api/parameter.rs | 7 +- sbroad-core/src/ir/block.rs | 2 + sbroad-core/src/ir/distribution.rs | 4 + sbroad-core/src/ir/expression/types.rs | 4 + sbroad-core/src/ir/node.rs | 41 +- sbroad-core/src/ir/node/plugin.rs | 523 ++++++++++++++++++++++++ sbroad-core/src/ir/tree/expression.rs | 1 + sbroad-core/src/ir/tree/relation.rs | 3 +- sbroad-core/src/ir/tree/subtree.rs | 3 +- 19 files changed, 1009 insertions(+), 26 deletions(-) create mode 100644 sbroad-core/src/ir/node/plugin.rs diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index f8b14009c..5c884df3d 100644 --- a/doc/sql/query.ebnf +++ b/doc/sql/query.ebnf @@ -106,17 +106,18 @@ grant ::= 'GRANT' ( | role ) 'TO' (role | user) -ddl ::= (alter_procedure | create_index | create_procedure | create_table - | drop_index | drop_procedure | drop_table | alter_system) - ('OPTION' '(' ('TIMEOUT' '=' double)')')? +ddl ::= (alter_plugin | alter_procedure | alter_system + | create_index | create_plugin | create_procedure | create_table + | drop_index | drop_plugin | drop_procedure | drop_table) alter_system ::= 'ALTER' 'SYSTEM' ( 'RESET' ('ALL' | param_name) | 'SET' param_name ('=' | 'TO') ('DEFAULT' | param_value) ) ('FOR' ('ALL' 'TIERS' | 'TIER' tier))? + ('OPTION' '(' ('TIMEOUT' '=' double)')')? alter_procedure ::= 'ALTER' 'PROCEDURE' procedure ('(' type (',' type)* ')')? - 'RENAME' 'TO' procedure + 'RENAME' 'TO' procedure ('OPTION' '(' ('TIMEOUT' '=' double)')')? create_index ::= 'CREATE' 'UNIQUE'? 'INDEX' index 'ON' table ('USING' ('TREE' | 'HASH' | 'RTREE' | 'BITSET'))? '(' column (',' column)* ')' ('WITH' '(' @@ -143,12 +144,12 @@ create_index ::= 'CREATE' 'UNIQUE'? 'INDEX' index 'ON' table | ('HINT' '=' ('TRUE' | 'FALSE')) ) )* - ')')? + ')')? ('OPTION' '(' ('TIMEOUT' '=' double)')')? create_procedure ::= 'CREATE' 'PROCEDURE' procedure '(' type (',' type)* ')' ('LANGUAGE' 'SQL')? ( ('AS' '$$' (insert | update | delete) '$$') | ('BEGIN' 'ATOMIC' (insert | update | delete) 'END') - ) + ) ('OPTION' '(' ('TIMEOUT' '=' double)')')? create_role ::= 'CREATE' 'ROLE' role create_table ::= 'CREATE' 'TABLE' table '(' @@ -157,6 +158,7 @@ create_table ::= 'CREATE' 'TABLE' table ')' ('USING' ('MEMTX' | 'VINYL'))? (('DISTRIBUTED' (('BY' '(' column (',' column)* ')' ('IN' 'TIER' tier)?) | 'GLOBALLY'))?)? + ('OPTION' '(' ('TIMEOUT' '=' double)')')? create_user ::= 'CREATE' 'USER' user 'WITH'? 'PASSWORD' "'" password "'" ('USING' ('CHAP-SHA1' | 'LDAP' | 'MD5'))? alter_user ::= 'ALTER' 'USER' user @@ -166,11 +168,29 @@ alter_user ::= 'ALTER' 'USER' user | 'PASSWORD' "'" password "'" ('USING' ('CHAP-SHA1' | 'LDAP' | 'MD5'))? | 'RENAME' 'TO' user ) -drop_index ::= 'DROP' 'INDEX' index -drop_procedure ::= 'DROP' 'PROCEDURE' procedure ('(' type (',' type)* ')')? -drop_table ::= 'DROP' 'TABLE' table +drop_index ::= 'DROP' 'INDEX' index ('OPTION' '(' ('TIMEOUT' '=' double)')')? +drop_procedure ::= 'DROP' 'PROCEDURE' procedure ('(' type (',' type)* ')')? ('OPTION' '(' ('TIMEOUT' '=' double)')')? +drop_table ::= 'DROP' 'TABLE' table ('OPTION' '(' ('TIMEOUT' '=' double)')')? drop_role ::= 'DROP' 'ROLE' role drop_user ::= 'DROP' 'USER' user +create_plugin ::= 'CREATE' 'PLUGIN' ('IF' 'NOT' 'EXISTS')? plugin version + ('OPTION' '(' ('TIMEOUT' '=' double)')')? +drop_plugin ::= 'DROP' 'PLUGIN' ('IF' 'EXISTS')? plugin version ('WITH' 'DATA')? + ('OPTION' '(' ('TIMEOUT' '=' double)')')? +alter_plugin ::= 'ALTER' 'PLUGIN' plugin ( + (version ( + 'ENABLE' | 'DISABLE' + | ('ADD' 'SERVICE' service 'TO' 'TIER' tier) + | ('REMOVE' 'SERVICE' service 'FROM' 'TIER' tier) + | ('SET' ((service'.'key '=' text) (',' service'.'key '=' text)*)) + )) + ('OPTION' '(' ('TIMEOUT' '=' double)')')? + | ('MIGRATE' 'TO' version) + ( 'OPTION' '(' + (('TIMEOUT' | 'ROLLBACK_TIMEOUT') '=' double) + (',' (('TIMEOUT' | 'ROLLBACK_TIMEOUT') '=' double))*')' + )? + ) type ::= 'BOOL' | 'BOOLEAN' | 'DATETIME' diff --git a/sbroad-cartridge/src/api/exec_query.rs b/sbroad-cartridge/src/api/exec_query.rs index 1ea8b976b..2020ece1a 100644 --- a/sbroad-cartridge/src/api/exec_query.rs +++ b/sbroad-cartridge/src/api/exec_query.rs @@ -44,6 +44,9 @@ fn dispatch_query_inner(args: &RawBytes) -> anyhow::Result<RawProcResult> { if let Ok(true) = query.is_block() { bail!("blocks of commands are not supported"); } + if let Ok(true) = query.is_plugin() { + bail!("plugin of commands are not supported"); + } let metadata = try_get_metadata_from_plan(query.get_exec_plan())?; let dispatch_result = query.dispatch()?; diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs index a9a360295..a7a73324f 100644 --- a/sbroad-core/src/backend/sql/ir.rs +++ b/sbroad-core/src/backend/sql/ir.rs @@ -389,6 +389,12 @@ impl ExecutionPlan { ), )); } + Node::Plugin(_) => { + return Err(SbroadError::Unsupported( + Entity::Node, + Some("Plugin are not supported in the generated SQL".into()), + )); + } Node::Relational(rel) => match rel { Relational::Except { .. } => sql.push_str("EXCEPT"), Relational::GroupBy { .. } => sql.push_str("GROUP BY"), diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs index e29b0a6c2..4dca82acc 100644 --- a/sbroad-core/src/backend/sql/tree.rs +++ b/sbroad-core/src/backend/sql/tree.rs @@ -752,6 +752,7 @@ impl<'p> SyntaxPlan<'p> { Node::Ddl(..) => panic!("DDL node {node:?} is not supported in the syntax plan"), Node::Acl(..) => panic!("ACL node {node:?} is not supported in the syntax plan"), Node::Block(..) => panic!("Block node {node:?} is not supported in the syntax plan"), + Node::Plugin(..) => panic!("Plugin node {node:?} is not supported in the syntax plan"), Node::Invalid(..) | Node::Parameter(..) => { let sn = SyntaxNode::new_parameter(id); self.nodes.push_sn_plan(sn); diff --git a/sbroad-core/src/executor.rs b/sbroad-core/src/executor.rs index 40554f155..da2c96d3b 100644 --- a/sbroad-core/src/executor.rs +++ b/sbroad-core/src/executor.rs @@ -144,13 +144,13 @@ where } plan.version_map = table_version_map; } - if !plan.is_ddl()? && !plan.is_acl()? { + if !plan.is_ddl()? && !plan.is_acl()? && !plan.is_plugin()? { cache.put(key, plan.clone())?; } } if plan.is_block()? { plan.bind_params(params)?; - } else if !plan.is_ddl()? && !plan.is_acl()? { + } else if !plan.is_ddl()? && !plan.is_acl()? && !plan.is_plugin()? { plan.bind_params(params)?; plan.apply_options()?; plan.optimize()?; @@ -342,6 +342,14 @@ where self.exec_plan.get_ir_plan().is_acl() } + /// Checks that query is for plugin. + /// + /// # Errors + /// - Plan is invalid + pub fn is_plugin(&self) -> Result<bool, SbroadError> { + self.exec_plan.get_ir_plan().is_plugin() + } + /// Checks that query is an empty query. pub fn is_empty(&self) -> bool { self.exec_plan.get_ir_plan().is_empty() diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs index f68740ef3..c5ca37882 100644 --- a/sbroad-core/src/executor/ir.rs +++ b/sbroad-core/src/executor/ir.rs @@ -805,6 +805,7 @@ impl ExecutionPlan { NodeOwned::Invalid { .. } | NodeOwned::Ddl { .. } | NodeOwned::Acl { .. } + | NodeOwned::Plugin { .. } | NodeOwned::Block { .. } => { panic!("Unexpected node in `take_subtree`: {node:?}") } diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 092c7dcca..2831d62b7 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -48,7 +48,7 @@ use crate::ir::operator::{ use crate::ir::relation::{Column, ColumnRole, TableKind, Type as RelationType}; use crate::ir::tree::traversal::{LevelNode, PostOrder, EXPR_CAPACITY}; use crate::ir::value::Value; -use crate::ir::{OptionKind, OptionParamValue, OptionSpec, Plan}; +use crate::ir::{node::plugin, OptionKind, OptionParamValue, OptionSpec, Plan}; use crate::otm::child_span; use crate::errors::Entity::AST; @@ -58,6 +58,10 @@ use crate::ir::acl::{GrantRevokeType, Privilege}; use crate::ir::aggregates::AggregateKind; use crate::ir::expression::NewColumnsSource; use crate::ir::helpers::RepeatableState; +use crate::ir::node::plugin::{ + AppendServiceToTier, ChangeConfig, CreatePlugin, DisablePlugin, DropPlugin, EnablePlugin, + MigrateTo, MigrateToOpts, RemoveServiceFromTier, ServiceSettings, SettingsPair, +}; use crate::ir::transformation::redistribution::ColumnPosition; use crate::warn; use sbroad_proc::otm_child_span; @@ -2596,6 +2600,257 @@ fn parse_select( select_expr.populate_plan(plan) } +fn parse_create_plugin( + ast: &AbstractSyntaxTree, + node: &ParseNode, +) -> Result<CreatePlugin, SbroadError> { + let mut if_not_exists = false; + let first_node_idx = node.first_child(); + let plugin_name_child_idx = if let Rule::IfNotExists = ast.nodes.get_node(first_node_idx)?.rule + { + if_not_exists = true; + 1 + } else { + 0 + }; + + let plugin_name_idx = node.child_n(plugin_name_child_idx); + let name = parse_identifier(ast, plugin_name_idx)?; + + let version_idx = node.child_n(plugin_name_child_idx + 1); + let version = parse_identifier(ast, version_idx)?; + + let mut timeout = plugin::get_default_timeout(); + if let Some(timeout_child_id) = node.children.get(plugin_name_child_idx + 2) { + timeout = get_timeout(ast, *timeout_child_id)?; + } + + Ok(CreatePlugin { + name, + version, + if_not_exists, + timeout, + }) +} + +fn parse_drop_plugin( + ast: &AbstractSyntaxTree, + node: &ParseNode, +) -> Result<DropPlugin, SbroadError> { + let mut if_exists = false; + let first_node_idx = node.first_child(); + let plugin_name_child_idx = if let Rule::IfExists = ast.nodes.get_node(first_node_idx)?.rule { + if_exists = true; + 1 + } else { + 0 + }; + + let plugin_name_idx = node.child_n(plugin_name_child_idx); + let name = parse_identifier(ast, plugin_name_idx)?; + let version_idx = node.child_n(plugin_name_child_idx + 1); + let version = parse_identifier(ast, version_idx)?; + + let mut with_data = false; + let timeout_idx = if let Rule::WithData = ast.nodes.get_node(plugin_name_child_idx + 2)?.rule { + with_data = true; + plugin_name_child_idx + 3 + } else { + plugin_name_child_idx + 2 + }; + + let mut timeout = plugin::get_default_timeout(); + if let Some(timeout_child_id) = node.children.get(timeout_idx) { + timeout = get_timeout(ast, *timeout_child_id)?; + } + + Ok(DropPlugin { + name, + version, + if_exists, + with_data, + timeout, + }) +} + +fn parse_alter_plugin( + ast: &AbstractSyntaxTree, + plan: &mut Plan, + node: &ParseNode, +) -> Result<Option<NodeId>, SbroadError> { + let plugin_name_idx = node.first_child(); + let plugin_name = parse_identifier(ast, plugin_name_idx)?; + + let idx = node.child_n(1); + let node = ast.nodes.get_node(idx)?; + let plugin_expr = match node.rule { + Rule::EnablePlugin => { + let version_idx = node.first_child(); + let version = parse_identifier(ast, version_idx)?; + + let mut timeout = plugin::get_default_timeout(); + if let Some(timeout_child_id) = node.children.get(1) { + timeout = get_timeout(ast, *timeout_child_id)?; + } + + Some( + plan.nodes.push( + EnablePlugin { + name: plugin_name, + version, + timeout, + } + .into(), + ), + ) + } + Rule::DisablePlugin => { + let version_idx = node.first_child(); + let version = parse_identifier(ast, version_idx)?; + + let mut timeout = plugin::get_default_timeout(); + if let Some(timeout_child_id) = node.children.get(1) { + timeout = get_timeout(ast, *timeout_child_id)?; + } + + Some( + plan.nodes.push( + DisablePlugin { + name: plugin_name, + version, + timeout, + } + .into(), + ), + ) + } + Rule::MigrateTo => { + let version_idx = node.first_child(); + let version = parse_identifier(ast, version_idx)?; + let opts = parse_plugin_opts::<MigrateToOpts>(ast, node, 1, |opts, node, idx| { + match node.rule { + Rule::Timeout => { + opts.timeout = get_timeout(ast, idx)?; + } + Rule::RollbackTimeout => { + opts.rollback_timeout = get_timeout(ast, idx)?; + } + _ => {} + }; + Ok(()) + })?; + + Some( + plan.nodes.push( + MigrateTo { + name: plugin_name, + version, + opts, + } + .into(), + ), + ) + } + Rule::AddServiceToTier => { + let version_idx = node.first_child(); + let version = parse_identifier(ast, version_idx)?; + let service_idx = node.child_n(1); + let service_name = parse_identifier(ast, service_idx)?; + let tier_idx = node.child_n(2); + let tier = parse_identifier(ast, tier_idx)?; + + let mut timeout = plugin::get_default_timeout(); + if let Some(timeout_child_id) = node.children.get(3) { + timeout = get_timeout(ast, *timeout_child_id)?; + } + + Some( + plan.nodes.push( + AppendServiceToTier { + plugin_name, + version, + service_name, + tier, + timeout, + } + .into(), + ), + ) + } + Rule::RemoveServiceFromTier => { + let version_idx = node.first_child(); + let version = parse_identifier(ast, version_idx)?; + let service_idx = node.child_n(1); + let service_name = parse_identifier(ast, service_idx)?; + let tier_idx = node.child_n(2); + let tier = parse_identifier(ast, tier_idx)?; + + let mut timeout = plugin::get_default_timeout(); + if let Some(timeout_child_id) = node.children.get(3) { + timeout = get_timeout(ast, *timeout_child_id)?; + } + + Some( + plan.nodes.push( + RemoveServiceFromTier { + plugin_name, + version, + service_name, + tier, + timeout, + } + .into(), + ), + ) + } + Rule::ChangeConfig => { + let version_idx = node.first_child(); + let version = parse_identifier(ast, version_idx)?; + + let mut key_value_grouped: HashMap<SmolStr, Vec<SettingsPair>> = HashMap::new(); + let mut timeout = plugin::get_default_timeout(); + + for &i in &node.children[1..] { + let next_node = ast.nodes.get_node(i)?; + if next_node.rule == Rule::ConfigKV { + let svc_idx = next_node.child_n(0); + let svc = parse_identifier(ast, svc_idx)?; + let key_idx = next_node.child_n(1); + let key = parse_identifier(ast, key_idx)?; + let value_idx = next_node.child_n(2); + let value = parse_string_literal(ast, value_idx)?; + let entry = key_value_grouped.entry(svc).or_default(); + entry.push(SettingsPair { key, value }); + } else { + timeout = get_timeout(ast, i)?; + break; + } + } + + Some( + plan.nodes.push( + ChangeConfig { + plugin_name, + version, + key_value_grouped: key_value_grouped + .into_iter() + .map(|(service, settings)| ServiceSettings { + name: service, + pairs: settings, + }) + .collect::<Vec<_>>(), + timeout, + } + .into(), + ), + ) + } + _ => None, + }; + + Ok(plugin_expr) +} + /// Generate an alias for the unnamed projection expressions. #[must_use] pub fn get_unnamed_column_alias(pos: usize) -> SmolStr { @@ -3811,6 +4066,21 @@ impl AbstractSyntaxTree { let plan_id = plan.nodes.push(create_role.into()); map.add(id, plan_id); } + Rule::CreatePlugin => { + let create_plugin = parse_create_plugin(self, node)?; + let plan_id = plan.nodes.push(create_plugin.into()); + map.add(id, plan_id); + } + Rule::DropPlugin => { + let drop_plugin = parse_drop_plugin(self, node)?; + let plan_id = plan.nodes.push(drop_plugin.into()); + map.add(id, plan_id); + } + Rule::AlterPlugin => { + if let Some(node_id) = parse_alter_plugin(self, &mut plan, node)? { + map.add(id, node_id); + } + } Rule::SetParam => { let set_param_node = parse_set_param(self, node)?; let plan_id = plan.nodes.push(set_param_node.into()); @@ -3907,3 +4177,19 @@ impl Plan { pub mod ast; pub mod ir; pub mod tree; + +fn parse_plugin_opts<T: Default>( + ast: &AbstractSyntaxTree, + node: &ParseNode, + start_from: usize, + mut with_opt_node: impl FnMut(&mut T, &ParseNode, usize) -> Result<(), SbroadError>, +) -> Result<T, SbroadError> { + let mut opts = T::default(); + + for ¶m_idx in &node.children[start_from..] { + let param_node = ast.nodes.get_node(param_idx)?; + with_opt_node(&mut opts, param_node, param_idx)?; + } + + Ok(opts) +} diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs index 64be86560..0b61f1791 100644 --- a/sbroad-core/src/frontend/sql/ast.rs +++ b/sbroad-core/src/frontend/sql/ast.rs @@ -35,6 +35,30 @@ impl ParseNode { value, } } + + /// Return first child from node children. + /// + /// # Panics + /// + /// Panics a children array is empty. + pub(super) fn first_child(&self) -> usize { + *self + .children + .first() + .expect("could not find first child in node") + } + + /// Return a nth child from node children. + /// + /// # Panics + /// + /// Panics if there is no n-child in a children array. + pub(super) fn child_n(&self, n: usize) -> usize { + *self + .children + .get(n) + .unwrap_or_else(|| panic!("could find {n} child in node")) + } } /// A storage arena of the parse nodes diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 2c397badd..ab49f8d03 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 | EmptyQuery) ~ EOF } +Command = _{ SOI ~ (Query | ExplainQuery | Block | DDL | ACL | Plugin | EmptyQuery) ~ EOF } // Helper rule to denote we have to update plan relations from metadata // (with Table which name corresponds to current node). @@ -8,6 +8,26 @@ Table = @{ Identifier } ScanTable = { Table } ScanCteOrTable = @{ Table } +Plugin = _{ CreatePlugin | DropPlugin | AlterPlugin } + CreatePlugin = { ^"create" ~ ^"plugin" ~ IfNotExists? ~ Identifier ~ PluginVersion ~ TimeoutOption? } + PluginVersion = @{ Unsigned ~ "." ~ Unsigned ~ "." ~ Unsigned } + IfNotExists = { ^"if" ~ ^"not" ~ ^"exists" } + DropPlugin = { ^"drop" ~ ^"plugin" ~ IfExists? ~ Identifier ~ PluginVersion ~ WithData? ~ TimeoutOption? } + IfExists = { ^"if" ~ ^"exists" } + WithData = { ^"with" ~ ^"data" } + AlterPlugin = { ^"alter" ~ ^"plugin" ~ Identifier ~ AlterVariant } + AlterVariant = _{ EnablePlugin | DisablePlugin | MigrateTo | AddServiceToTier | RemoveServiceFromTier | ChangeConfig } + EnablePlugin = { PluginVersion ~ ^"enable" ~ TimeoutOption? } + DisablePlugin = { PluginVersion ~ ^"disable" ~ TimeoutOption? } + MigrateTo = { ^"migrate" ~ ^"to" ~ PluginVersion ~ MigrateUpOption? } + RollbackTimeout = { ^"rollback_timeout" ~ "=" ~ Duration } + MigrateUpOptionParam = _{ Timeout | RollbackTimeout } + MigrateUpOption = _{ ^"option" ~ "(" ~ MigrateUpOptionParam ~ ("," ~ MigrateUpOptionParam)* ~ ")" } + AddServiceToTier = { PluginVersion ~ ^"add" ~ ^"service" ~ Identifier ~ ^"to" ~ ^"tier" ~ Identifier ~ TimeoutOption? } + RemoveServiceFromTier = { PluginVersion ~ ^"remove" ~ ^"service" ~ Identifier ~ ^"from" ~ ^"tier" ~ Identifier ~ TimeoutOption? } + ChangeConfig = { PluginVersion ~ ^"set" ~ ConfigKV ~ ("," ~ ConfigKV)* ~ TimeoutOption? } + ConfigKV = { Identifier ~ ^"." ~ Identifier ~ "=" ~ SingleQuotedString } + ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivilege | RevokePrivilege } CreateUser = { ^"create" ~ ^"user" ~ Identifier ~ (^"with")? ~ ^"password" ~ SingleQuotedString ~ diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs index e9094fd3e..52473088f 100644 --- a/sbroad-core/src/ir.rs +++ b/sbroad-core/src/ir.rs @@ -22,11 +22,15 @@ use tarantool::tlua; use operator::Arithmetic; use relation::{Table, Type}; +use self::parameters::Parameters; +use self::relation::Relations; +use self::transformation::redistribution::MotionPolicy; use crate::errors::Entity::Query; use crate::errors::{Action, Entity, SbroadError, TypeError}; use crate::executor::engine::helpers::to_user; use crate::executor::engine::TableVersionMap; use crate::ir::helpers::RepeatableState; +use crate::ir::node::plugin::{MutPlugin, Plugin}; use crate::ir::node::{ Alias, ArenaType, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Constant, ExprInParentheses, GroupBy, Having, Insert, Limit, Motion, MutNode, Node, Node136, Node224, Node32, Node64, @@ -42,10 +46,6 @@ use crate::ir::undo::TransformationLog; use crate::ir::value::Value; use crate::{collection, error, warn}; -use self::parameters::Parameters; -use self::relation::Relations; -use self::transformation::redistribution::MotionPolicy; - // TODO: remove when rust version in bumped in module #[allow(elided_lifetimes_in_associated_constant)] pub mod acl; @@ -142,6 +142,10 @@ impl Nodes { Node96::StableFunction(stable_func) => { Node::Expression(Expression::StableFunction(stable_func)) } + Node96::CreatePlugin(create) => Node::Plugin(Plugin::Create(create)), + Node96::EnablePlugin(enable) => Node::Plugin(Plugin::Enable(enable)), + Node96::DisablePlugin(disable) => Node::Plugin(Plugin::Disable(disable)), + Node96::DropPlugin(drop) => Node::Plugin(Plugin::Drop(drop)), }), ArenaType::Arena136 => self .arena136 @@ -163,6 +167,10 @@ impl Nodes { Node136::RenameRoutine(rename_routine) => { Node::Ddl(Ddl::RenameRoutine(rename_routine)) } + Node136::MigrateTo(migrate) => Node::Plugin(Plugin::MigrateTo(migrate)), + Node136::ChangeConfig(change_config) => { + Node::Plugin(Plugin::ChangeConfig(change_config)) + } }), ArenaType::Arena224 => self .arena224 @@ -171,6 +179,12 @@ impl Nodes { Node224::CreateIndex(create_index) => Node::Ddl(Ddl::CreateIndex(create_index)), Node224::CreateTable(create_table) => Node::Ddl(Ddl::CreateTable(create_table)), Node224::Invalid(inv) => Node::Invalid(inv), + Node224::AppendServiceToTier(append) => { + Node::Plugin(Plugin::AppendServiceToTier(append)) + } + Node224::RemoveServiceFromTier(remove) => { + Node::Plugin(Plugin::RemoveServiceFromTier(remove)) + } }), } } @@ -270,6 +284,10 @@ impl Nodes { Node96::StableFunction(stable_func) => { MutNode::Expression(MutExpression::StableFunction(stable_func)) } + Node96::CreatePlugin(create) => MutNode::Plugin(MutPlugin::Create(create)), + Node96::EnablePlugin(enable) => MutNode::Plugin(MutPlugin::Enable(enable)), + Node96::DisablePlugin(disable) => MutNode::Plugin(MutPlugin::Disable(disable)), + Node96::DropPlugin(drop) => MutNode::Plugin(MutPlugin::Drop(drop)), }), ArenaType::Arena136 => { self.arena136 @@ -303,6 +321,12 @@ impl Nodes { Node136::RenameRoutine(rename_routine) => { MutNode::Ddl(MutDdl::RenameRoutine(rename_routine)) } + Node136::MigrateTo(migrate) => { + MutNode::Plugin(MutPlugin::MigrateTo(migrate)) + } + Node136::ChangeConfig(change_config) => { + MutNode::Plugin(MutPlugin::ChangeConfig(change_config)) + } }) } ArenaType::Arena224 => { @@ -316,6 +340,12 @@ impl Nodes { Node224::CreateTable(create_table) => { MutNode::Ddl(MutDdl::CreateTable(create_table)) } + Node224::AppendServiceToTier(append) => { + MutNode::Plugin(MutPlugin::AppendServiceToTier(append)) + } + Node224::RemoveServiceFromTier(remove) => { + MutNode::Plugin(MutPlugin::RemoveServiceFromTier(remove)) + } }) } } @@ -1337,6 +1367,15 @@ impl Plan { Ok(matches!(top, Node::Acl(_))) } + /// Checks that plan is a plugin query. + /// + /// # Errors + /// - top node doesn't exist in the plan or is invalid. + pub fn is_plugin(&self) -> Result<bool, SbroadError> { + let top_id = self.get_top()?; + Ok(matches!(self.get_node(top_id)?, Node::Plugin(_))) + } + /// Set top node of plan /// # Errors /// - top node doesn't exist in the plan. @@ -1360,7 +1399,8 @@ impl Plan { | Node::Ddl(..) | Node::Invalid(..) | Node::Acl(..) - | Node::Block(..) => Err(SbroadError::Invalid( + | Node::Block(..) + | Node::Plugin(..) => Err(SbroadError::Invalid( Entity::Node, Some(format_smolstr!("node is not Relational type: {node:?}")), )), @@ -1380,6 +1420,7 @@ impl Plan { | MutNode::Ddl(..) | MutNode::Invalid(..) | MutNode::Acl(..) + | MutNode::Plugin(..) | MutNode::Block(..) => Err(SbroadError::Invalid( Entity::Node, Some("Node is not relational".into()), @@ -1429,6 +1470,7 @@ impl Plan { | MutNode::Ddl(..) | MutNode::Invalid(..) | MutNode::Acl(..) + | MutNode::Plugin(..) | MutNode::Block(..) => Err(SbroadError::Invalid( Entity::Node, Some(format_smolstr!( diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs index 2a16c6b89..91b9902fd 100644 --- a/sbroad-core/src/ir/api/parameter.rs +++ b/sbroad-core/src/ir/api/parameter.rs @@ -354,7 +354,11 @@ impl<'binder> ParamsBinder<'binder> { } } }, - Node::Invalid(..) | Node::Parameter(..) | Node::Ddl(..) | Node::Acl(..) => {} + Node::Invalid(..) + | Node::Parameter(..) + | Node::Ddl(..) + | Node::Acl(..) + | Node::Plugin(_) => {} } } @@ -529,6 +533,7 @@ impl<'binder> ParamsBinder<'binder> { MutNode::Invalid(..) | MutNode::Parameter(..) | MutNode::Ddl(..) + | MutNode::Plugin(_) | MutNode::Acl(..) => {} } } diff --git a/sbroad-core/src/ir/block.rs b/sbroad-core/src/ir/block.rs index e1d0c3d11..1891ac175 100644 --- a/sbroad-core/src/ir/block.rs +++ b/sbroad-core/src/ir/block.rs @@ -21,6 +21,7 @@ impl Plan { | Node::Ddl(..) | Node::Acl(..) | Node::Invalid(..) + | Node::Plugin(_) | Node::Parameter(..) => Err(SbroadError::Invalid( Entity::Node, Some(format_smolstr!( @@ -43,6 +44,7 @@ impl Plan { | MutNode::Ddl(..) | MutNode::Acl(..) | MutNode::Invalid(..) + | MutNode::Plugin(_) | MutNode::Parameter(..) => Err(SbroadError::Invalid( Entity::Node, Some(format_smolstr!( diff --git a/sbroad-core/src/ir/distribution.rs b/sbroad-core/src/ir/distribution.rs index ac2168c8b..1e8451f59 100644 --- a/sbroad-core/src/ir/distribution.rs +++ b/sbroad-core/src/ir/distribution.rs @@ -833,6 +833,10 @@ impl Plan { Entity::Distribution, Some("Failed to get distribution for an invalid node.".to_smolstr()), )), + Node::Plugin(_) => Err(SbroadError::Invalid( + Entity::Distribution, + Some("Failed to get distribution for a PLUGIN block node.".to_smolstr()), + )), } } diff --git a/sbroad-core/src/ir/expression/types.rs b/sbroad-core/src/ir/expression/types.rs index cd0c8e71a..ae6614412 100644 --- a/sbroad-core/src/ir/expression/types.rs +++ b/sbroad-core/src/ir/expression/types.rs @@ -39,6 +39,10 @@ impl Plan { Entity::Node, Some("code block node has no type".to_smolstr()), )), + Node::Plugin(_) => Err(SbroadError::Invalid( + Entity::Node, + Some("Plugin node has no type".to_smolstr()), + )), } } } diff --git a/sbroad-core/src/ir/node.rs b/sbroad-core/src/ir/node.rs index bcd47df43..cd3ddf822 100644 --- a/sbroad-core/src/ir/node.rs +++ b/sbroad-core/src/ir/node.rs @@ -13,6 +13,11 @@ use tarantool::{ space::SpaceEngineType, }; +use super::{ + ddl::AlterSystemType, + expression::{cast, FunctionFeature, TrimKind}, + operator::{self, ConflictStrategy, JoinKind, OrderByElement, UpdateStrategy}, +}; use crate::ir::{ acl::{AlterOption, GrantRevokeType}, ddl::{ColumnDef, Language, ParamDef, SetParamScopeType, SetParamValue}, @@ -22,17 +27,16 @@ use crate::ir::{ transformation::redistribution::{ColumnPosition, MotionPolicy, Program}, value::Value, }; - -use super::{ - ddl::AlterSystemType, - expression::{cast, FunctionFeature, TrimKind}, - operator::{self, ConflictStrategy, JoinKind, OrderByElement, UpdateStrategy}, +use plugin::{ + AppendServiceToTier, ChangeConfig, CreatePlugin, DisablePlugin, DropPlugin, EnablePlugin, + MigrateTo, MutPlugin, Plugin, PluginOwned, RemoveServiceFromTier, }; pub mod acl; pub mod block; pub mod ddl; pub mod expression; +pub mod plugin; pub mod relational; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Hash, Copy)] @@ -1106,6 +1110,10 @@ pub enum Node96 { StableFunction(StableFunction), DropProc(DropProc), Insert(Insert), + CreatePlugin(CreatePlugin), + EnablePlugin(EnablePlugin), + DisablePlugin(DisablePlugin), + DropPlugin(DropPlugin), } impl Node96 { @@ -1119,6 +1127,10 @@ impl Node96 { Node96::StableFunction(stable_func) => { NodeOwned::Expression(ExprOwned::StableFunction(stable_func)) } + Node96::DropPlugin(drop_plugin) => NodeOwned::Plugin(PluginOwned::Drop(drop_plugin)), + Node96::CreatePlugin(create) => NodeOwned::Plugin(PluginOwned::Create(create)), + Node96::EnablePlugin(enable) => NodeOwned::Plugin(PluginOwned::Enable(enable)), + Node96::DisablePlugin(disable) => NodeOwned::Plugin(PluginOwned::Disable(disable)), } } } @@ -1136,6 +1148,8 @@ pub enum Node136 { GrantPrivilege(GrantPrivilege), RevokePrivilege(RevokePrivilege), Update(Update), + MigrateTo(MigrateTo), + ChangeConfig(ChangeConfig), } impl Node136 { @@ -1160,6 +1174,10 @@ impl Node136 { Node136::RenameRoutine(rename_routine) => { NodeOwned::Ddl(DdlOwned::RenameRoutine(rename_routine)) } + Node136::MigrateTo(migrate) => NodeOwned::Plugin(PluginOwned::MigrateTo(migrate)), + Node136::ChangeConfig(change_config) => { + NodeOwned::Plugin(PluginOwned::ChangeConfig(change_config)) + } } } } @@ -1170,6 +1188,8 @@ pub enum Node224 { Invalid(Invalid), CreateTable(CreateTable), CreateIndex(CreateIndex), + AppendServiceToTier(AppendServiceToTier), + RemoveServiceFromTier(RemoveServiceFromTier), } impl Node224 { @@ -1183,6 +1203,12 @@ impl Node224 { NodeOwned::Ddl(DdlOwned::CreateIndex(create_index)) } Node224::Invalid(inv) => NodeOwned::Invalid(inv), + Node224::AppendServiceToTier(append) => { + NodeOwned::Plugin(PluginOwned::AppendServiceToTier(append)) + } + Node224::RemoveServiceFromTier(remove) => { + NodeOwned::Plugin(PluginOwned::RemoveServiceFromTier(remove)) + } } } } @@ -1235,6 +1261,7 @@ pub enum Node<'nodes> { Block(Block<'nodes>), Parameter(&'nodes Parameter), Invalid(&'nodes Invalid), + Plugin(Plugin<'nodes>), } #[allow(clippy::module_name_repetitions)] @@ -1247,6 +1274,7 @@ pub enum MutNode<'nodes> { Block(MutBlock<'nodes>), Parameter(&'nodes mut Parameter), Invalid(&'nodes mut Invalid), + Plugin(MutPlugin<'nodes>), } impl Node<'_> { @@ -1260,6 +1288,7 @@ impl Node<'_> { Node::Block(block) => NodeOwned::Block(block.get_block_owned()), Node::Parameter(param) => NodeOwned::Parameter((*param).clone()), Node::Invalid(inv) => NodeOwned::Invalid((*inv).clone()), + Node::Plugin(plugin) => NodeOwned::Plugin(plugin.get_plugin_owned()), } } } @@ -1275,6 +1304,7 @@ pub enum NodeOwned { Block(BlockOwned), Parameter(Parameter), Invalid(Invalid), + Plugin(PluginOwned), } impl From<NodeOwned> for NodeAligned { @@ -1287,6 +1317,7 @@ impl From<NodeOwned> for NodeAligned { NodeOwned::Invalid(inv) => inv.into(), NodeOwned::Parameter(param) => param.into(), NodeOwned::Relational(rel) => rel.into(), + NodeOwned::Plugin(p) => p.into(), } } } diff --git a/sbroad-core/src/ir/node/plugin.rs b/sbroad-core/src/ir/node/plugin.rs new file mode 100644 index 000000000..1717fe562 --- /dev/null +++ b/sbroad-core/src/ir/node/plugin.rs @@ -0,0 +1,523 @@ +use crate::errors::{Entity, SbroadError}; +use crate::ir::node::{Node136, Node224, Node96, NodeAligned}; +use crate::ir::{Node, NodeId, Plan}; +use serde::{Deserialize, Serialize}; +use smol_str::{format_smolstr, SmolStr}; +use tarantool::decimal::Decimal; + +#[must_use] +pub fn get_default_timeout() -> Decimal { + Decimal::from(10) +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MigrateToOpts { + pub timeout: Decimal, + pub rollback_timeout: Decimal, +} + +impl Default for MigrateToOpts { + fn default() -> Self { + MigrateToOpts { + timeout: get_default_timeout(), + rollback_timeout: get_default_timeout(), + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, PartialOrd, Ord)] +pub struct SettingsPair { + pub key: SmolStr, + pub value: SmolStr, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, PartialOrd, Ord)] +pub struct ServiceSettings { + pub name: SmolStr, + pub pairs: Vec<SettingsPair>, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct CreatePlugin { + pub name: SmolStr, + pub version: SmolStr, + pub if_not_exists: bool, + pub timeout: Decimal, +} + +impl From<CreatePlugin> for NodeAligned { + fn from(value: CreatePlugin) -> Self { + Self::Node96(Node96::CreatePlugin(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct EnablePlugin { + pub name: SmolStr, + pub version: SmolStr, + pub timeout: Decimal, +} + +impl From<EnablePlugin> for NodeAligned { + fn from(value: EnablePlugin) -> Self { + Self::Node96(Node96::EnablePlugin(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct DisablePlugin { + pub name: SmolStr, + pub version: SmolStr, + pub timeout: Decimal, +} + +impl From<DisablePlugin> for NodeAligned { + fn from(value: DisablePlugin) -> Self { + Self::Node96(Node96::DisablePlugin(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct DropPlugin { + pub name: SmolStr, + pub version: SmolStr, + pub if_exists: bool, + pub with_data: bool, + pub timeout: Decimal, +} + +impl From<DropPlugin> for NodeAligned { + fn from(value: DropPlugin) -> Self { + Self::Node96(Node96::DropPlugin(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MigrateTo { + pub name: SmolStr, + pub version: SmolStr, + pub opts: MigrateToOpts, +} + +impl From<MigrateTo> for NodeAligned { + fn from(value: MigrateTo) -> Self { + Self::Node136(Node136::MigrateTo(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct AppendServiceToTier { + pub plugin_name: SmolStr, + pub version: SmolStr, + pub service_name: SmolStr, + pub tier: SmolStr, + pub timeout: Decimal, +} + +impl From<AppendServiceToTier> for NodeAligned { + fn from(value: AppendServiceToTier) -> Self { + Self::Node224(Node224::AppendServiceToTier(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct RemoveServiceFromTier { + pub plugin_name: SmolStr, + pub version: SmolStr, + pub service_name: SmolStr, + pub tier: SmolStr, + pub timeout: Decimal, +} + +impl From<RemoveServiceFromTier> for NodeAligned { + fn from(value: RemoveServiceFromTier) -> Self { + Self::Node224(Node224::RemoveServiceFromTier(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct ChangeConfig { + pub plugin_name: SmolStr, + pub version: SmolStr, + pub key_value_grouped: Vec<ServiceSettings>, + pub timeout: Decimal, +} + +impl From<ChangeConfig> for NodeAligned { + fn from(value: ChangeConfig) -> Self { + Self::Node136(Node136::ChangeConfig(value)) + } +} + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub enum MutPlugin<'a> { + Create(&'a mut CreatePlugin), + Enable(&'a mut EnablePlugin), + Disable(&'a mut DisablePlugin), + Drop(&'a mut DropPlugin), + MigrateTo(&'a mut MigrateTo), + AppendServiceToTier(&'a mut AppendServiceToTier), + RemoveServiceFromTier(&'a mut RemoveServiceFromTier), + ChangeConfig(&'a mut ChangeConfig), +} + +/// Represent a plugin query. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Plugin<'a> { + /// Create a new plugin. + Create(&'a CreatePlugin), + /// Enable plugin. + Enable(&'a EnablePlugin), + /// Disable plugin. + Disable(&'a DisablePlugin), + /// Remove plugin from system. + Drop(&'a DropPlugin), + /// Run installed plugin migrations. + MigrateTo(&'a MigrateTo), + /// Append plugin service to a tier. + AppendServiceToTier(&'a AppendServiceToTier), + /// Remove plugin service from tier. + RemoveServiceFromTier(&'a RemoveServiceFromTier), + /// Change plugin service configuration. + ChangeConfig(&'a ChangeConfig), +} + +impl<'a> Plugin<'a> { + #[must_use] + pub fn get_plugin_owned(&self) -> PluginOwned { + match self { + Plugin::Create(create) => PluginOwned::Create((*create).clone()), + Plugin::Enable(enable) => PluginOwned::Enable((*enable).clone()), + Plugin::Disable(disable) => PluginOwned::Disable((*disable).clone()), + Plugin::Drop(drop) => PluginOwned::Drop((*drop).clone()), + Plugin::MigrateTo(migrate_to) => PluginOwned::MigrateTo((*migrate_to).clone()), + Plugin::AppendServiceToTier(add_to_tier) => { + PluginOwned::AppendServiceToTier((*add_to_tier).clone()) + } + Plugin::RemoveServiceFromTier(rm_from_tier) => { + PluginOwned::RemoveServiceFromTier((*rm_from_tier).clone()) + } + Plugin::ChangeConfig(change_config) => { + PluginOwned::ChangeConfig((*change_config).clone()) + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum PluginOwned { + /// Create a new plugin. + Create(CreatePlugin), + /// Enable plugin. + Enable(EnablePlugin), + /// Disable plugin. + Disable(DisablePlugin), + /// Remove plugin from system. + Drop(DropPlugin), + /// Run installed plugin migrations. + MigrateTo(MigrateTo), + /// Append plugin service to a tier. + AppendServiceToTier(AppendServiceToTier), + /// Remove plugin service from tier. + RemoveServiceFromTier(RemoveServiceFromTier), + /// Change plugin service configuration. + ChangeConfig(ChangeConfig), +} + +impl From<PluginOwned> for NodeAligned { + fn from(value: PluginOwned) -> Self { + match value { + PluginOwned::Create(create) => create.into(), + PluginOwned::Enable(enable) => enable.into(), + PluginOwned::Disable(disable) => disable.into(), + PluginOwned::Drop(drop) => drop.into(), + PluginOwned::MigrateTo(migrate) => migrate.into(), + PluginOwned::AppendServiceToTier(add) => add.into(), + PluginOwned::RemoveServiceFromTier(rm) => rm.into(), + PluginOwned::ChangeConfig(change_config) => change_config.into(), + } + } +} + +impl Plan { + /// Get a reference to a plugin node. + /// + /// # Errors + /// - the node is not a block node. + pub fn get_plugin_node(&self, node_id: NodeId) -> Result<Plugin, SbroadError> { + let node = self.get_node(node_id)?; + match node { + Node::Plugin(plugin) => Ok(plugin), + _ => Err(SbroadError::Invalid( + Entity::Node, + Some(format_smolstr!( + "node {node:?} (id {node_id}) is not Block type" + )), + )), + } + } +} + +#[cfg(test)] +mod test { + use super::{ + get_default_timeout, AppendServiceToTier, ChangeConfig, CreatePlugin, DisablePlugin, + DropPlugin, EnablePlugin, MigrateTo, MigrateToOpts, PluginOwned, RemoveServiceFromTier, + ServiceSettings, SettingsPair, + }; + use crate::executor::engine::mock::RouterConfigurationMock; + use crate::frontend::sql::ast::AbstractSyntaxTree; + use crate::frontend::Ast; + use crate::ir::node::{ArenaType, NodeId}; + use crate::ir::Plan; + use smol_str::SmolStr; + use tarantool::decimal::Decimal; + + #[test] + fn test_plugin_parsing() { + struct TestCase { + sql: &'static str, + arena_type: ArenaType, + expected: PluginOwned, + } + + let test_cases = &[ + TestCase { + sql: r#"CREATE PLUGIN "abc" 0.1.1"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Create(CreatePlugin { + name: SmolStr::from("abc"), + version: SmolStr::from("0.1.1"), + if_not_exists: false, + timeout: get_default_timeout(), + }), + }, + TestCase { + sql: r#"CREATE PLUGIN "test_plugin" 0.0.1"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Create(CreatePlugin { + name: SmolStr::from("test_plugin"), + version: SmolStr::from("0.0.1"), + if_not_exists: false, + timeout: get_default_timeout(), + }), + }, + TestCase { + sql: r#"CREATE PLUGIN IF NOT EXISTS "abc" 0.1.1"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Create(CreatePlugin { + name: SmolStr::from("abc"), + version: SmolStr::from("0.1.1"), + if_not_exists: true, + timeout: get_default_timeout(), + }), + }, + TestCase { + sql: r#"CREATE PLUGIN IF NOT EXISTS "abcde" 0.1.2 option(timeout=1)"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Create(CreatePlugin { + name: SmolStr::from("abcde"), + version: SmolStr::from("0.1.2"), + if_not_exists: true, + timeout: Decimal::from(1), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 1.1.1 ENABLE"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Enable(EnablePlugin { + name: SmolStr::from("abc"), + version: SmolStr::from("1.1.1"), + timeout: Decimal::from(10), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 1.1.1 ENABLE option(timeout=1)"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Enable(EnablePlugin { + name: SmolStr::from("abc"), + version: SmolStr::from("1.1.1"), + timeout: Decimal::from(1), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 1.1.1 DISABLE option(timeout=1)"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Disable(DisablePlugin { + name: SmolStr::from("abc"), + version: SmolStr::from("1.1.1"), + timeout: Decimal::from(1), + }), + }, + TestCase { + sql: r#"DROP PLUGIN "abc" 1.1.1 option(timeout=1)"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Drop(DropPlugin { + name: SmolStr::from("abc"), + version: SmolStr::from("1.1.1"), + if_exists: false, + with_data: false, + timeout: Decimal::from(1), + }), + }, + TestCase { + sql: r#"DROP PLUGIN IF EXISTS "abcde" 1.1.1 WITH DATA option(timeout=10)"#, + arena_type: ArenaType::Arena96, + expected: PluginOwned::Drop(DropPlugin { + name: SmolStr::from("abcde"), + version: SmolStr::from("1.1.1"), + if_exists: true, + with_data: true, + timeout: Decimal::from(10), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" MIGRATE TO 0.1.0"#, + arena_type: ArenaType::Arena136, + expected: PluginOwned::MigrateTo(MigrateTo { + name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + opts: MigrateToOpts { + timeout: get_default_timeout(), + rollback_timeout: get_default_timeout(), + }, + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" MIGRATE TO 0.1.0 option(timeout=11, rollback_timeout=12)"#, + arena_type: ArenaType::Arena136, + expected: PluginOwned::MigrateTo(MigrateTo { + name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + opts: MigrateToOpts { + timeout: Decimal::from(11), + rollback_timeout: Decimal::from(12), + }, + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 0.1.0 ADD SERVICE "svc1" TO TIER "tier1" option(timeout=1)"#, + arena_type: ArenaType::Arena224, + expected: PluginOwned::AppendServiceToTier(AppendServiceToTier { + service_name: SmolStr::from("svc1"), + plugin_name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + tier: SmolStr::from("tier1"), + timeout: Decimal::from(1), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 0.1.0 REMOVE SERVICE "svc1" FROM TIER "tier1" option(timeout=11)"#, + arena_type: ArenaType::Arena224, + expected: PluginOwned::RemoveServiceFromTier(RemoveServiceFromTier { + service_name: SmolStr::from("svc1"), + plugin_name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + tier: SmolStr::from("tier1"), + timeout: Decimal::from(11), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 0.1.0 SET "svc1"."key1" = '{"a": 1, "b": 2}' option(timeout=12)"#, + arena_type: ArenaType::Arena136, + expected: PluginOwned::ChangeConfig(ChangeConfig { + plugin_name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + key_value_grouped: vec![ServiceSettings { + name: SmolStr::from("svc1"), + pairs: vec![SettingsPair { + key: SmolStr::from("key1"), + value: SmolStr::from("{\"a\": 1, \"b\": 2}"), + }], + }], + timeout: Decimal::from(12), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 0.1.0 SET "svc1"."key1" = 'a', "svc2"."key2" = 'b', "svc3"."key3" = 'c' option(timeout=11)"#, + arena_type: ArenaType::Arena136, + expected: PluginOwned::ChangeConfig(ChangeConfig { + plugin_name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + key_value_grouped: vec![ + ServiceSettings { + name: SmolStr::from("svc1"), + pairs: vec![SettingsPair { + key: SmolStr::from("key1"), + value: SmolStr::from("a"), + }], + }, + ServiceSettings { + name: SmolStr::from("svc2"), + pairs: vec![SettingsPair { + key: SmolStr::from("key2"), + value: SmolStr::from("b"), + }], + }, + ServiceSettings { + name: SmolStr::from("svc3"), + pairs: vec![SettingsPair { + key: SmolStr::from("key3"), + value: SmolStr::from("c"), + }], + }, + ], + timeout: Decimal::from(11), + }), + }, + TestCase { + sql: r#"ALTER PLUGIN "abc" 0.1.0 SET "svc1"."key1" = 'a', "svc1"."key2" = 'b'"#, + arena_type: ArenaType::Arena136, + expected: PluginOwned::ChangeConfig(ChangeConfig { + plugin_name: SmolStr::from("abc"), + version: SmolStr::from("0.1.0"), + key_value_grouped: vec![ServiceSettings { + name: SmolStr::from("svc1"), + pairs: vec![ + SettingsPair { + key: SmolStr::from("key1"), + value: SmolStr::from("a"), + }, + SettingsPair { + key: SmolStr::from("key2"), + value: SmolStr::from("b"), + }, + ], + }], + timeout: Decimal::from(10), + }), + }, + ]; + + for tc in test_cases { + let metadata = &RouterConfigurationMock::new(); + let plan: Plan = AbstractSyntaxTree::transform_into_plan(tc.sql, metadata).unwrap(); + let node = plan + .get_plugin_node(NodeId { + offset: 0, + arena_type: tc.arena_type, + }) + .unwrap() + .get_plugin_owned(); + let node = if let PluginOwned::ChangeConfig(ChangeConfig { + plugin_name, + version, + mut key_value_grouped, + timeout, + }) = node + { + key_value_grouped.sort(); + PluginOwned::ChangeConfig(ChangeConfig { + plugin_name, + version, + key_value_grouped, + timeout, + }) + } else { + node + }; + + assert_eq!(node, tc.expected, "from sql: `{}`", tc.sql); + } + } +} diff --git a/sbroad-core/src/ir/tree/expression.rs b/sbroad-core/src/ir/tree/expression.rs index 5d708111b..467efe2fa 100644 --- a/sbroad-core/src/ir/tree/expression.rs +++ b/sbroad-core/src/ir/tree/expression.rs @@ -172,6 +172,7 @@ fn expression_next<'nodes>(iter: &mut impl ExpressionTreeIterator<'nodes>) -> Op | Node::Ddl(_) | Node::Relational(_) | Node::Invalid(_) + | Node::Plugin(_) | Node::Parameter(_) => None, } } diff --git a/sbroad-core/src/ir/tree/relation.rs b/sbroad-core/src/ir/tree/relation.rs index 6399b3097..fe25f5824 100644 --- a/sbroad-core/src/ir/tree/relation.rs +++ b/sbroad-core/src/ir/tree/relation.rs @@ -115,7 +115,8 @@ fn relational_next<'nodes>(iter: &mut impl RelationalTreeIterator<'nodes>) -> Op | Node::Invalid(_) | Node::Ddl(_) | Node::Acl(_) - | Node::Block(_), + | Node::Block(_) + | Node::Plugin(_), ) | None => None, } diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs index 0db2a6332..2a505a2c7 100644 --- a/sbroad-core/src/ir/tree/subtree.rs +++ b/sbroad-core/src/ir/tree/subtree.rs @@ -205,7 +205,8 @@ fn subtree_next<'plan>( | Node::Parameter(..) | Node::Ddl(..) | Node::Acl(..) - | Node::Block(..) => None, + | Node::Block(..) + | Node::Plugin(..) => None, Node::Expression(expr) => match expr { Expression::Alias { .. } | Expression::ExprInParentheses { .. } -- GitLab