From 718891fbacc4ac510606d01f9a6eb5a7b59e14ee Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 09:08:00 +0100 Subject: [PATCH 1/7] Tests for PL/SQL block with IF condition inside - Oracle and MSSQL --- test/resources/create_stored_procedure_mssql.sql | 14 ++++++++++++++ test/resources/create_stored_procedures_oracle.sql | 13 ++++++++++++- test/tests/common_tests/stored_procedures.robot | 3 +++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedure_mssql.sql index f7da94ff..c392de7e 100644 --- a/test/resources/create_stored_procedure_mssql.sql +++ b/test/resources/create_stored_procedure_mssql.sql @@ -33,4 +33,18 @@ BEGIN SELECT FIRST_NAME FROM person; SELECT LAST_NAME FROM person; RETURN; +END; + +DROP PROCEDURE IF EXISTS check_condition; +CREATE PROCEDURE check_condition +AS +DECLARE @v_condition BIT = 1; +IF @v_condition = 1 +BEGIN +PRINT 'Condition is true'; +END +ELSE +BEGIN +PRINT 'Condition is false'; +END; END; \ No newline at end of file diff --git a/test/resources/create_stored_procedures_oracle.sql b/test/resources/create_stored_procedures_oracle.sql index f56cd3a7..5e155da3 100644 --- a/test/resources/create_stored_procedures_oracle.sql +++ b/test/resources/create_stored_procedures_oracle.sql @@ -29,4 +29,15 @@ OPEN first_names_cursor for SELECT FIRST_NAME FROM person; OPEN second_names_cursor for SELECT LAST_NAME FROM person; -END; \ No newline at end of file +END; + +CREATE OR REPLACE PROCEDURE +check_condition AS +v_condition BOOLEAN := TRUE; +BEGIN +IF v_condition THEN +DBMS_OUTPUT.PUT_LINE('Condition is true'); +ELSE +DBMS_OUTPUT.PUT_LINE('Condition is false'); +END IF; +END check_condition; \ No newline at end of file diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot index 32aa6e23..0e52fef8 100644 --- a/test/tests/common_tests/stored_procedures.robot +++ b/test/tests/common_tests/stored_procedures.robot @@ -88,6 +88,9 @@ Procedure Returns Multiple Result Sets Should Be Equal ${second result set}[0][0] See Should Be Equal ${second result set}[1][0] Schneider +Procedure With IF/ELSE Block + Call Stored Procedure check_condition + *** Keywords *** Create And Fill Tables And Stored Procedures From a762ab4446092f48f4817f506ed8a7691d98d817 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 09:10:01 +0100 Subject: [PATCH 2/7] Fix docker run strings --- test/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/readme.md b/test/readme.md index c9f9fde7..41e7175d 100644 --- a/test/readme.md +++ b/test/readme.md @@ -40,7 +40,7 @@ See the folder `.github/workflows` ## Microsoft SQL Server - https://hub.docker.com/_/microsoft-mssql-server -- docker run --rm --name mssql -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=MyPass1234!" -p 1433:1433 -d mcr.microsoft.com/mssql/server +- docker run --rm --name mssql -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD='MyPass1234!' -p 1433:1433 -d mcr.microsoft.com/mssql/server --> login and create DB: - docker exec -it mssql bash - /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'MyPass1234!' From dc5a2e143d5280305b8d026f9e7f0576784d9d62 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 09:12:11 +0100 Subject: [PATCH 3/7] Improve statemnt blocks recognition using regexp --- src/DatabaseLibrary/query.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 2be123f9..a3bcf0ad 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -13,6 +13,7 @@ # limitations under the License. import inspect +import re import sys from typing import List, Optional @@ -277,12 +278,17 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") current_statement = "" inside_statements_group = False - + proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?") + proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") for line in sql_file: line = line.strip() if line.startswith("#") or line.startswith("--") or line == "/": continue - if line.lower().startswith("begin"): + + # check if the line matches the creating procedure regexp pattern + elif proc_start_pattern.match(line.lower()): + inside_statements_group = True + elif line.lower().startswith("begin"): inside_statements_group = True # semicolons inside the line? use them to separate statements @@ -297,12 +303,16 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali for sqlFragment in sqlFragments: if len(sqlFragment.strip()) == 0: continue + if inside_statements_group: # if statements inside a begin/end block have semicolns, # they must persist - even with oracle sqlFragment += "; " - if sqlFragment.lower() == "end; ": + + if proc_end_pattern.match(sqlFragment.lower()): inside_statements_group = False + elif proc_start_pattern.match(sqlFragment.lower()): + inside_statements_group = True elif sqlFragment.lower().startswith("begin"): inside_statements_group = True @@ -326,7 +336,8 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali for statement in statements_to_execute: logger.info(f"Executing statement from script file: {statement}") - omit_semicolon = not statement.lower().endswith("end;") + line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") + omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: db_connection.client.commit() From 0999fef1fbd6c96e161413948b72670151b4f252 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 17:10:05 +0100 Subject: [PATCH 4/7] Add scripts for create procedure tests for other databases --- .../create_stored_procedure_mssql.sql | 2 +- .../create_stored_procedure_mysql.sql | 13 ++++++++++++- .../create_stored_procedure_postgres.sql | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedure_mssql.sql index c392de7e..9baf3961 100644 --- a/test/resources/create_stored_procedure_mssql.sql +++ b/test/resources/create_stored_procedure_mssql.sql @@ -46,5 +46,5 @@ END ELSE BEGIN PRINT 'Condition is false'; -END; +END END; \ No newline at end of file diff --git a/test/resources/create_stored_procedure_mysql.sql b/test/resources/create_stored_procedure_mysql.sql index 2f691304..5da8b262 100644 --- a/test/resources/create_stored_procedure_mysql.sql +++ b/test/resources/create_stored_procedure_mysql.sql @@ -27,4 +27,15 @@ CREATE PROCEDURE get_all_first_and_second_names() BEGIN SELECT FIRST_NAME FROM person; SELECT LAST_NAME FROM person; -END; \ No newline at end of file +END; + +DROP PROCEDURE IF EXISTS check_condition; +CREATE PROCEDURE check_condition() +BEGIN + DECLARE v_condition BOOLEAN DEFAULT TRUE; + IF v_condition THEN + SELECT 'Condition is true' AS Result; + ELSE + SELECT 'Condition is false' AS Result; + END IF; +END \ No newline at end of file diff --git a/test/resources/create_stored_procedure_postgres.sql b/test/resources/create_stored_procedure_postgres.sql index d35e9d74..158f547e 100644 --- a/test/resources/create_stored_procedure_postgres.sql +++ b/test/resources/create_stored_procedure_postgres.sql @@ -49,4 +49,23 @@ RETURN NEXT result1; OPEN result2 FOR SELECT LAST_NAME FROM person; RETURN NEXT result2; END +'; + +DROP ROUTINE IF EXISTS check_condition; +CREATE FUNCTION +check_condition() +RETURNS VOID +LANGUAGE plpgsql +AS +' +DECLARE + v_condition BOOLEAN := TRUE; + v_res BOOLEAN := TRUE; +BEGIN + IF v_condition THEN + v_res := TRUE; + ELSE + v_res := FALSE; + END IF; +END '; \ No newline at end of file From 352962d916023a6f26294c93e35aca12d0ba61d6 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 17:25:14 +0100 Subject: [PATCH 5/7] Consistent file names --- ...ocedure_mssql.sql => create_stored_procedures_mssql.sql} | 0 ...ocedure_mysql.sql => create_stored_procedures_mysql.sql} | 0 ...e_postgres.sql => create_stored_procedures_postgres.sql} | 0 test/tests/common_tests/stored_procedures.robot | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename test/resources/{create_stored_procedure_mssql.sql => create_stored_procedures_mssql.sql} (100%) rename test/resources/{create_stored_procedure_mysql.sql => create_stored_procedures_mysql.sql} (100%) rename test/resources/{create_stored_procedure_postgres.sql => create_stored_procedures_postgres.sql} (100%) diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedures_mssql.sql similarity index 100% rename from test/resources/create_stored_procedure_mssql.sql rename to test/resources/create_stored_procedures_mssql.sql diff --git a/test/resources/create_stored_procedure_mysql.sql b/test/resources/create_stored_procedures_mysql.sql similarity index 100% rename from test/resources/create_stored_procedure_mysql.sql rename to test/resources/create_stored_procedures_mysql.sql diff --git a/test/resources/create_stored_procedure_postgres.sql b/test/resources/create_stored_procedures_postgres.sql similarity index 100% rename from test/resources/create_stored_procedure_postgres.sql rename to test/resources/create_stored_procedures_postgres.sql diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot index 0e52fef8..e0cad5b5 100644 --- a/test/tests/common_tests/stored_procedures.robot +++ b/test/tests/common_tests/stored_procedures.robot @@ -98,11 +98,11 @@ Create And Fill Tables And Stored Procedures IF "${DB_MODULE}" in ["oracledb", "cx_Oracle"] Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_oracle.sql ELSE IF "${DB_MODULE}" in ["pymysql"] - Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_mysql.sql + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_mysql.sql ELSE IF "${DB_MODULE}" in ["psycopg2", "psycopg3"] - Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_postgres.sql + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_postgres.sql ELSE IF "${DB_MODULE}" in ["pymssql"] - Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_mssql.sql + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_mssql.sql ELSE Skip Don't know how to create stored procedures for '${DB_MODULE}' END From 038d9f63ed7f7d80c081355762b0c28fb66364af Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 17:08:10 +0100 Subject: [PATCH 6/7] Allow disabling the statement splitting when running an SQL script file --- src/DatabaseLibrary/query.py | 132 ++++++++++-------- .../sql_script_split_commands.robot | 22 +++ 2 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 test/tests/custom_db_tests/sql_script_split_commands.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index a3bcf0ad..b38d826f 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -209,11 +209,17 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali if cur and not sansTran: db_connection.client.rollback() - def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None): + def execute_sql_script( + self, sqlScriptFileName: str, sansTran: bool = False, split: bool = True, alias: Optional[str] = None + ): """ Executes the content of the `sqlScriptFileName` as SQL commands. Useful for setting the database to a known state before running your tests, or clearing out your test data after running each a test. + SQL commands are expected to be delimited by a semicolon (';') - they will be split and executed separately. + You can disable this behaviour setting the parameter `split` to _False_ - + in this case the entire script content will be passed to the database module for execution. + Sample usage : | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-setup.sql | @@ -222,7 +228,6 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-teardown.sql | | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-teardown.sql | - SQL commands are expected to be delimited by a semicolon (';') - they will be executed separately. For example: DELETE FROM person_employee_table; @@ -273,72 +278,77 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali with open(sqlScriptFileName, encoding="UTF-8") as sql_file: cur = None try: - statements_to_execute = [] cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") - current_statement = "" - inside_statements_group = False - proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?") - proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") - for line in sql_file: - line = line.strip() - if line.startswith("#") or line.startswith("--") or line == "/": - continue - - # check if the line matches the creating procedure regexp pattern - elif proc_start_pattern.match(line.lower()): - inside_statements_group = True - elif line.lower().startswith("begin"): - inside_statements_group = True - - # semicolons inside the line? use them to separate statements - # ... but not if they are inside a begin/end block (aka. statements group) - sqlFragments = line.split(";") - # no semicolons - if len(sqlFragments) == 1: - current_statement += line + " " - continue - quotes = 0 - # "select * from person;" -> ["select..", ""] - for sqlFragment in sqlFragments: - if len(sqlFragment.strip()) == 0: + if not split: + logger.info("Statements splitting disabled - pass entire script content to the database module") + self.__execute_sql(cur, sql_file.read()) + else: + logger.info("Splitting script file into statements...") + statements_to_execute = [] + current_statement = "" + inside_statements_group = False + proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?") + proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") + for line in sql_file: + line = line.strip() + if line.startswith("#") or line.startswith("--") or line == "/": continue - if inside_statements_group: - # if statements inside a begin/end block have semicolns, - # they must persist - even with oracle - sqlFragment += "; " - - if proc_end_pattern.match(sqlFragment.lower()): - inside_statements_group = False - elif proc_start_pattern.match(sqlFragment.lower()): + # check if the line matches the creating procedure regexp pattern + if proc_start_pattern.match(line.lower()): inside_statements_group = True - elif sqlFragment.lower().startswith("begin"): + elif line.lower().startswith("begin"): inside_statements_group = True - # check if the semicolon is a part of the value (quoted string) - quotes += sqlFragment.count("'") - quotes -= sqlFragment.count("\\'") - quotes -= sqlFragment.count("''") - inside_quoted_string = quotes % 2 != 0 - if inside_quoted_string: - sqlFragment += ";" # restore the semicolon - - current_statement += sqlFragment - if not inside_statements_group and not inside_quoted_string: - statements_to_execute.append(current_statement.strip()) - current_statement = "" - quotes = 0 - - current_statement = current_statement.strip() - if len(current_statement) != 0: - statements_to_execute.append(current_statement) - - for statement in statements_to_execute: - logger.info(f"Executing statement from script file: {statement}") - line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") - omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) - self.__execute_sql(cur, statement, omit_semicolon) + # semicolons inside the line? use them to separate statements + # ... but not if they are inside a begin/end block (aka. statements group) + sqlFragments = line.split(";") + # no semicolons + if len(sqlFragments) == 1: + current_statement += line + " " + continue + quotes = 0 + # "select * from person;" -> ["select..", ""] + for sqlFragment in sqlFragments: + if len(sqlFragment.strip()) == 0: + continue + + if inside_statements_group: + # if statements inside a begin/end block have semicolns, + # they must persist - even with oracle + sqlFragment += "; " + + if proc_end_pattern.match(sqlFragment.lower()): + inside_statements_group = False + elif proc_start_pattern.match(sqlFragment.lower()): + inside_statements_group = True + elif sqlFragment.lower().startswith("begin"): + inside_statements_group = True + + # check if the semicolon is a part of the value (quoted string) + quotes += sqlFragment.count("'") + quotes -= sqlFragment.count("\\'") + quotes -= sqlFragment.count("''") + inside_quoted_string = quotes % 2 != 0 + if inside_quoted_string: + sqlFragment += ";" # restore the semicolon + + current_statement += sqlFragment + if not inside_statements_group and not inside_quoted_string: + statements_to_execute.append(current_statement.strip()) + current_statement = "" + quotes = 0 + + current_statement = current_statement.strip() + if len(current_statement) != 0: + statements_to_execute.append(current_statement) + + for statement in statements_to_execute: + logger.info(f"Executing statement from script file: {statement}") + line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") + omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) + self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: db_connection.client.commit() finally: diff --git a/test/tests/custom_db_tests/sql_script_split_commands.robot b/test/tests/custom_db_tests/sql_script_split_commands.robot new file mode 100644 index 00000000..e6223ccb --- /dev/null +++ b/test/tests/custom_db_tests/sql_script_split_commands.robot @@ -0,0 +1,22 @@ +*** Settings *** +Documentation Tests for the parameter _split_ in the keyword +... _Execute SQL Script_ - special for the issue #184: +... https://github.com/MarketSquare/Robotframework-Database-Library/issues/184 + +Resource ../../resources/common.resource +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Create Person Table +Test Teardown Drop Tables Person And Foobar + + +*** Test Cases *** +Split Commands + [Documentation] Such a simple script works always, + ... just check if the logs if the parameter value was processed properly + Execute Sql Script ${CURDIR}/../../resources/insert_data_in_person_table.sql split=True + +Don't Split Commands + [Documentation] Running such a script as a single statement works for PostgreSQL, + ... but fails in Oracle. Check in the logs if the splitting was disabled. + Execute Sql Script ${CURDIR}/../../resources/insert_data_in_person_table.sql split=False From 8292df26c8e2c69b81f6b8038094411dfa93adf7 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 17:10:08 +0100 Subject: [PATCH 7/7] Parameter 'omitTrailingSemicolon' for explicit instruction, if the trailing semicolon (;) at the SQL string end should be removed or not --- src/DatabaseLibrary/query.py | 21 +++++++++++---- .../oracle_omit_semicolon.robot | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 test/tests/custom_db_tests/oracle_omit_semicolon.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index b38d826f..5cbdf521 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -356,13 +356,23 @@ def execute_sql_script( db_connection.client.rollback() def execute_sql_string( - self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None + self, + sqlString: str, + sansTran: bool = False, + omitTrailingSemicolon: Optional[bool] = None, + alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ - Executes the sqlString as SQL commands. Useful to pass arguments to your sql. - SQL commands are expected to be delimited by a semicolon (';'). + Executes the ``sqlString`` as a single SQL command. - Use optional `sansTran` to run command without an explicit transaction commit or rollback: + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback. + + Use optional ``omitTrailingSemicolon`` parameter for explicit instruction, + if the trailing semicolon (;) at the SQL string end should be removed or not: + - Some database modules (e.g. Oracle) throw an exception, if you leave a semicolon at the string end + - However, there are exceptional cases, when you need it even for Oracle - e.g. at the end of a PL/SQL block. + - If not specified, it's decided based on the current database module in use. For Oracle, the semicolon is removed by default. Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. @@ -374,6 +384,7 @@ def execute_sql_string( | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias | | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | sansTran=True | + | Execute Sql String | CREATE PROCEDURE proc AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; | omitTrailingSemicolon=False | | @{parameters} | Create List | person_employee_table | | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | """ @@ -382,7 +393,7 @@ def execute_sql_string( try: cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL String | {sqlString}") - self.__execute_sql(cur, sqlString, parameters=parameters) + self.__execute_sql(cur, sqlString, omit_trailing_semicolon=omitTrailingSemicolon, parameters=parameters) if not sansTran: db_connection.client.commit() finally: diff --git a/test/tests/custom_db_tests/oracle_omit_semicolon.robot b/test/tests/custom_db_tests/oracle_omit_semicolon.robot new file mode 100644 index 00000000..de2db7d4 --- /dev/null +++ b/test/tests/custom_db_tests/oracle_omit_semicolon.robot @@ -0,0 +1,27 @@ +*** Settings *** +Documentation Tests for the parameter _omitTrailingSemicolon_ in the keyword +... _Execute SQL String_ - special for the issue #184: +... https://github.com/MarketSquare/Robotframework-Database-Library/issues/184 +... The _PLSQL BLOCK_ is most likely valid for Oracle DB only. + +Resource ../../resources/common.resource +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Create Person Table And Insert Data +Test Teardown Drop Tables Person And Foobar + + +*** Variables *** +${NORMAL QUERY} SELECT * FROM person; +${PLSQL BLOCK} DECLARE ERRCODE NUMBER; ERRMSG VARCHAR2(200); BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; + + +*** Test Cases *** +Explicitely Omit Semicolon + [Documentation] Check if it works for Oracle - explicitely omitting the semicolon + ... is equal to the default behaviour, otherwise oracle_db throws an error + Execute Sql String ${NORMAL QUERY} omitTrailingSemicolon=True + +Explicitely Dont't Omit Semicolon + [Documentation] Check if it works for Oracle - it throws an error without a semicolon + Execute Sql String ${PLSQL BLOCK} omitTrailingSemicolon=False