diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index c651b41e49925759420e90997c2d7b1d1585bc3a..388dfdd1d0552c58a17ab4a25b0c68628583ebcf 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 dc30c0c4f00102e00013609be2cd9a10d1de5ed0..c59f7c0462af992d8ccaa2df0283845c6b3742d9 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 0000000000000000000000000000000000000000..0d1a7fa573ead40c8e21d40018d1ec9c85fe5bcf --- /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 cb9ccca14e9ad70b5d7b6c423b38c4757f6b967f..cd5533a80a9fbb75ec7c9c9bacff2de0c145c87c 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 99c5de8dd31584064f79aeec2463f4f085bb0ae4..3caf1ec09c8564e3639f8e49eb56608d63bc153d 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 8d108a8b5352ac0e5d314cb4264d7f38c5e63b0f..e40dece2c846d5ffc614280d2fbe36ea17c30593 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 50834bfedfa2fa456734bbe2dc1ca0302bf4e296..7ab5fd941047ab1890cdca781912c20761cd7bae 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 06830993442e50b6a2e7488c1c1941785a7f0cd2..f61112f5ef0834688dc4c28df99326e385e88eb4 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 be8d4e140bbf04003ab149dedbb41647dfad1557..29ecb0c77c9e198a9b5663cf4f4e08d6516d10b7 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 1bc1723e607ca3ff6ab77900171dcb3f036158b8..51fa2069fa978394a7fc465f37638e75e58d4a9c 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.