From 6b972a01ec914f8e3994333a544bb4dc13785ea8 Mon Sep 17 00:00:00 2001
From: Kaitmazian Maksim <m.kaitmazian@picodata.io>
Date: Fri, 2 Feb 2024 19:09:21 +0300
Subject: [PATCH] feat: implement SQL support for drop procedure

---
 doc/sql/query.ebnf                      |   3 +-
 sbroad-core/src/frontend/sql.rs         | 137 +++++++++++++++++++-----
 sbroad-core/src/frontend/sql/ast.rs     |   6 ++
 sbroad-core/src/frontend/sql/query.pest |   6 +-
 sbroad-core/src/ir/ddl.rs               |   8 +-
 5 files changed, 128 insertions(+), 32 deletions(-)

diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 7456d29ff6..56122ea580 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -2,7 +2,7 @@ statement   ::= explain | ddl | dml | dql | acl
 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
+ddl         ::= create_table | drop_table | create_procedure | drop_procedure
 acl         ::= drop_role | drop_user | create_role | create_user | alter_user | grant_privilege | revoke_privilege
 revoke_privilege      ::= 'REVOKE' privilege 'FROM' (role | user) ('OPTION' '(' ('TIMEOUT' '=' double)')')?
 grant_privilege       ::= 'GRANT' privilege 'TO' (role | user) ('OPTION' '(' ('TIMEOUT' '=' double)')')?
@@ -23,6 +23,7 @@ alter_user   ::= 'ALTER USER' user 'WITH'? ('LOGIN' | 'NOLOGIN' | 'PASSWORD' "'"
 column       ::= name ('BOOL' | 'DECIMAL' | 'DOUBLE' | 'INT' | 'NUMBER' | 'SCALAR' | 'STRING' | 'TEXT' | 'UNSIGNED' | 'VARCHAR') (('NOT'?)  'NULL')?
 primary_key  ::= 'PRIMARY KEY' '(' name (',' name)* ')'
 distribution ::= 'GLOBAL' | ('DISTRIBUTED BY' '(' name (',' name)*  ')')
+drop_procedure ::= 'DROP PROCEDURE' procedure ('(' type (',' type)* ')')? ('OPTION' '(' ('TIMEOUT' '=' double)')')?
 drop_table   ::= 'DROP TABLE' table ('OPTION' '(' ('TIMEOUT' '=' double)')')?
 drop_role    ::= 'DROP ROLE' role ('OPTION' '(' ('TIMEOUT' '=' double)')')?
 drop_user    ::= 'DROP USER' user ('OPTION' '(' ('TIMEOUT' '=' double)')')?
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index aa59409195..99c5de8dd3 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -117,6 +117,47 @@ fn parse_string_value_node(ast: &AbstractSyntaxTree, node_id: usize) -> Result<&
     Ok(string_value.as_str())
 }
 
