From 6b0a5027e14d3bedee2164263bd3e4bfca03d1f7 Mon Sep 17 00:00:00 2001 From: Denis Smirnov <sd@picodata.io> Date: Mon, 5 Feb 2024 18:51:16 +0700 Subject: [PATCH] feat: implement procedure call --- doc/sql/query.ebnf | 3 +- sbroad-cartridge/src/api/exec_query.rs | 3 + .../test_app/test/integration/block_test.lua | 31 ++++++++++ sbroad-core/src/executor.rs | 6 +- sbroad-core/src/frontend/sql.rs | 59 +++++++++++++++++-- sbroad-core/src/frontend/sql/ast.rs | 10 +++- sbroad-core/src/frontend/sql/query.pest | 11 ++-- sbroad-core/src/ir/api/parameter.rs | 27 ++++++++- sbroad-core/src/ir/block.rs | 3 +- sbroad-core/src/ir/relation.rs | 43 ++++++++++++++ 10 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 sbroad-cartridge/test_app/test/integration/block_test.lua diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index c651b41e49..388dfdd1d0 100644 --- a/doc/sql/query.ebnf +++ b/doc/sql/query.ebnf @@ -1,9 +1,10 @@ -statement ::= explain | ddl | dml | dql | acl +statement ::= explain | ddl | dml | dql | acl | call explain ::= 'EXPLAIN' ( dml | dql ) dql ::= (select | select union all select | select (except 'DISTINCT'? ) select ) options? dml ::= (delete | insert | update) options? ddl ::= create_table | drop_table | create_procedure | drop_procedure acl ::= drop_role | drop_user | create_role | create_user | alter_user | grant_privilege | revoke_privilege +call ::= 'CALL' procedure '(' value ( ',' value )* ')' options? revoke_privilege ::= 'REVOKE' privilege 'FROM' (role | user) ('OPTION' '(' ('TIMEOUT' '=' double)')')? grant_privilege ::= 'GRANT' privilege 'TO' (role | user) ('OPTION' '(' ('TIMEOUT' '=' double)')')? privilege ::= (('CREATE' | 'ALTER' | 'DROP') 'USER') diff --git a/sbroad-cartridge/src/api/exec_query.rs b/sbroad-cartridge/src/api/exec_query.rs index dc30c0c4f0..c59f7c0462 100644 --- a/sbroad-cartridge/src/api/exec_query.rs +++ b/sbroad-cartridge/src/api/exec_query.rs @@ -51,6 +51,9 @@ pub extern "C" fn dispatch_query(f_ctx: FunctionCtx, args: FunctionArgs) -> c_in if let Ok(true) = query.is_acl() { return tarantool_error("ACL queries are not supported"); } + if let Ok(true) = query.is_block() { + return tarantool_error("Blocks of commands are not supported"); + } match query.dispatch() { Ok(result) => child_span("\"tarantool.tuple.return\"", || { diff --git a/sbroad-cartridge/test_app/test/integration/block_test.lua b/sbroad-cartridge/test_app/test/integration/block_test.lua new file mode 100644 index 0000000000..0d1a7fa573 --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/block_test.lua @@ -0,0 +1,31 @@ +local t = require('luatest') +local g = t.group('sbroad_with_block') + +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_call_proc = function() + local api = cluster:server("api-1").net_box + + local _, err = api:call( + "sbroad.execute", + { [[ call proc() ]], {} } + ) + t.assert_equals( + string.format("%s", err), + [[Sbroad Error: Blocks of commands are not supported]] + ) +end diff --git a/sbroad-core/src/executor.rs b/sbroad-core/src/executor.rs index cb9ccca14e..cd5533a80a 100644 --- a/sbroad-core/src/executor.rs +++ b/sbroad-core/src/executor.rs @@ -141,11 +141,13 @@ where } plan.version_map = table_version_map; } - if !plan.is_ddl()? && !plan.is_acl()? && !plan.is_block()? { + if !plan.is_ddl()? && !plan.is_acl()? { cache.put(key, plan.clone())?; } } - if !plan.is_ddl()? && !plan.is_acl()? && !plan.is_block()? { + if plan.is_block()? { + plan.bind_params(params)?; + } else if !plan.is_ddl()? && !plan.is_acl()? { plan.bind_params(params)?; plan.apply_options()?; plan.optimize()?; diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 99c5de8dd3..3caf1ec09c 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -25,13 +25,14 @@ use crate::ir::operator::{Arithmetic, Bool, ConflictStrategy, JoinKind, Relation use crate::ir::relation::{Column, ColumnRole, Type as RelationType}; use crate::ir::tree::traversal::PostOrder; use crate::ir::value::Value; -use crate::ir::{Node, OptionKind, OptionSpec, Plan}; +use crate::ir::{Node, NodeId, OptionKind, OptionSpec, Plan}; use crate::otm::child_span; use crate::errors::Entity::AST; use crate::ir::acl::AlterOption; use crate::ir::acl::{Acl, GrantRevokeType, Privilege}; use crate::ir::aggregates::AggregateKind; +use crate::ir::block::Block; use crate::ir::helpers::RepeatableState; use crate::ir::transformation::redistribution::ColumnPosition; use sbroad_proc::otm_child_span; @@ -158,6 +159,43 @@ fn parse_proc_params( Ok(params) } +fn parse_call_proc( + ast: &AbstractSyntaxTree, + node: &ParseNode, + map: &Translation, +) -> Result<Block, SbroadError> { + if node.rule != Type::CallProc { + return Err(SbroadError::Invalid( + Entity::Type, + Some("call procedure".into()), + )); + } + let mut proc_name: String = String::new(); + let mut values: Vec<NodeId> = Vec::new(); + for child_id in &node.children { + let child_node = ast.nodes.get_node(*child_id)?; + match child_node.rule { + Type::ProcName => { + proc_name = normalize_name_for_space_api(parse_string_value_node(ast, *child_id)?); + } + Type::ProcValues => { + let values_node = ast.nodes.get_node(*child_id)?; + values.reserve(values_node.children.len()); + for value_id in &values_node.children { + let plan_value_id = map.get(*value_id)?; + values.push(plan_value_id); + } + } + _ => panic!("Unexpected node: {child_node:?}"), + } + } + let call_proc = Block::Procedure { + name: proc_name, + values, + }; + Ok(call_proc) +} + fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> { if node.rule != Type::CreateProc { return Err(SbroadError::Invalid( @@ -173,7 +211,7 @@ fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, for child_id in &node.children { let child_node = ast.nodes.get_node(*child_id)?; match child_node.rule { - Type::NewProc => { + Type::ProcName => { proc_name = normalize_name_for_space_api(parse_string_value_node(ast, *child_id)?); } Type::ProcParams => { @@ -2143,6 +2181,19 @@ impl Ast for AbstractSyntaxTree { map.get(*node.children.first().expect("no children for Query rule"))?; map.add(id, child_id); } + Type::Block => { + // Query may have two children: + // 1. call + // 2. Option child - for which no plan node is created + let child_id = + map.get(*node.children.first().expect("no children for Block rule"))?; + map.add(id, child_id); + } + Type::CallProc => { + let call_proc = parse_call_proc(self, node, &map)?; + let plan_id = plan.nodes.push(Node::Block(call_proc)); + map.add(id, plan_id); + } Type::CreateProc => { let create_proc = parse_create_proc(self, node)?; let plan_id = plan.nodes.push(Node::Ddl(create_proc)); @@ -2421,7 +2472,6 @@ impl Ast for AbstractSyntaxTree { | Type::Md5 | Type::Memtx | Type::Multiply - | Type::NewProc | Type::NewTable | Type::NotEq | Type::Name @@ -2449,8 +2499,9 @@ impl Ast for AbstractSyntaxTree { | Type::ProcBody | Type::ProcParamDef | Type::ProcParams - | Type::ProcLanguage | Type::ProcName + | Type::ProcValues + | Type::ProcLanguage | Type::ProcWithOptionalParams | Type::ScanName | Type::Select diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs index 8d108a8b53..e40dece2c8 100644 --- a/sbroad-core/src/frontend/sql/ast.rs +++ b/sbroad-core/src/frontend/sql/ast.rs @@ -40,9 +40,11 @@ pub enum Type { Asterisk, AuthMethod, Between, + Block, Cast, ChapSha1, Cmp, + CallProc, CreateProc, CreateTable, Column, @@ -103,7 +105,6 @@ pub enum Type { Multiplication, Multiply, Name, - NewProc, NewTable, Not, NotEq, @@ -142,6 +143,7 @@ pub enum Type { ProcParams, ProcParamDef, ProcWithOptionalParams, + ProcValues, Projection, Reference, RevokePrivilege, @@ -206,9 +208,11 @@ impl Type { Rule::Asterisk => Ok(Type::Asterisk), Rule::AuthMethod => Ok(Type::AuthMethod), Rule::Between => Ok(Type::Between), + Rule::Block => Ok(Type::Block), Rule::Cast => Ok(Type::Cast), Rule::ChapSha1 => Ok(Type::ChapSha1), Rule::Cmp => Ok(Type::Cmp), + Rule::CallProc => Ok(Type::CallProc), Rule::CreateProc => Ok(Type::CreateProc), Rule::CreateRole => Ok(Type::CreateRole), Rule::CreateTable => Ok(Type::CreateTable), @@ -269,7 +273,6 @@ impl Type { Rule::Multiplication => Ok(Type::Multiplication), Rule::Multiply => Ok(Type::Multiply), Rule::Name => Ok(Type::Name), - Rule::NewProc => Ok(Type::NewProc), Rule::NewTable => Ok(Type::NewTable), Rule::Not => Ok(Type::Not), Rule::NotEq => Ok(Type::NotEq), @@ -304,10 +307,11 @@ impl Type { Rule::PrivilegeWrite => Ok(Type::PrivilegeWrite), Rule::ProcBody => Ok(Type::ProcBody), Rule::ProcLanguage => Ok(Type::ProcLanguage), - Rule::ProcName => Ok(Type::ProcName), Rule::ProcParams => Ok(Type::ProcParams), Rule::ProcParamDef => Ok(Type::ProcParamDef), Rule::ProcWithOptionalParams => Ok(Type::ProcWithOptionalParams), + Rule::ProcName => Ok(Type::ProcName), + Rule::ProcValues => Ok(Type::ProcValues), Rule::Projection => Ok(Type::Projection), Rule::Reference => Ok(Type::Reference), Rule::RevokePrivilege => Ok(Type::RevokePrivilege), diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 50834bfedf..7ab5fd9410 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 | DDL | ACL) ~ EOF } +Command = _{ SOI ~ (Query | ExplainQuery | Block | DDL | ACL) ~ EOF } ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivilege | RevokePrivilege } /// Represents both RoleName and UserName. @@ -78,11 +78,11 @@ DDL = _{ CreateTable | DropTable | CreateProc | DropProc } DeletedTable = @{ Table } CreateProc = { - ^"create" ~ ^"procedure" ~ NewProc ~ + ^"create" ~ ^"procedure" ~ ProcName ~ "(" ~ ProcParams? ~ ")" ~ (^"language" ~ ProcLanguage)? ~ ((^"as" ~ "$$" ~ ProcBody ~ "$$") | (^"begin" ~ "atomic" ~ ProcBody ~ "end")) } - NewProc = @{ Name } + ProcName = @{ Name } ProcParams = { ProcParamDef ~ ("," ~ ProcParamDef)* } ProcParamDef = { Type } ProcLanguage = { SQL } @@ -91,7 +91,10 @@ DDL = _{ CreateTable | DropTable | CreateProc | DropProc } DropProc = { ^"drop" ~ ^"procedure" ~ ProcWithOptionalParams ~ Option? } ProcWithOptionalParams = { ProcName ~ ("(" ~ ProcParams ~ ")")? } - ProcName = @{ Name } + +Block = { CallProc ~ Option? } + CallProc = { ^"call" ~ ProcName ~ "(" ~ ProcValues ~ ")" } + ProcValues = { (Value | Parameter)? ~ ("," ~ (Value | Parameter))* } ExplainQuery = _{ Explain } Explain = { ^"explain" ~ Query } diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs index 0683099344..f61112f5ef 100644 --- a/sbroad-core/src/ir/api/parameter.rs +++ b/sbroad-core/src/ir/api/parameter.rs @@ -1,4 +1,5 @@ use crate::errors::{Entity, SbroadError}; +use crate::ir::block::Block; use crate::ir::expression::Expression; use crate::ir::operator::Relational; use crate::ir::tree::traversal::PostOrder; @@ -247,7 +248,18 @@ impl Plan { | Expression::Constant { .. } | Expression::CountAsterisk => {} }, - Node::Parameter | Node::Ddl(..) | Node::Acl(..) | Node::Block(..) => {} + Node::Block(block) => match block { + Block::Procedure { ref values, .. } => { + for param_id in values { + if param_node_ids.take(param_id).is_some() { + // We don't need to wrap arguments, passed into the + // procedure call, into the rows. + idx = idx.saturating_sub(1); + } + } + } + }, + Node::Parameter | Node::Ddl(..) | Node::Acl(..) => {} } } @@ -360,7 +372,18 @@ impl Plan { | Expression::Constant { .. } | Expression::CountAsterisk => {} }, - Node::Parameter | Node::Ddl(..) | Node::Acl(..) | Node::Block(..) => {} + Node::Block(block) => match block { + Block::Procedure { ref mut values, .. } => { + for param_id in values { + if param_node_ids_cloned.take(param_id).is_some() { + idx = idx.saturating_sub(1); + let val_id = get_value(*param_id, idx)?; + *param_id = val_id; + } + } + } + }, + Node::Parameter | Node::Ddl(..) | Node::Acl(..) => {} } } diff --git a/sbroad-core/src/ir/block.rs b/sbroad-core/src/ir/block.rs index be8d4e140b..29ecb0c77c 100644 --- a/sbroad-core/src/ir/block.rs +++ b/sbroad-core/src/ir/block.rs @@ -1,6 +1,7 @@ +//! IR nodes representing blocks of commands. + use crate::errors::{Entity, SbroadError}; use crate::ir::{Node, NodeId, Plan}; - use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/sbroad-core/src/ir/relation.rs b/sbroad-core/src/ir/relation.rs index 1bc1723e60..51fa2069fa 100644 --- a/sbroad-core/src/ir/relation.rs +++ b/sbroad-core/src/ir/relation.rs @@ -91,6 +91,33 @@ impl From<&Type> for SpaceFieldType { } } +impl TryFrom<SpaceFieldType> for Type { + type Error = SbroadError; + + fn try_from(field_type: SpaceFieldType) -> Result<Self, Self::Error> { + match field_type { + SpaceFieldType::Boolean => Ok(Type::Boolean), + SpaceFieldType::Decimal => Ok(Type::Decimal), + SpaceFieldType::Double => Ok(Type::Double), + SpaceFieldType::Integer => Ok(Type::Integer), + SpaceFieldType::Number => Ok(Type::Number), + SpaceFieldType::Scalar => Ok(Type::Scalar), + SpaceFieldType::String => Ok(Type::String), + SpaceFieldType::Unsigned => Ok(Type::Unsigned), + SpaceFieldType::Array => Ok(Type::Array), + SpaceFieldType::Any + | SpaceFieldType::Varbinary + | SpaceFieldType::Uuid + | SpaceFieldType::Map + | SpaceFieldType::Interval + | SpaceFieldType::Datetime => Err(SbroadError::NotImplemented( + Entity::Type, + field_type.to_string(), + )), + } + } +} + impl Type { /// Type constructor /// @@ -141,6 +168,22 @@ impl Type { | Type::Unsigned ) } + + /// Check if the type can be casted to another type. + #[must_use] + pub fn is_castable_to(&self, to: &Type) -> bool { + matches!( + (self, to), + (Type::Array, Type::Array) + | (Type::Boolean, Type::Boolean) + | ( + Type::Double | Type::Integer | Type::Unsigned | Type::Decimal | Type::Number, + Type::Double | Type::Integer | Type::Unsigned | Type::Decimal | Type::Number, + ) + | (Type::Scalar, Type::Scalar) + | (Type::String, Type::String) + ) + } } /// A role of the column in the relation. -- GitLab