From 79d1d796d0d94e688c4c2c04b2d89c4a285e06f8 Mon Sep 17 00:00:00 2001
From: Kaitmazian Maksim <m.kaitmazian@picodata.io>
Date: Thu, 12 Sep 2024 13:00:35 +0300
Subject: [PATCH] feat: introduce IF EXISTS and IF NOT EXISTS options

---
 doc/sql/query.ebnf                      |  20 +-
 sbroad-core/src/frontend/sql.rs         | 287 +++++++++++++++---------
 sbroad-core/src/frontend/sql/query.pest |  25 ++-
 sbroad-core/src/ir.rs                   |  42 ++--
 sbroad-core/src/ir/node.rs              |  40 ++--
 sbroad-core/src/ir/node/plugin.rs       |  10 +-
 sbroad-core/src/ir/node/tests.rs        |   4 +-
 sbroad-core/src/ir/operator.rs          |   2 +-
 8 files changed, 253 insertions(+), 177 deletions(-)

diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 55b6f08019..771825a30c 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -129,7 +129,7 @@ alter_system ::= 'ALTER' 'SYSTEM'
                   ('OPTION' '(' ('TIMEOUT' '=' double)')')?
 alter_procedure ::= 'ALTER' 'PROCEDURE' procedure ('(' type (',' type)* ')')?
                      'RENAME' 'TO' procedure ('OPTION' '(' ('TIMEOUT' '=' double)')')?
-create_index ::= 'CREATE' 'UNIQUE'? 'INDEX' index 'ON' table
+create_index ::= 'CREATE' 'UNIQUE'? 'INDEX' ('IF' 'NOT' 'EXISTS')? index 'ON' table
                  ('USING' ('TREE' | 'HASH' | 'RTREE' | 'BITSET'))?
                  '(' column (',' column)* ')' ('WITH' '('
                      (
@@ -156,13 +156,13 @@ create_index ::= 'CREATE' 'UNIQUE'? 'INDEX' index 'ON' table
                          )
                      )*
                  ')')? ('OPTION' '(' ('TIMEOUT' '=' double)')')?
-create_procedure ::= 'CREATE' 'PROCEDURE' procedure '(' type (',' type)* ')'
+create_procedure ::= 'CREATE' 'PROCEDURE' ('IF' 'NOT' 'EXISTS')? 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
+create_role    ::= 'CREATE' 'ROLE' ('IF' 'NOT' 'EXISTS')? role
+create_table   ::= 'CREATE' 'TABLE' ('IF' 'NOT' 'EXISTS')? table
                    '('
                        column type ('NOT'? 'NULL')? ('PRIMARY' 'KEY')? (',' column type ('NOT'? 'NULL')? ('PRIMARY' 'KEY')?)*
                        (',' 'PRIMARY' 'KEY' '(' column (',' column)* ')')?
@@ -170,7 +170,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 "'"
+create_user    ::= 'CREATE' 'USER' ('IF' 'NOT' 'EXISTS')? user 'WITH'? 'PASSWORD' "'" password "'"
                    ('USING' ('CHAP-SHA1' | 'LDAP' | 'MD5'))?
 alter_user     ::= 'ALTER' 'USER' user
                    'WITH'? (
@@ -179,11 +179,11 @@ alter_user     ::= 'ALTER' 'USER' user
                        | 'PASSWORD' "'" password "'" ('USING' ('CHAP-SHA1' | 'LDAP' | 'MD5'))?
                        | 'RENAME' 'TO' user
                    )
-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
+drop_index     ::= 'DROP' 'INDEX' ('IF' 'EXISTS')? index ('OPTION' '(' ('TIMEOUT' '=' double)')')?
+drop_procedure ::= 'DROP' 'PROCEDURE' ('IF' 'EXISTS')? procedure ('(' type (',' type)* ')')? ('OPTION' '(' ('TIMEOUT' '=' double)')')?
+drop_table     ::= 'DROP' 'TABLE' ('IF' 'EXISTS')? table ('OPTION' '(' ('TIMEOUT' '=' double)')')?
+drop_role      ::= 'DROP' 'ROLE' ('IF' 'EXISTS')? role
+drop_user      ::= 'DROP' 'USER' ('IF' 'EXISTS')? user
 create_plugin  ::= 'CREATE' 'PLUGIN' ('IF' 'NOT' 'EXISTS')? plugin version
                     ('OPTION' '(' ('TIMEOUT' '=' double)')')?
 drop_plugin    ::= 'DROP' 'PLUGIN' ('IF' 'EXISTS')? plugin version ('WITH' 'DATA')?
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index 15d6ccd9ee..051a0ad3e5 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -72,6 +72,9 @@ use tarantool::space::SpaceEngineType;
 const DEFAULT_TIMEOUT_F64: f64 = 24.0 * 60.0 * 60.0;
 const DEFAULT_AUTH_METHOD: &str = "md5";
 
+const DEFAULT_IF_EXISTS: bool = false;
+const DEFAULT_IF_NOT_EXISTS: bool = false;
+
 fn get_default_timeout() -> Decimal {
     Decimal::from_str(&format!("{DEFAULT_TIMEOUT_F64}")).expect("default timeout casting failed")
 }