+fn parse_proc_params(
+    ast: &AbstractSyntaxTree,
+    params_node: &ParseNode,
+) -> Result<Vec<ParamDef>, SbroadError> {
+    if params_node.rule != Type::ProcParams {
+        return Err(SbroadError::Invalid(
+            Entity::Type,
+            Some("proc params".into()),
+        ));
+    }
+
+    let mut params = Vec::with_capacity(params_node.children.len());
+    for param_id in &params_node.children {
+        let param_def_node = ast.nodes.get_node(*param_id)?;
+        match param_def_node.rule {
+            Type::ProcParamDef => {
+                let type_id = *param_def_node
+                    .children
+                    .first()
+                    .expect("ProcParamDef node must contain exactly one child (type node).");
+                assert!(param_def_node.children.get(1).is_none());
+                let type_node = ast.nodes.get_node(type_id)?;
+                let data_type = match type_node.rule {
+                    Type::TypeBool => RelationType::Boolean,
+                    Type::TypeDecimal => RelationType::Decimal,
+                    Type::TypeDouble => RelationType::Double,
+                    Type::TypeInt => RelationType::Integer,
+                    Type::TypeNumber => RelationType::Number,
+                    Type::TypeScalar => RelationType::Scalar,
+                    Type::TypeString | Type::TypeText | Type::TypeVarchar => RelationType::String,
+                    Type::TypeUnsigned => RelationType::Unsigned,
+                    _ => panic!("Unexpected node: {type_node:?}"),
+                };
+                params.push(ParamDef { data_type });
+            }
+            _ => panic!("Unexpected node: {param_def_node:?}"),
+        }
+    }
+    Ok(params)
+}
+
 fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
     if node.rule != Type::CreateProc {
         return Err(SbroadError::Invalid(
@@ -136,35 +177,7 @@ fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
                 proc_name = normalize_name_for_space_api(parse_string_value_node(ast, *child_id)?);
             }
             Type::ProcParams => {
-                let params_node = ast.nodes.get_node(*child_id)?;
-                params.reserve(params_node.children.len());
-                for param_id in &params_node.children {
-                    let param_def_node = ast.nodes.get_node(*param_id)?;
-                    match param_def_node.rule {
-                        Type::ProcParamDef => {
-                            let type_id = *param_def_node.children.first().expect(
-                                "ProcParamDef node must contain exactly one child (type node).",
-                            );
-                            assert!(param_def_node.children.get(1).is_none());
-                            let type_node = ast.nodes.get_node(type_id)?;
-                            let data_type = match type_node.rule {
-                                Type::TypeBool => RelationType::Boolean,
-                                Type::TypeDecimal => RelationType::Decimal,
-                                Type::TypeDouble => RelationType::Double,
-                                Type::TypeInt => RelationType::Integer,
-                                Type::TypeNumber => RelationType::Number,
-                                Type::TypeScalar => RelationType::Scalar,
-                                Type::TypeString | Type::TypeText | Type::TypeVarchar => {
-                                    RelationType::String
-                                }
-                                Type::TypeUnsigned => RelationType::Unsigned,
-                                _ => panic!("Unexpected node: {type_node:?}"),
-                            };
-                            params.push(ParamDef { data_type });
-                        }
-                        _ => panic!("Unexpected node: {param_def_node:?}"),
-                    }
-                }
+                params = parse_proc_params(ast, child_node)?;
             }
             Type::ProcLanguage => {
                 // We don't need to parse language node, because we support only SQL.
@@ -192,6 +205,65 @@ fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
     Ok(create_proc)
 }
 
+fn parse_proc_with_optional_params(
+    ast: &AbstractSyntaxTree,
+    node: &ParseNode,
+) -> Result<(String, Vec<ParamDef>), SbroadError> {
+    if node.rule != Type::ProcWithOptionalParams {
+        return Err(SbroadError::Invalid(
+            Entity::Type,
+            Some("proc with optional params".into()),
+        ));
+    }
+
+    let mut name = String::new();
+    let mut params = Vec::new();
+    for child_id in &node.children {
+        let child_node = ast.nodes.get_node(*child_id)?;
+        match child_node.rule {
+            Type::ProcName => {
+                name = normalize_name_for_space_api(parse_string_value_node(ast, *child_id)?);
+            }
+            Type::ProcParams => {
+                params = parse_proc_params(ast, child_node)?;
+            }
+            _ => panic!("Unexpected node: {child_node:?}"),
+        }
+    }
+
+    Ok((name, params))
+}
+
+fn parse_drop_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+    if node.rule != Type::DropProc {
+        return Err(SbroadError::Invalid(
+            Entity::Type,
+            Some("drop procedure".into()),
+        ));
+    }
+
+    let mut name: String = String::new();
+    let mut params: Vec<ParamDef> = Vec::new();
+    let mut timeout = get_default_timeout();
+    for child_id in &node.children {
+        let child_node = ast.nodes.get_node(*child_id)?;
+        match child_node.rule {
+            Type::ProcWithOptionalParams => {
+                (name, params) = parse_proc_with_optional_params(ast, child_node)?;
+            }
+            Type::Timeout => {
+                timeout = get_timeout(ast, *child_id)?;
+            }
+            _ => panic!("Unexpected node: {child_node:?}"),
+        }
+    }
+    Ok(Ddl::DropProc {
+        name,
+        params,
+        timeout,
+    })
+}
+
 #[allow(clippy::too_many_lines)]
 #[allow(clippy::uninlined_format_args)]
 fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
@@ -2152,6 +2224,11 @@ impl Ast for AbstractSyntaxTree {
                     let plan_id = plan.nodes.push(Node::Ddl(drop_table));
                     map.add(id, plan_id);
                 }
+                Type::DropProc => {
+                    let drop_proc = parse_drop_proc(self, node)?;
+                    let plan_id = plan.nodes.push(Node::Ddl(drop_proc));
+                    map.add(id, plan_id);
+                }
                 Type::AlterUser => {
                     let user_name_node_id = node
                         .children
@@ -2373,6 +2450,8 @@ impl Ast for AbstractSyntaxTree {
                 | Type::ProcParamDef
                 | Type::ProcParams
                 | Type::ProcLanguage
+                | Type::ProcName
+                | Type::ProcWithOptionalParams
                 | Type::ScanName
                 | Type::Select
                 | Type::Sharding
diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs
index bc29122de5..8d108a8b53 100644
--- a/sbroad-core/src/frontend/sql/ast.rs
+++ b/sbroad-core/src/frontend/sql/ast.rs
@@ -65,6 +65,7 @@ pub enum Type {
     Distribution,
     Divide,
     Double,
+    DropProc,
     DropRole,
     DropTable,
     DropUser,
@@ -137,8 +138,10 @@ pub enum Type {
     PrivilegeWrite,
     ProcBody,
     ProcLanguage,
+    ProcName,
     ProcParams,
     ProcParamDef,
+    ProcWithOptionalParams,
     Projection,
     Reference,
     RevokePrivilege,
@@ -226,6 +229,7 @@ impl Type {
             Rule::DeletedTable => Ok(Type::DeletedTable),
             Rule::Divide => Ok(Type::Divide),
             Rule::Double => Ok(Type::Double),
+            Rule::DropProc => Ok(Type::DropProc),
             Rule::DropRole => Ok(Type::DropRole),
             Rule::DropTable => Ok(Type::DropTable),
             Rule::DropUser => Ok(Type::DropUser),
@@ -300,8 +304,10 @@ 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::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 955cfbff72..be7a257cdd 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -47,7 +47,7 @@ ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivil
             PrivilegeUsage = { ^"usage" }
             PrivilegeWrite = { ^"write" }
 
-DDL = _{ CreateTable | DropTable | CreateProc }
+DDL = _{ CreateTable | DropTable | CreateProc | DropProc }
     CreateTable = {
         ^"create" ~ ^"table" ~ NewTable ~
         "(" ~ Columns ~ "," ~ PrimaryKey ~ ")" ~
@@ -89,6 +89,10 @@ DDL = _{ CreateTable | DropTable | CreateProc }
         SQL = { ^"sql" }
         ProcBody = { (Insert | Update | Delete) }
 
+    DropProc = { ^"drop" ~ ^"procedure" ~ ProcWithOptionalParams ~ Option? }
+        ProcWithOptionalParams = { ProcName ~ ("(" ~ ProcParams ~ ")")? }
+        ProcName = @{ Name }
+
 ExplainQuery = _{ Explain }
     Explain = { ^"explain" ~ Query }
 
diff --git a/sbroad-core/src/ir/ddl.rs b/sbroad-core/src/ir/ddl.rs
index a41ef4ce45..18081fd16f 100644
--- a/sbroad-core/src/ir/ddl.rs
+++ b/sbroad-core/src/ir/ddl.rs
@@ -57,6 +57,11 @@ pub enum Ddl {
         language: Language,
         timeout: Decimal,
     },
+    DropProc {
+        name: String,
+        params: Vec<ParamDef>,
+        timeout: Decimal,
+    },
 }
 
 impl Ddl {
@@ -68,7 +73,8 @@ impl Ddl {
         match self {
             Ddl::CreateTable { ref timeout, .. }
             | Ddl::DropTable { ref timeout, .. }
-            | Ddl::CreateProc { ref timeout, .. } => timeout,
+            | Ddl::CreateProc { ref timeout, .. }
+            | Ddl::DropProc { ref timeout, .. } => timeout,
         }
         .to_string()
         .parse()
-- 
GitLab