From e93bfc77805248b50f80c997cdeb3e6669890f5e Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 24 Sep 2025 11:18:03 +0200 Subject: [PATCH 1/2] New parameter 'replace_robot_variables' in keywords running queries or scripts --- src/DatabaseLibrary/assertion.py | 18 +++- src/DatabaseLibrary/query.py | 38 ++++++++- test/resources/common.resource | 1 + .../select_with_robot_variables.sql | 1 + .../replace_robot_variables.robot | 85 +++++++++++++++++++ test/tests/common_tests/script_files.robot | 3 - 6 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 test/resources/script_file_tests/select_with_robot_variables.sql create mode 100644 test/tests/common_tests/replace_robot_variables.robot diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 3d2fd56..926947f 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -278,6 +278,7 @@ def check_row_count( retry_timeout="0 seconds", retry_pause="0.5 seconds", *, + replace_robot_variables=False, selectStatement: Optional[str] = None, sansTran: Optional[bool] = None, ): @@ -298,6 +299,8 @@ def check_row_count( Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass. See `Retry mechanism` for more details. + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. + === Some parameters were renamed in version 2.0 === The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, please use new parameters ``select_statement`` and ``no_transaction`` instead. @@ -317,7 +320,11 @@ def check_row_count( while not check_ok: try: num_rows = self.row_count( - select_statement, no_transaction=no_transaction, alias=alias, parameters=parameters + select_statement, + no_transaction=no_transaction, + alias=alias, + parameters=parameters, + replace_robot_variables=replace_robot_variables, ) verify_assertion(num_rows, assertion_operator, expected_value, "Wrong row count:", assertion_message) check_ok = True @@ -343,6 +350,7 @@ def check_query_result( retry_timeout="0 seconds", retry_pause="0.5 seconds", *, + replace_robot_variables=False, selectStatement: Optional[str] = None, sansTran: Optional[bool] = None, ): @@ -368,6 +376,8 @@ def check_query_result( Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass. See `Retry mechanism` for more details. + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. + === Some parameters were renamed in version 2.0 === The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, please use new parameters ``select_statement`` and ``no_transaction`` instead. @@ -390,7 +400,11 @@ def check_query_result( while not check_ok: try: query_results = self.query( - select_statement, no_transaction=no_transaction, alias=alias, parameters=parameters + select_statement, + no_transaction=no_transaction, + alias=alias, + parameters=parameters, + replace_robot_variables=replace_robot_variables, ) row_count = len(query_results) assert ( diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 18af7e6..b1f4e40 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -20,6 +20,7 @@ import sqlparse from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn from robot.utils.dotdict import DotDict from .connection_manager import Connection @@ -46,6 +47,7 @@ def query( alias: Optional[str] = None, parameters: Optional[Tuple] = None, *, + replace_robot_variables=False, selectStatement: Optional[str] = None, sansTran: Optional[bool] = None, returnAsDict: Optional[bool] = None, @@ -65,6 +67,8 @@ def query( Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. + === Some parameters were renamed in version 2.0 === The old parameters ``selectStatement``, ``sansTran`` and ``returnAsDict`` are *deprecated*, please use new parameters ``select_statement``, ``no_transaction`` and ``return_dict`` instead. @@ -88,6 +92,7 @@ def query( select_statement, parameters=parameters, omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + replace_robot_variables=replace_robot_variables, ) all_rows = cur.fetchall() if all_rows is None: @@ -109,6 +114,7 @@ def row_count( alias: Optional[str] = None, parameters: Optional[Tuple] = None, *, + replace_robot_variables=False, selectStatement: Optional[str] = None, sansTran: Optional[bool] = None, ): @@ -123,6 +129,8 @@ def row_count( Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. + === Some parameters were renamed in version 2.0 === The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, please use new parameters ``select_statement`` and ``no_transaction`` instead. @@ -145,6 +153,7 @@ def row_count( select_statement, parameters=parameters, omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + replace_robot_variables=replace_robot_variables, ) data = cur.fetchall() if data is None: @@ -169,6 +178,7 @@ def description( alias: Optional[str] = None, parameters: Optional[Tuple] = None, *, + replace_robot_variables=False, selectStatement: Optional[str] = None, sansTran: Optional[bool] = None, ): @@ -183,6 +193,8 @@ def description( Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. + === Some parameters were renamed in version 2.0 === The old parameters ``selectStatement`` and ``sansTran`` are *deprecated*, please use new parameters ``select_statement`` and ``no_transaction`` instead. @@ -205,6 +217,7 @@ def description( select_statement, parameters=parameters, omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + replace_robot_variables=replace_robot_variables, ) self._commit_if_needed(db_connection, no_transaction) description = list(cur.description) @@ -264,8 +277,9 @@ def execute_sql_script( no_transaction: bool = False, alias: Optional[str] = None, split: bool = True, - external_parser=False, *, + external_parser=False, + replace_robot_variables=False, sqlScriptFileName: Optional[str] = None, sansTran: Optional[bool] = None, ): @@ -284,6 +298,8 @@ def execute_sql_script( Use ``alias`` to specify what connection should be used if `Handling multiple database connections`. + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. + === Some parameters were renamed in version 2.0 === The old parameters ``sqlScriptFileName`` and ``sansTran`` are *deprecated*, please use new parameters ``script_path`` and ``no_transaction`` instead. @@ -307,6 +323,7 @@ def execute_sql_script( cur, sql_file.read(), omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + replace_robot_variables=replace_robot_variables, ) else: statements_to_execute = self.split_sql_script(script_path, external_parser=external_parser) @@ -314,7 +331,7 @@ def execute_sql_script( proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") 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) + self._execute_sql(cur, statement, omit_semicolon, replace_robot_variables=replace_robot_variables) self._commit_if_needed(db_connection, no_transaction) except Exception as e: self._rollback_and_raise(db_connection, no_transaction, e) @@ -411,6 +428,7 @@ def execute_sql_string( parameters: Optional[Tuple] = None, omit_trailing_semicolon: Optional[bool] = None, *, + replace_robot_variables=False, sqlString: Optional[str] = None, sansTran: Optional[bool] = None, omitTrailingSemicolon: Optional[bool] = None, @@ -427,7 +445,9 @@ def execute_sql_string( Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). - Set the ``omit_trailing_semicolon`` to explicitly control the `Omitting trailing semicolon behavior` for the command. + Set ``omit_trailing_semicolon`` to explicitly control the `Omitting trailing semicolon behavior` for the command. + + Set ``replace_robot_variables`` to resolve RF variables (like ${MY_VAR}) before executing the SQL. === Some parameters were renamed in version 2.0 === The old parameters ``sqlString``, ``sansTran`` and ``omitTrailingSemicolon`` are *deprecated*, @@ -449,7 +469,13 @@ def execute_sql_string( cur = db_connection.client.cursor() if omit_trailing_semicolon is None: omit_trailing_semicolon = db_connection.omit_trailing_semicolon - self._execute_sql(cur, sql_string, omit_trailing_semicolon=omit_trailing_semicolon, parameters=parameters) + self._execute_sql( + cur, + sql_string, + omit_trailing_semicolon=omit_trailing_semicolon, + parameters=parameters, + replace_robot_variables=replace_robot_variables, + ) self._commit_if_needed(db_connection, no_transaction) except Exception as e: self._rollback_and_raise(db_connection, no_transaction, e) @@ -784,9 +810,11 @@ def _execute_sql( sql_statement: str, omit_trailing_semicolon: Optional[bool] = False, parameters: Optional[Tuple] = None, + replace_robot_variables=False, ): """ Runs the `sql_statement` using `cur` as Cursor object. + Use `omit_trailing_semicolon` parameter (bool) for explicit instruction, if the trailing semicolon (;) should be removed - otherwise the statement won't be executed by some databases (e.g. Oracle). @@ -794,6 +822,8 @@ def _execute_sql( """ if omit_trailing_semicolon: sql_statement = sql_statement.rstrip(";") + if replace_robot_variables: + sql_statement = BuiltIn().replace_variables(sql_statement) if parameters is None: logger.info(f'Executing sql:
{sql_statement}', html=True) return cur.execute(sql_statement) diff --git a/test/resources/common.resource b/test/resources/common.resource index 260002c..7487618 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -17,6 +17,7 @@ ${DB_NAME} db ${DB_PASS} pass ${DB_PORT} 5432 ${DB_USER} db_user +${Script files dir} ${CURDIR}/script_file_tests # used for MySQL via PyODBC only ${DB_DRIVER} ODBC Driver 18 for SQL Server diff --git a/test/resources/script_file_tests/select_with_robot_variables.sql b/test/resources/script_file_tests/select_with_robot_variables.sql new file mode 100644 index 0000000..8d8b042 --- /dev/null +++ b/test/resources/script_file_tests/select_with_robot_variables.sql @@ -0,0 +1 @@ +SELECT * FROM ${PERSON_TABLE} \ No newline at end of file diff --git a/test/tests/common_tests/replace_robot_variables.robot b/test/tests/common_tests/replace_robot_variables.robot new file mode 100644 index 0000000..dd7e35b --- /dev/null +++ b/test/tests/common_tests/replace_robot_variables.robot @@ -0,0 +1,85 @@ +*** Settings *** +Documentation Tests which work with the same input params across all databases. + +Resource ../../resources/common.resource + +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Create Table And Set Test Variable +Test Teardown Drop Tables Person And Foobar + + +*** Variables *** +${Query with vars} SELECT * FROM \${PERSON_TABLE} +${Script with vars} ${Script files dir}/select_with_robot_variables.sql + +&{Error} +... psycopg2=*syntax error*$* +... oracledb=*$*invalid character* +... pymssql=*Incorrect syntax*$* +... pymysql=xfg +... pyodbc=xfgf +... ibm_db_dbi=*Invalid SQL syntax* + + +*** Test Cases *** +Query + ${results}= Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Query ${Query with vars} + ${results}= Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Query ${Query with vars} replace_robot_variables=False + Query ${Query with vars} replace_robot_variables=True + +SQL String + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Execute Sql String ${Query with vars} + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Execute Sql String ${Query with vars} replace_robot_variables=False + Execute Sql String ${Query with vars} replace_robot_variables=True + +SQL Script + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Execute Sql Script ${Script with vars} + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Execute Sql Script ${Script with vars} replace_robot_variables=False + Execute Sql Script ${Script with vars} replace_robot_variables=True + +Row Count + ${result}= Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Row Count ${Query with vars} + ${result}= Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Row Count ${Query with vars} replace_robot_variables=False + ${result}= Row Count ${Query with vars} replace_robot_variables=True + +Description + ${result}= Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Description ${Query with vars} + ${result}= Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Description ${Query with vars} replace_robot_variables=False + ${result}= Description ${Query with vars} replace_robot_variables=True + +Check Query Result + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Check Query Result ${Query with vars} contains Franz Allan col=1 + Run Keyword And Expect Error + ... ${Error}[${DB_MODULE}] + ... Check Query Result + ... ${Query with vars} + ... contains + ... Franz Allan + ... col=1 + ... replace_robot_variables=False + Check Query Result ${Query with vars} contains Franz Allan col=1 replace_robot_variables=True + +Check Row Count + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Check Row Count ${Query with vars} == 2 + Run Keyword And Expect Error ${Error}[${DB_MODULE}] + ... Check Row Count ${Query with vars} == 2 replace_robot_variables=False + Check Row Count ${Query with vars} == 2 replace_robot_variables=True + + +*** Keywords *** +Create Table And Set Test Variable + Create Person Table And Insert Data + Set Test Variable ${PERSON_TABLE} person diff --git a/test/tests/common_tests/script_files.robot b/test/tests/common_tests/script_files.robot index db94c88..66d04bc 100644 --- a/test/tests/common_tests/script_files.robot +++ b/test/tests/common_tests/script_files.robot @@ -6,9 +6,6 @@ Suite Teardown Disconnect From Database Test Setup Create Person Table Test Teardown Drop Tables Person And Foobar -*** Variables *** -${Script files dir} ${CURDIR}/../../resources/script_file_tests - *** Test Cases *** Semicolons As Statement Separators In One Line From e8c8a32afcbe8fd598c474d64ab22ca06e7c83e9 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 24 Sep 2025 11:25:43 +0200 Subject: [PATCH 2/2] Fix tests for variables replacement - use proper error messages --- test/tests/common_tests/replace_robot_variables.robot | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/tests/common_tests/replace_robot_variables.robot b/test/tests/common_tests/replace_robot_variables.robot index dd7e35b..7883f35 100644 --- a/test/tests/common_tests/replace_robot_variables.robot +++ b/test/tests/common_tests/replace_robot_variables.robot @@ -17,9 +17,10 @@ ${Script with vars} ${Script files dir}/select_with_robot_variables.sql ... psycopg2=*syntax error*$* ... oracledb=*$*invalid character* ... pymssql=*Incorrect syntax*$* -... pymysql=xfg -... pyodbc=xfgf +... pymysql=*error*syntax* +... pyodbc=*error*syntax* ... ibm_db_dbi=*Invalid SQL syntax* +... sqlite3=*unrecognized token*$* *** Test Cases ***