@@ -259,19 +262,20 @@ fn parse_create_proc(
     ast: &AbstractSyntaxTree,
     node: &ParseNode,
 ) -> Result<CreateProc, SbroadError> {
-    let proc_name_id = node.children.first().expect("Expected to get Proc name");
-    let proc_name = parse_identifier(ast, *proc_name_id)?;
-
-    let proc_params_id = node.children.get(1).expect("Expedcted to get Proc params");
-    let proc_params = ast.nodes.get_node(*proc_params_id)?;
-    let params = parse_proc_params(ast, proc_params)?;
-
+    let mut name = None;
+    let mut params = None;
     let language = Language::SQL;
     let mut body = SmolStr::default();
+    let mut if_not_exists = DEFAULT_IF_NOT_EXISTS;
     let mut timeout = get_default_timeout();
-    for child_id in node.children.iter().skip(2) {
+    for child_id in &node.children {
         let child_node = ast.nodes.get_node(*child_id)?;
         match child_node.rule {
+            Rule::Identifier => name = Some(parse_identifier(ast, *child_id)?),
+            Rule::ProcParams => {
+                let proc_params = ast.nodes.get_node(*child_id)?;
+                params = Some(parse_proc_params(ast, proc_params)?);
+            }
             Rule::ProcLanguage => {
                 // We don't need to parse language node, because we support only SQL.
             }
@@ -282,17 +286,21 @@ fn parse_create_proc(
                     .expect("procedure body must not be empty")
                     .clone();
             }
+            Rule::IfNotExists => if_not_exists = true,
             Rule::Timeout => {
                 timeout = get_timeout(ast, *child_id)?;
             }
             _ => unreachable!("Unexpected node: {child_node:?}"),
         }
     }
+    let name = name.expect("name expected as a child");
+    let params = params.expect("params expected as a child");
     let create_proc = CreateProc {
-        name: proc_name,
+        name,
         params,
         language,
         body,
+        if_not_exists,
         timeout,
     };
     Ok(create_proc)
@@ -405,22 +413,29 @@ fn parse_proc_with_optional_params(
 }
 
 fn parse_drop_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<DropProc, SbroadError> {
-    let proc_with_optional_params_id = node
-        .children
-        .first()
-        .expect("Expected to see ProcWithOptionalParams");
-    let proc_with_optional_params = ast.nodes.get_node(*proc_with_optional_params_id)?;
-    let (name, params) = parse_proc_with_optional_params(ast, proc_with_optional_params)?;
-
-    let timeout = if let Some(timeout_id) = node.children.get(1) {
-        get_timeout(ast, *timeout_id)?
-    } else {
-        get_default_timeout()
-    };
+    let mut name = None;
+    let mut params = None;
+    let mut timeout = get_default_timeout();
+    let mut if_exists = DEFAULT_IF_EXISTS;
+    for child_id in &node.children {
+        let child_node = ast.nodes.get_node(*child_id)?;
+        match child_node.rule {
+            Rule::ProcWithOptionalParams => {
+                let (proc_name, proc_params) = parse_proc_with_optional_params(ast, child_node)?;
+                name = Some(proc_name);
+                params = proc_params;
+            }
+            Rule::TimeoutOption => timeout = get_timeout(ast, *child_id)?,
+            Rule::IfExists => if_exists = true,
+            child_node => panic!("unexpected node {child_node:?}"),
+        }
+    }
 
+    let name = name.expect("drop proc wihtout proc name");
     Ok(DropProc {
         name,
         params,
+        if_exists,
         timeout,
     })
 }
@@ -444,6 +459,7 @@ fn parse_create_index(
     let mut dimension = None;
     let mut distance = None;
     let mut hint = None;
+    let mut if_not_exists = DEFAULT_IF_NOT_EXISTS;
     let mut timeout = get_default_timeout();
 
     let first_child = |node: &ParseNode| -> &ParseNode {
@@ -483,6 +499,7 @@ fn parse_create_index(
         let child_node = ast.nodes.get_node(*child_id)?;
         match child_node.rule {
             Rule::Unique => unique = true,
+            Rule::IfNotExists => if_not_exists = true,
             Rule::Identifier => name = parse_identifier(ast, *child_id)?,
             Rule::Table => table_name = parse_identifier(ast, *child_id)?,
             Rule::IndexType => {
@@ -550,6 +567,7 @@ fn parse_create_index(
         table_name,
         columns,
         unique,
+        if_not_exists,
         index_type,
         bloom_fpr,
         page_size,
@@ -568,15 +586,21 @@ fn parse_drop_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<DropIn
     assert_eq!(node.rule, Rule::DropIndex);
     let mut name = SmolStr::default();
     let mut timeout = get_default_timeout();
+    let mut if_exists = DEFAULT_IF_EXISTS;
     for child_id in &node.children {
         let child_node = ast.nodes.get_node(*child_id)?;
         match child_node.rule {
             Rule::Identifier => name = parse_identifier(ast, *child_id)?,
             Rule::Timeout => timeout = get_timeout(ast, *child_id)?,
+            Rule::IfExists => if_exists = true,
             _ => panic!("Unexpected drop index node: {child_node:?}"),
         }
     }
-    Ok(DropIndex { name, timeout })
+    Ok(DropIndex {
+        name,
+        if_exists,
+        timeout,
+    })
 }
 
 #[allow(clippy::too_many_lines)]
@@ -600,6 +624,7 @@ fn parse_create_table(
     let mut timeout = get_default_timeout();
     let mut tier = None;
     let mut is_global = false;
+    let mut if_not_exists = DEFAULT_IF_NOT_EXISTS;
 
     let nullable_primary_key_column_error = Err(SbroadError::Invalid(
         Entity::Column,
@@ -615,6 +640,7 @@ fn parse_create_table(
     for child_id in &node.children {
         let child_node = ast.nodes.get_node(*child_id)?;
         match child_node.rule {
+            Rule::IfNotExists => if_not_exists = true,
             Rule::NewTable => {
                 table_name = parse_identifier(ast, *child_id)?;
             }
@@ -858,34 +884,63 @@ fn parse_create_table(
     if shard_key.is_empty() && !is_global {
         shard_key = pk_keys.clone();
     }
-    let create_sharded_table = if shard_key.is_empty() {
+
+    let sharding_key = if !shard_key.is_empty() {
+        Some(shard_key)
+    } else {
         if engine_type != SpaceEngineType::Memtx {
             return Err(SbroadError::Unsupported(
                 Entity::Query,
                 Some("global spaces can use only memtx engine".into()),
             ));
-        }
-        CreateTable {
-            name: table_name,
-            format: columns,
-            primary_key: pk_keys,
-            sharding_key: None,
-            engine_type,
-            timeout,
-            tier,
-        }
-    } else {
-        CreateTable {
-            name: table_name,
-            format: columns,
-            primary_key: pk_keys,
-            sharding_key: Some(shard_key),
-            engine_type,
-            timeout,
-            tier,
-        }
+        };
+        None
     };
-    Ok(create_sharded_table)
+
+    Ok(CreateTable {
+        name: table_name,
+        format: columns,
+        primary_key: pk_keys,
+        sharding_key,
+        engine_type,
+        if_not_exists,
+        timeout,
+        tier,
+    })
+}
+
+fn parse_drop_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<DropTable, SbroadError> {
+    let mut table_name = SmolStr::default();
+    let mut timeout = get_default_timeout();
+    let mut if_exists = DEFAULT_IF_EXISTS;
+    for child_id in &node.children {
+        let child_node = ast.nodes.get_node(*child_id)?;
+        match child_node.rule {
+            Rule::Table => {
+                table_name = parse_identifier(ast, *child_id)?;
+            }
+            Rule::IfExists => {
+                if_exists = true;
+            }
+            Rule::Timeout => {
+                timeout = get_timeout(ast, *child_id)?;
+            }
+            _ => {
+                return Err(SbroadError::Invalid(
+                    Entity::Node,
+                    Some(format_smolstr!(
+                        "AST drop table node {:?} contains unexpected children",
+                        child_node,
+                    )),
+                ));
+            }
+        }
+    }
+    Ok(DropTable {
+        name: table_name,
+        if_exists,
+        timeout,
+    })
 }
 
 fn parse_set_param(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<SetParam, SbroadError> {
@@ -3567,6 +3622,10 @@ impl AbstractSyntaxTree {
         let mut col_idx: usize = 0;
         let mut worker = ExpressionsWorker::new(metadata, sq_pair_to_ast_ids);
         let mut ctes = CTEs::new();
+        // This flag disables resolving of table names for DROP TABLE queries,
+        // as it can be used with tables that are not presented in metadata.
+        // Unresolved table names are handled in picodata depending in IF EXISTS options.
+        let resolve_table_names = self.nodes.get_node(top)?.rule != Rule::DropTable;
 
         for level_node in dft_post.iter(top) {
             let id = level_node.1;
@@ -3611,7 +3670,7 @@ impl AbstractSyntaxTree {
                 Rule::Cte => {
                     parse_cte(self, id, &mut map, &mut ctes, &mut plan)?;
                 }
-                Rule::Table => {
+                Rule::Table if resolve_table_names => {
                     // The thing is we don't want to normalize name.
                     // Should we fix `parse_identifier` or `table` logic?
                     let table_name = parse_identifier(self, id)?;
@@ -4194,50 +4253,37 @@ impl AbstractSyntaxTree {
                     map.add(id, plan_id);
                 }
                 Rule::DropRole => {
-                    let role_name_id = node
-                        .children
-                        .first()
-                        .expect("RoleName expected under DropRole node");
-                    let role_name = parse_identifier(self, *role_name_id)?;
-
-                    let mut timeout = get_default_timeout();
-                    if let Some(timeout_child_id) = node.children.get(1) {
-                        timeout = get_timeout(self, *timeout_child_id)?;
-                    }
-                    let drop_role = DropRole {
-                        name: role_name,
-                        timeout,
-                    };
-                    let plan_id = plan.nodes.push(drop_role.into());
-                    map.add(id, plan_id);
-                }
-                Rule::DropTable => {
-                    let mut table_name = SmolStr::default();
+                    let mut name = None;
                     let mut timeout = get_default_timeout();
+                    let mut if_exists = DEFAULT_IF_EXISTS;
                     for child_id in &node.children {
                         let child_node = self.nodes.get_node(*child_id)?;
                         match child_node.rule {
-                            Rule::Table => {
-                                table_name = parse_identifier(self, *child_id)?;
-                            }
-                            Rule::Timeout => {
-                                timeout = get_timeout(self, *child_id)?;
-                            }
+                            Rule::Identifier => name = Some(parse_identifier(self, *child_id)?),
+                            Rule::IfExists => if_exists = true,
+                            Rule::Timeout => timeout = get_timeout(self, *child_id)?,
                             _ => {
                                 return Err(SbroadError::Invalid(
                                     Entity::Node,
                                     Some(format_smolstr!(
-                                        "AST drop table node {:?} contains unexpected children",
-                                        child_node,
+                                        "ACL node contains unexpected child: {child_node:?}",
                                     )),
                                 ));
                             }
                         }
                     }
-                    let drop_table = DropTable {
-                        name: table_name,
+                    let name = name.expect("RoleName expected under DropRole node");
+                    let drop_role = DropRole {
+                        name,
+                        if_exists,
                         timeout,
                     };
+
+                    let plan_id = plan.nodes.push(drop_role.into());
+                    map.add(id, plan_id);
+                }
+                Rule::DropTable => {
+                    let drop_table = parse_drop_table(self, node)?;
                     let plan_id = plan.nodes.push(drop_table.into());
                     map.add(id, plan_id);
                 }
@@ -4323,28 +4369,19 @@ impl AbstractSyntaxTree {
                     map.add(id, plan_id);
                 }
                 Rule::CreateUser => {
-                    let mut iter = node.children.iter();
-                    let user_name_node_id = iter.next().ok_or_else(|| {
-                        SbroadError::Invalid(
-                            Entity::ParseNode,
-                            Some(SmolStr::from("RoleName expected as a first child")),
-                        )
-                    })?;
-                    let user_name = parse_identifier(self, *user_name_node_id)?;
-
-                    let pwd_node_id = iter.next().ok_or_else(|| {
-                        SbroadError::Invalid(
-                            Entity::ParseNode,
-                            Some(SmolStr::from("Password expected as a second child")),
-                        )
-                    })?;
-                    let password = parse_string_literal(self, *pwd_node_id)?;
-
+                    let mut name = None;
+                    let mut password = None;
+                    let mut if_not_exists = DEFAULT_IF_NOT_EXISTS;
                     let mut timeout = get_default_timeout();
                     let mut auth_method = get_default_auth_method();
-                    for child_id in iter {
+                    for child_id in &node.children {
                         let child_node = self.nodes.get_node(*child_id)?;
                         match child_node.rule {
+                            Rule::Identifier => name = Some(parse_identifier(self, *child_id)?),
+                            Rule::SingleQuotedString => {
+                                password = Some(parse_string_literal(self, *child_id)?);
+                            }
+                            Rule::IfNotExists => if_not_exists = true,
                             Rule::Timeout => {
                                 timeout = get_timeout(self, *child_id)?;
                             }
@@ -4369,48 +4406,76 @@ impl AbstractSyntaxTree {
                         }
                     }
 
+                    let name = name.expect("username expected as a child");
+                    let password = password.expect("password expected as a child");
                     let create_user = CreateUser {
-                        name: user_name,
+                        name,
                         password,
+                        if_not_exists,
                         auth_method,
                         timeout,
                     };
+
                     let plan_id = plan.nodes.push(create_user.into());
                     map.add(id, plan_id);
                 }
                 Rule::DropUser => {
-                    let user_name_id = node
-                        .children
-                        .first()
-                        .expect("RoleName expected under DropUser node");
-                    let user_name = parse_identifier(self, *user_name_id)?;
-
+                    let mut name = None;
                     let mut timeout = get_default_timeout();
-                    if let Some(timeout_child_id) = node.children.get(1) {
-                        timeout = get_timeout(self, *timeout_child_id)?;
+                    let mut if_exists = DEFAULT_IF_EXISTS;
+                    for child_id in &node.children {
+                        let child_node = self.nodes.get_node(*child_id)?;
+                        match child_node.rule {
+                            Rule::Identifier => name = Some(parse_identifier(self, *child_id)?),
+                            Rule::IfExists => if_exists = true,
+                            Rule::Timeout => timeout = get_timeout(self, *child_id)?,
+                            _ => {
+                                return Err(SbroadError::Invalid(
+                                    Entity::Node,
+                                    Some(format_smolstr!(
+                                        "ACL node contains unexpected child: {child_node:?}",
+                                    )),
+                                ));
+                            }
+                        }
                     }
+                    let name = name.expect("RoleName expected under DropUser node");
                     let drop_user = DropUser {
-                        name: user_name,
+                        name,
+                        if_exists,
                         timeout,
                     };
                     let plan_id = plan.nodes.push(drop_user.into());
                     map.add(id, plan_id);
                 }
                 Rule::CreateRole => {
-                    let role_name_id = node
-                        .children
-                        .first()
-                        .expect("RoleName expected under CreateRole node");
-                    let role_name = parse_identifier(self, *role_name_id)?;
-
+                    let mut name = None;
+                    let mut if_not_exists = DEFAULT_IF_NOT_EXISTS;
                     let mut timeout = get_default_timeout();
-                    if let Some(timeout_child_id) = node.children.get(1) {
-                        timeout = get_timeout(self, *timeout_child_id)?;
+                    for child_id in &node.children {
+                        let child_node = self.nodes.get_node(*child_id)?;
+                        match child_node.rule {
+                            Rule::Identifier => name = Some(parse_identifier(self, *child_id)?),
+                            Rule::IfNotExists => if_not_exists = true,
+                            Rule::Timeout => timeout = get_timeout(self, *child_id)?,
+                            _ => {
+                                return Err(SbroadError::Invalid(
+                                    Entity::Node,
+                                    Some(format_smolstr!(
+                                        "ACL node contains unexpected child: {child_node:?}",
+                                    )),
+                                ));
+                            }
+                        }
                     }
+
+                    let name = name.expect("RoleName expected under CreateRole node");
                     let create_role = CreateRole {
-                        name: role_name,
+                        name,
+                        if_not_exists,
                         timeout,
                     };
+
                     let plan_id = plan.nodes.push(create_role.into());
                     map.add(id, plan_id);
                 }
diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest
index 5d69964fe6..d4883bdd68 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -8,12 +8,13 @@ Table     = @{ Identifier }
 ScanTable = { Table }
 ScanCteOrTable = @{ Table }
 
+IfExists = { ^"if" ~ ^"exists" }
+IfNotExists = { ^"if" ~ ^"not" ~ ^"exists" }
+
 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 }
@@ -30,7 +31,7 @@ Plugin = _{ CreatePlugin | DropPlugin | AlterPlugin }
 
 ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivilege | RevokePrivilege }
     CreateUser = {
-        ^"create" ~ ^"user" ~ Identifier ~ (^"with")? ~ ^"password" ~ SingleQuotedString ~
+        ^"create" ~ ^"user" ~ IfNotExists? ~ Identifier ~ (^"with")? ~ ^"password" ~ SingleQuotedString ~
         AuthMethod? ~ TimeoutOption?
     }
     AlterUser = {
@@ -45,9 +46,9 @@ ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivil
             ChapSha1 = { ^"chap-sha1" }
             Md5 = { ^"md5" }
             Ldap = { ^"ldap" }
-    DropUser = { ^"drop" ~ ^"user" ~ Identifier ~ TimeoutOption? }
-    CreateRole = { ^"create" ~ ^"role" ~ Identifier ~ TimeoutOption? }
-    DropRole = { ^"drop" ~ ^"role" ~ Identifier ~ TimeoutOption? }
+    DropUser = { ^"drop" ~ ^"user" ~ IfExists? ~ Identifier ~ TimeoutOption? }
+    CreateRole = { ^"create" ~ ^"role" ~ IfNotExists? ~ Identifier ~ TimeoutOption? }
+    DropRole = { ^"drop" ~ ^"role" ~ IfExists? ~ Identifier ~ TimeoutOption? }
     GrantPrivilege = { ^"grant" ~ PrivBlock ~ ^"to" ~ Identifier ~ TimeoutOption? }
     RevokePrivilege = { ^"revoke" ~ PrivBlock ~ ^"from" ~ Identifier ~ TimeoutOption? }
         PrivBlock = _{ PrivBlockPrivilege | PrivBlockRolePass }
@@ -78,7 +79,7 @@ ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivil
 DDL = _{ CreateTable | DropTable | CreateIndex | DropIndex
          | CreateProc | DropProc | RenameProc | SetParam | SetTransaction | AlterSystem }
     CreateTable = {
-        ^"create" ~ ^"table" ~ NewTable ~
+        ^"create" ~ ^"table" ~ IfNotExists? ~ NewTable ~
         "(" ~ Columns ~ ("," ~ PrimaryKey)? ~ ")" ~
         Engine? ~ Distribution? ~ TimeoutOption?
     }
@@ -95,10 +96,10 @@ DDL = _{ CreateTable | DropTable | CreateIndex | DropIndex
         Global = { ^"globally" }
         Sharding = { ^"by" ~ "(" ~ Identifier ~ ("," ~ Identifier)* ~ ")" ~ Tier? }
         Tier = { ^"in" ~ ^"tier" ~ Identifier }
-    DropTable = { ^"drop" ~ ^"table" ~ Table ~ TimeoutOption? }
+    DropTable = { ^"drop" ~ ^"table" ~ IfExists? ~ Table ~ TimeoutOption? }
 
     CreateProc = {
-        ^"create" ~ ^"procedure" ~ Identifier
+        ^"create" ~ ^"procedure" ~ IfNotExists? ~ Identifier
         ~ ProcParams ~ (^"language" ~ ProcLanguage)?
         ~ ((^"as" ~ "$$" ~ ProcBody ~ "$$") | (^"begin" ~ "atomic" ~ ProcBody ~ "end"))
         ~ TimeoutOption?
@@ -108,7 +109,7 @@ DDL = _{ CreateTable | DropTable | CreateIndex | DropIndex
             SQL = { ^"sql" }
         ProcBody = { (Insert | Update | Delete) }
 
-    DropProc = { ^"drop" ~ ^"procedure" ~ ProcWithOptionalParams ~ TimeoutOption? }
+    DropProc = { ^"drop" ~ ^"procedure" ~ IfExists? ~ ProcWithOptionalParams ~ TimeoutOption? }
         ProcWithOptionalParams = { Identifier ~ ProcParams? }
 
     RenameProc = { ^"alter" ~ ^"procedure" ~ OldProc ~ ProcParams? ~ ^"rename" ~ ^"to" ~ NewProc ~ TimeoutOption? }
@@ -116,7 +117,7 @@ DDL = _{ CreateTable | DropTable | CreateIndex | DropIndex
         NewProc = @{ Identifier }
 
     CreateIndex = {
-        ^"create" ~ Unique? ~ ^"index" ~ Identifier ~ ^"on" ~ Table
+        ^"create" ~ Unique? ~ ^"index" ~ IfNotExists? ~ Identifier ~ ^"on" ~ Table
         ~ IndexType? ~ "(" ~ Parts ~ ")" ~ IndexOptions? ~ TimeoutOption?
     }
     Unique = { ^"unique" }
@@ -140,7 +141,7 @@ DDL = _{ CreateTable | DropTable | CreateIndex | DropIndex
             Manhattan = { ^"manhattan" }
         Hint = { ^"hint" ~ "=" ~ (True | False) }
 
-    DropIndex = { ^"drop" ~ ^"index" ~ Identifier ~ TimeoutOption? }
+    DropIndex = { ^"drop" ~ ^"index" ~ IfExists? ~ Identifier ~ TimeoutOption? }
 
     SetParam = { ^"set" ~ SetScope? ~ ConfParam  }
         SetScope = { ScopeSession | ScopeLocal }
diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs
index ea7e9454f7..f03c9fa718 100644
--- a/sbroad-core/src/ir.rs
+++ b/sbroad-core/src/ir.rs
@@ -33,7 +33,7 @@ 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,
+    GroupBy, Having, Insert, Limit, Motion, MutNode, Node, Node136, Node232, Node32, Node64,
     Node96, NodeId, NodeOwned, OrderBy, Projection, Reference, Row, ScanRelation, Selection,
     StableFunction, Trim, UnaryExpr, Values,
 };
@@ -79,7 +79,7 @@ pub struct Nodes {
     arena64: Vec<Node64>,
     arena96: Vec<Node96>,
     arena136: Vec<Node136>,
-    arena224: Vec<Node224>,
+    arena224: Vec<Node232>,
 }
 
 impl Nodes {
@@ -179,17 +179,17 @@ impl Nodes {
                         Node::Plugin(Plugin::ChangeConfig(change_config))
                     }
                 }),
-            ArenaType::Arena224 => self
+            ArenaType::Arena232 => self
                 .arena224
                 .get(id.offset as usize)
                 .map(|node| match node {
-                    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) => {
+                    Node232::CreateIndex(create_index) => Node::Ddl(Ddl::CreateIndex(create_index)),
+                    Node232::CreateTable(create_table) => Node::Ddl(Ddl::CreateTable(create_table)),
+                    Node232::Invalid(inv) => Node::Invalid(inv),
+                    Node232::AppendServiceToTier(append) => {
                         Node::Plugin(Plugin::AppendServiceToTier(append))
                     }
-                    Node224::RemoveServiceFromTier(remove) => {
+                    Node232::RemoveServiceFromTier(remove) => {
                         Node::Plugin(Plugin::RemoveServiceFromTier(remove))
                     }
                 }),
@@ -341,21 +341,21 @@ impl Nodes {
                         }
                     })
             }
-            ArenaType::Arena224 => {
+            ArenaType::Arena232 => {
                 self.arena224
                     .get_mut(id.offset as usize)
                     .map(|node| match node {
-                        Node224::CreateIndex(create_index) => {
+                        Node232::CreateIndex(create_index) => {
                             MutNode::Ddl(MutDdl::CreateIndex(create_index))
                         }
-                        Node224::Invalid(inv) => MutNode::Invalid(inv),
-                        Node224::CreateTable(create_table) => {
+                        Node232::Invalid(inv) => MutNode::Invalid(inv),
+                        Node232::CreateTable(create_table) => {
                             MutNode::Ddl(MutDdl::CreateTable(create_table))
                         }
-                        Node224::AppendServiceToTier(append) => {
+                        Node232::AppendServiceToTier(append) => {
                             MutNode::Plugin(MutPlugin::AppendServiceToTier(append))
                         }
-                        Node224::RemoveServiceFromTier(remove) => {
+                        Node232::RemoveServiceFromTier(remove) => {
                             MutNode::Plugin(MutPlugin::RemoveServiceFromTier(remove))
                         }
                     })
@@ -384,9 +384,9 @@ impl Nodes {
                 offset: u32::try_from(self.arena136.len()).unwrap(),
                 arena_type: ArenaType::Arena136,
             },
-            ArenaType::Arena224 => NodeId {
+            ArenaType::Arena232 => NodeId {
                 offset: u32::try_from(self.arena224.len()).unwrap(),
-                arena_type: ArenaType::Arena224,
+                arena_type: ArenaType::Arena232,
             },
         }
     }
@@ -425,7 +425,7 @@ impl Nodes {
         self.arena136.iter()
     }
 
-    pub fn iter224(&self) -> Iter<'_, Node224> {
+    pub fn iter224(&self) -> Iter<'_, Node232> {
         self.arena224.iter()
     }
 
@@ -477,10 +477,10 @@ impl Nodes {
 
                 new_node_id
             }
-            NodeAligned::Node224(node224) => {
+            NodeAligned::Node232(node224) => {
                 let new_node_id = NodeId {
                     offset: u32::try_from(self.arena224.len()).unwrap(),
-                    arena_type: ArenaType::Arena224,
+                    arena_type: ArenaType::Arena232,
                 };
 
                 self.arena224.push(node224);
@@ -881,13 +881,13 @@ impl Plan {
                 let node136 = std::mem::replace(node136, stub);
                 node136.into_owned()
             }
-            ArenaType::Arena224 => {
+            ArenaType::Arena232 => {
                 let node224 = self
                     .nodes
                     .arena224
                     .get_mut(usize::try_from(dst_id.offset).unwrap())
                     .unwrap();
-                let stub = Node224::Invalid(Invalid {});
+                let stub = Node232::Invalid(Invalid {});
                 let node224 = std::mem::replace(node224, stub);
                 node224.into_owned()
             }
diff --git a/sbroad-core/src/ir/node.rs b/sbroad-core/src/ir/node.rs
index 9c4f998236..dbd7cb7cf1 100644
--- a/sbroad-core/src/ir/node.rs
+++ b/sbroad-core/src/ir/node.rs
@@ -45,7 +45,7 @@ pub enum ArenaType {
     Arena64,
     Arena96,
     Arena136,
-    Arena224,
+    Arena232,
 }
 
 impl Display for ArenaType {
@@ -63,7 +63,7 @@ impl Display for ArenaType {
             ArenaType::Arena136 => {
                 write!(f, "136")
             }
-            ArenaType::Arena224 => {
+            ArenaType::Arena232 => {
                 write!(f, "224")
             }
         }
@@ -743,6 +743,7 @@ impl From<ValuesRow> for NodeAligned {
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct DropRole {
     pub name: SmolStr,
+    pub if_exists: bool,
     pub timeout: Decimal,
 }
 
@@ -755,6 +756,7 @@ impl From<DropRole> for NodeAligned {
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct DropUser {
     pub name: SmolStr,
+    pub if_exists: bool,
     pub timeout: Decimal,
 }
 
@@ -767,6 +769,7 @@ impl From<DropUser> for NodeAligned {
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct CreateRole {
     pub name: SmolStr,
+    pub if_not_exists: bool,
     pub timeout: Decimal,
 }
 
@@ -780,6 +783,7 @@ impl From<CreateRole> for NodeAligned {
 pub struct CreateUser {
     pub name: SmolStr,
     pub password: SmolStr,
+    pub if_not_exists: bool,
     pub auth_method: SmolStr,
     pub timeout: Decimal,
 }
@@ -838,6 +842,7 @@ pub struct CreateTable {
     pub sharding_key: Option<Vec<SmolStr>>,
     /// Vinyl is supported only for sharded tables.
     pub engine_type: SpaceEngineType,
+    pub if_not_exists: bool,
     pub timeout: Decimal,
     /// Shows which tier the sharded table belongs to.
     /// Field has value, only if it was specified in [ON TIER] part of CREATE TABLE statement.
@@ -849,13 +854,14 @@ pub struct CreateTable {
 
 impl From<CreateTable> for NodeAligned {
     fn from(value: CreateTable) -> Self {
-        Self::Node224(Node224::CreateTable(value))
+        Self::Node232(Node232::CreateTable(value))
     }
 }
 
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct DropTable {
     pub name: SmolStr,
+    pub if_exists: bool,
     pub timeout: Decimal,
 }
 
@@ -870,6 +876,7 @@ pub struct CreateProc {
     pub name: SmolStr,
     pub params: Vec<ParamDef>,
     pub body: SmolStr,
+    pub if_not_exists: bool,
     pub language: Language,
     pub timeout: Decimal,
 }
@@ -884,6 +891,7 @@ impl From<CreateProc> for NodeAligned {
 pub struct DropProc {
     pub name: SmolStr,
     pub params: Option<Vec<ParamDef>>,
+    pub if_exists: bool,
     pub timeout: Decimal,
 }
 
@@ -913,6 +921,7 @@ pub struct CreateIndex {
     pub table_name: SmolStr,
     pub columns: Vec<SmolStr>,
     pub unique: bool,
+    pub if_not_exists: bool,
     pub index_type: IndexType,
     pub bloom_fpr: Option<Decimal>,
     pub page_size: Option<u32>,
@@ -927,13 +936,14 @@ pub struct CreateIndex {
 
 impl From<CreateIndex> for NodeAligned {
     fn from(value: CreateIndex) -> Self {
-        Self::Node224(Node224::CreateIndex(value))
+        Self::Node232(Node232::CreateIndex(value))
     }
 }
 
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct DropIndex {
     pub name: SmolStr,
+    pub if_exists: bool,
     pub timeout: Decimal,
 }
 
@@ -1225,7 +1235,7 @@ impl Node136 {
 
 #[allow(clippy::module_name_repetitions, clippy::large_enum_variant)]
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
-pub enum Node224 {
+pub enum Node232 {
     Invalid(Invalid),
     CreateTable(CreateTable),
     CreateIndex(CreateIndex),
@@ -1233,21 +1243,21 @@ pub enum Node224 {
     RemoveServiceFromTier(RemoveServiceFromTier),
 }
 
-impl Node224 {
+impl Node232 {
     #[must_use]
     pub fn into_owned(self) -> NodeOwned {
         match self {
-            Node224::CreateTable(create_table) => {
+            Node232::CreateTable(create_table) => {
                 NodeOwned::Ddl(DdlOwned::CreateTable(create_table))
             }
-            Node224::CreateIndex(create_index) => {
+            Node232::CreateIndex(create_index) => {
                 NodeOwned::Ddl(DdlOwned::CreateIndex(create_index))
             }
-            Node224::Invalid(inv) => NodeOwned::Invalid(inv),
-            Node224::AppendServiceToTier(append) => {
+            Node232::Invalid(inv) => NodeOwned::Invalid(inv),
+            Node232::AppendServiceToTier(append) => {
                 NodeOwned::Plugin(PluginOwned::AppendServiceToTier(append))
             }
-            Node224::RemoveServiceFromTier(remove) => {
+            Node232::RemoveServiceFromTier(remove) => {
                 NodeOwned::Plugin(PluginOwned::RemoveServiceFromTier(remove))
             }
         }
@@ -1261,7 +1271,7 @@ pub enum NodeAligned {
     Node64(Node64),
     Node96(Node96),
     Node136(Node136),
-    Node224(Node224),
+    Node232(Node232),
 }
 
 impl From<Node32> for NodeAligned {
@@ -1288,9 +1298,9 @@ impl From<Node136> for NodeAligned {
     }
 }
 
-impl From<Node224> for NodeAligned {
-    fn from(value: Node224) -> Self {
-        Self::Node224(value)
+impl From<Node232> for NodeAligned {
+    fn from(value: Node232) -> Self {
+        Self::Node232(value)
     }
 }
 // parameter to avoid multiple enums
diff --git a/sbroad-core/src/ir/node/plugin.rs b/sbroad-core/src/ir/node/plugin.rs
index 57a103c104..424d90df44 100644
--- a/sbroad-core/src/ir/node/plugin.rs
+++ b/sbroad-core/src/ir/node/plugin.rs
@@ -1,5 +1,5 @@
 use crate::errors::{Entity, SbroadError};
-use crate::ir::node::{Node136, Node224, Node96, NodeAligned};
+use crate::ir::node::{Node136, Node232, Node96, NodeAligned};
 use crate::ir::{Node, NodeId, Plan};
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr};
@@ -116,7 +116,7 @@ pub struct AppendServiceToTier {
 
 impl From<AppendServiceToTier> for NodeAligned {
     fn from(value: AppendServiceToTier) -> Self {
-        Self::Node224(Node224::AppendServiceToTier(value))
+        Self::Node232(Node232::AppendServiceToTier(value))
     }
 }
 
@@ -131,7 +131,7 @@ pub struct RemoveServiceFromTier {
 
 impl From<RemoveServiceFromTier> for NodeAligned {
     fn from(value: RemoveServiceFromTier) -> Self {
-        Self::Node224(Node224::RemoveServiceFromTier(value))
+        Self::Node232(Node232::RemoveServiceFromTier(value))
     }
 }
 
@@ -419,7 +419,7 @@ mod test {
             },
             TestCase {
                 sql: r#"ALTER PLUGIN "abc" 0.1.0 ADD SERVICE "svc1" TO TIER "tier1" option(timeout=1)"#,
-                arena_type: ArenaType::Arena224,
+                arena_type: ArenaType::Arena232,
                 expected: PluginOwned::AppendServiceToTier(AppendServiceToTier {
                     service_name: SmolStr::from("svc1"),
                     plugin_name: SmolStr::from("abc"),
@@ -430,7 +430,7 @@ mod test {
             },
             TestCase {
                 sql: r#"ALTER PLUGIN "abc" 0.1.0 REMOVE SERVICE "svc1" FROM TIER "tier1" option(timeout=11)"#,
-                arena_type: ArenaType::Arena224,
+                arena_type: ArenaType::Arena232,
                 expected: PluginOwned::RemoveServiceFromTier(RemoveServiceFromTier {
                     service_name: SmolStr::from("svc1"),
                     plugin_name: SmolStr::from("abc"),
diff --git a/sbroad-core/src/ir/node/tests.rs b/sbroad-core/src/ir/node/tests.rs
index d5453a0a98..e541233a21 100644
--- a/sbroad-core/src/ir/node/tests.rs
+++ b/sbroad-core/src/ir/node/tests.rs
@@ -1,4 +1,4 @@
-use crate::ir::node::{Node136, Node224, Node32, Node64, Node96};
+use crate::ir::node::{Node136, Node232, Node32, Node64, Node96};
 
 #[test]
 fn test_node_size() {
@@ -6,5 +6,5 @@ fn test_node_size() {
     assert!(std::mem::size_of::<Node64>() == 72);
     assert!(std::mem::size_of::<Node96>() == 96);
     assert!(std::mem::size_of::<Node136>() == 136);
-    assert!(std::mem::size_of::<Node224>() == 224);
+    assert!(std::mem::size_of::<Node232>() == 232);
 }
diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs
index 9b31ad124e..8669a42497 100644
--- a/sbroad-core/src/ir/operator.rs
+++ b/sbroad-core/src/ir/operator.rs
@@ -1635,7 +1635,7 @@ impl Plan {
         for (id, _) in self.nodes.arena224.iter().enumerate() {
             let parent_id = NodeId {
                 offset: u32::try_from(id).unwrap(),
-                arena_type: ArenaType::Arena224,
+                arena_type: ArenaType::Arena232,
             };
 
             if !matches!(self.get_node(parent_id)?, Node::Relational(_)) {
-- 
GitLab