Verify that you have JavaScript enabled in your browser.
+
+ Make sure you are using a modern enough browser. If using
+ Internet Explorer, version 11 is required.
+
+
+ Check are there messages in your browser's
+ JavaScript error log. Please report the problem if you suspect
+ you have encountered a bug.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..f678224e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,40 @@
+[build-system]
+requires = [
+ "setuptools>=61.0",
+ "robotframework>=5.0.1",
+ "robotframework-assertion-engine"
+ ]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "robotframework-databaselibrary"
+authors = [{name="Franz Allan Valencia See", email="franz.see@gmail.com"},
+]
+description = "Database Library for Robot Framework"
+readme = "README.md"
+requires-python = ">=3.8.1"
+dependencies = [
+ "robotframework>=5.0.1",
+ "robotframework-assertion-engine"
+]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: OS Independent",
+]
+license = {text = "Apache License 2.0"}
+dynamic = ["version"]
+
+[project.urls]
+"Homepage" = "https://github.com/MarketSquare/Robotframework-Database-Library"
+"Keyword docs" = "http://marketsquare.github.io/Robotframework-Database-Library/"
+
+[tool.setuptools.dynamic]
+version = {attr = "DatabaseLibrary.__version__"}
+
+[tool.black]
+line-length = 120
+
+[tool.isort]
+profile = "black"
+line_length = 120
diff --git a/requirements.txt b/requirements.txt
index cb708a2b..0a7cdb7b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,7 @@
-#PyMySQL==0.7.4
-#psycopg2==2.6.1
-robotframework>=3.0
+robotframework
+robotframework-excellib
+robotframework-assertion-engine
+psycopg2-binary
+pre-commit
+build
+twine
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100755
index fa5c0286..00000000
--- a/setup.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2010 Franz Allan Valencia See
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-"""Setup script for Robot's DatabaseLibrary distributions"""
-
-from os.path import abspath, dirname, join
-
-try:
- from setuptools import setup
-except ImportError as error:
- from distutils.core import setup
-
-
-version_file = join(dirname(abspath(__file__)), 'src', 'DatabaseLibrary', 'version.py')
-
-with open(version_file) as file:
- code = compile(file.read(), version_file, 'exec')
- exec(code)
-
-setup(name = 'robotframework-databaselibrary',
- version = VERSION,
- description = 'Database utility library for Robot Framework',
- author = 'Franz Allan Valencia See',
- author_email = 'franz.see@gmail.com',
- url = 'https://github.com/franz-see/Robotframework-Database-Library',
- package_dir = { '' : 'src'},
- packages = ['DatabaseLibrary'],
- package_data = {'DatabaseLibrary': []},
- requires = ['robotframework']
- )
diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py
index b9daabef..8b1ef859 100644
--- a/src/DatabaseLibrary/__init__.py
+++ b/src/DatabaseLibrary/__init__.py
@@ -14,55 +14,422 @@
import os
+from DatabaseLibrary.assertion import Assertion
from DatabaseLibrary.connection_manager import ConnectionManager
from DatabaseLibrary.query import Query
-from DatabaseLibrary.assertion import Assertion
from DatabaseLibrary.version import VERSION
-_version_ = VERSION
+__version__ = VERSION
+
class DatabaseLibrary(ConnectionManager, Query, Assertion):
"""
- Database Library contains utilities meant for Robot Framework's usage.
+ The Database Library for [https://robotframework.org|Robot Framework] allows you to query a database and verify the results.
+ It requires an appropriate *Python module to be installed separately* - depending on your database, like e.g. `oracledb` or `pymysql`.
+
+ == Table of contents ==
+ %TOC%
+
+ = Requirements =
+ - Python
+ - Robot Framework
+ - Python database module you're going to use - e.g. `oracledb`
+
+ = Installation =
+ | pip install robotframework-databaselibrary
+ Don't forget to install the required Python database module!
+
+ = Basic usage examples =
+ | *** Settings ***
+ | Library DatabaseLibrary
+ | Test Setup Connect To My Oracle DB
+ |
+ | *** Keywords ***
+ | Connect To My Oracle DB
+ | Connect To Database
+ | ... oracledb
+ | ... db_name=db
+ | ... db_user=my_user
+ | ... db_password=my_pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1521
+ |
+ | *** Test Cases ***
+ | Get All Names
+ | ${Rows}= Query select FIRST_NAME, LAST_NAME from person
+ | Should Be Equal ${Rows}[0][0] Franz Allan
+ | Should Be Equal ${Rows}[0][1] See
+ | Should Be Equal ${Rows}[1][0] Jerry
+ | Should Be Equal ${Rows}[1][1] Schneider
+ |
+ | Person Table Contains Expected Records
+ | ${sql}= Catenate select LAST_NAME from person
+ | Check Query Result ${sql} contains See
+ | Check Query Result ${sql} equals Schneider row=1
+ |
+ | Wait Until Table Gets New Record
+ | ${sql}= Catenate select LAST_NAME from person
+ | Check Row Count ${sql} > 2 retry_timeout=5s
+ |
+ | Person Table Contains No Joe
+ | ${sql}= Catenate SELECT id FROM person
+ | ... WHERE FIRST_NAME= 'Joe'
+ | Check Row Count ${sql} == 0
+ |
+
+ = Handling multiple database connections =
+ The library can handle multiple connections to different databases using *aliases*.
+ An alias is set while creating a connection and can be passed to library keywords in a corresponding argument.
+ == Example ==
+ | *** Settings ***
+ | Library DatabaseLibrary
+ | Test Setup Connect To All Databases
+ | Test Teardown Disconnect From All Databases
+ |
+ | *** Keywords ***
+ | Connect To All Databases
+ | Connect To Database
+ | ... psycopg2
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=5432
+ | ... alias=postgres
+ | Connect To Database
+ | ... pymysql
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=3306
+ | ... alias=mysql
+ |
+ | *** Test Cases ***
+ | Using Aliases
+ | ${names}= Query select LAST_NAME from person alias=postgres
+ | Execute Sql String drop table XYZ alias=mysql
+ |
+ | Switching Default Alias
+ | Switch Database postgres
+ | ${names}= Query select LAST_NAME from person
+ | Switch Database mysql
+ | Execute Sql String drop table XYZ
+ |
+
+ = Connection examples for different DB modules =
+ == Oracle (oracle_db) ==
+ | # Thin mode is used by default
+ | Connect To Database
+ | ... oracledb
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1521
+ |
+ | # Thick mode with default location of the Oracle Instant Client
+ | Connect To Database
+ | ... oracledb
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1521
+ | ... oracle_driver_mode=thick
+ |
+ | # Thick mode with custom location of the Oracle Instant Client
+ | Connect To Database
+ | ... oracledb
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1521
+ | ... oracle_driver_mode=thick,lib_dir=C:/instant_client_23_5
+ == PostgreSQL (psycopg2) ==
+ | Connect To Database
+ | ... psycopg2
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=5432
+ == Microsoft SQL Server (pymssql) ==
+ | # UTF-8 charset is used by default
+ | Connect To Database
+ | ... pymssql
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1433
+ |
+ | # Specifying a custom charset
+ | Connect To Database
+ | ... pymssql
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1433
+ | ... db_charset=cp1252
+ == MySQL (pymysql) ==
+ | # UTF-8 charset is used by default
+ | Connect To Database
+ | ... pymysql
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=3306
+ |
+ | # Specifying a custom charset
+ | Connect To Database
+ | ... pymysql
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=3306
+ | ... db_charset=cp1252
+ == IBM DB2 (ibm_db) ==
+ | Connect To Database
+ | ... ibm_db_dbi
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=50000
+ == MySQL via ODBC (pyodbc) ==
+ | # ODBC driver name is required
+ | # ODBC driver itself has to be installed
+ | Connect To Database
+ | ... pyodbc
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=3306
+ | ... odbc_driver={MySQL ODBC 9.2 ANSI Driver}
+ |
+ | # Specifying a custom charset if needed
+ | Connect To Database
+ | ... pyodbc
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=3306
+ | ... odbc_driver={MySQL ODBC 9.2 ANSI Driver}
+ | ... db_charset=latin1
+ == Oracle via JDBC (jaydebeapi) ==
+ | # Username and password must be set as a dictionary
+ | VAR &{CREDENTIALS} user=db_user password=pass
+ |
+ | # JAR file with Oracle JDBC driver is required
+ | # Jaydebeapi is not "natively" supported by the Database Library,
+ | # so using the custom parameters
+ | Connect To Database
+ | ... jaydebeapi
+ | ... jclassname=oracle.jdbc.driver.OracleDriver
+ | ... url=jdbc:oracle:thin:@127.0.0.1:1521/db
+ | ... driver_args=${CREDENTIALS}
+ | ... jars=C:/ojdbc17.jar
+ |
+ | # Set if getting error 'Could not commit/rollback with auto-commit enabled'
+ | Set Auto Commit False
+ |
+ | # Set for automatically removing trailing ';' (might be helpful for Oracle)
+ | Set Omit Trailing Semicolon True
+ == SQLite (sqlite3) ==
+ | # Using custom parameters required
+ | Connect To Database
+ | ... sqlite3
+ | ... database=./my_database.db
+ | ... isolation_level=${None}
+ == Teradata (teradata) ==
+ | Connect To Database
+ | ... teradata
+ | ... db_name=db
+ | ... db_user=db_user
+ | ... db_password=pass
+ | ... db_host=127.0.0.1
+ | ... db_port=1025
+
+ = Using configuration file =
+ The `Connect To Database` keyword allows providing the connection parameters in two ways:
+ - As keyword arguments
+ - In a configuration file - a simple list of _key=value_ pairs, set inside an _alias_ section.
+
+ You can use only one way or you can combine them:
+ - The keyword arguments are taken by default
+ - If no keyword argument is provided, a parameter value is searched in the config file
+
+ Along with commonly used connection parameters, named exactly as keyword arguments, a config file
+ can contain any other DB module specific parameters as key/value pairs.
+ If same custom parameter is provided both as a keyword argument *and* in config file,
+ the *keyword argument value takes precedence*.
+
+ The path to the config file is set by default to `./resources/db.cfg`.
+ You can change it using an according parameter in the `Connect To Database` keyword.
+
+ A config file *must* contain at least one section name -
+ the connection alias, if used (see `Handling multiple database connections`), or
+ `[default]` if no aliases are used.
+
+ == Config file examples ==
+ === Config file with default alias (equal to using no aliases at all) ===
+ | [default]
+ | db_module=psycopg2
+ | db_name=yourdbname
+ | db_user=yourusername
+ | db_password=yourpassword
+ | db_host=yourhost
+ | db_port=yourport
- This can allow you to query your database after an action has been made to verify the results.
+ === Config file with a specific alias ===
+ | [myoracle]
+ | db_module=oracledb
+ | db_name=yourdbname
+ | db_user=yourusername
+ | db_password=yourpassword
+ | db_host=yourhost
+ | db_port=yourport
- This is `compatible*` with any Database API Specification 2.0 module.
+ === Config file with some params only ===
+ | [default]
+ | db_password=mysecret
+ === Config file with some custom DB module specific params ===
+ | [default]
+ | my_custom_param=value
- References:
+ = Inline assertions =
+ Keywords, that accept arguments ``assertion_operator`` <`AssertionOperator`> and ``expected_value``,
+ perform a check according to the specified condition - using the [https://github.com/MarketSquare/AssertionEngine|Assertion Engine].
- + Database API Specification 2.0 - http://www.python.org/dev/peps/pep-0249/
+ Examples:
+ | Check Row Count | SELECT id FROM person | *==* | 2 |
+ | Check Query Result | SELECT first_name FROM person | *contains* | Allan |
- + Lists of DB API 2.0 - http://wiki.python.org/moin/DatabaseInterfaces
+ = Retry mechanism =
+ Assertion keywords, that accept arguments ``retry_timeout`` and ``retry_pause``, support waiting for assertion to pass.
- + Python Database Programming - http://wiki.python.org/moin/DatabaseProgramming/
+ Setting the ``retry_timeout`` argument enables the mechanism -
+ in this case the SQL request and the assertion are executed in a loop,
+ until the assertion is passed or the ``retry_timeout`` is reached.
+ The pause between the loop iterations is set using the ``retry_pause`` argument.
- Notes:
+ The argument values are set in [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format|Robot Framework time format] -
+ e.g. ``5 seconds``.
+ The retry mechanism is disabled by default - ``retry_timeout`` is set to ``0``.
+ Examples:
+ | Check Row Count | SELECT id FROM person | *==* | 2 | retry_timeout=10 seconds |
+ | Check Query Result | SELECT first_name FROM person | *contains* | Allan | retry_timeout=5s | retry_pause=1s |
- `compatible* - or at least theoretically it should be compatible. Currently tested only with postgresql
- (using psycopg2).`
+ = Logging query results =
+ Keywords, that fetch results of a SQL query, print the result rows as a table in RF log.
+ - A log head limit of *50 rows* is applied, other table rows are truncated in the log message.
+ - The limit and the logging in general can be adjusted any time in your tests using the Keyword `Set Logging Query Results`.
- Example Usage:
- | # Setup |
- | Connect to Database |
- | # Guard assertion (verify that test started in expected state). |
- | Check if not exists in database | select id from person where first_name = 'Franz Allan' and last_name = 'See' |
- | # Drive UI to do some action |
- | Go To | http://localhost/person/form.html | | # From selenium library |
- | Input Text | name=first_name | Franz Allan | # From selenium library |
- | Input Text | name=last_name | See | # From selenium library |
- | Click Button | Save | | # From selenium library |
- | # Log results |
- | @{queryResults} | Query | select * from person |
- | Log Many | @{queryResults} |
- | # Verify if persisted in the database |
- | Check if exists in database | select id from person where first_name = 'Franz Allan' and last_name = 'See' |
- | # Teardown |
- | Disconnect from Database |
+ You can also setup the limit or disable the logging during the library import.
+ Examples:
+
+ | *** Settings ***
+ | # Default behavior - logging of query results is enabled, log head is 50 rows.
+ | Library DatabaseLibrary
+ |
+ | # Logging of query results is disabled, log head is 50 rows (default).
+ | Library DatabaseLibrary log_query_results=False
+ |
+ | # Logging of query results is enabled (default), log head is 10 rows.
+ | Library DatabaseLibrary log_query_results_head=10
+ |
+ | # Logging of query results is enabled (default), log head limit is disabled (log all rows).
+ | Library DatabaseLibrary log_query_results_head=0
+
+ = Commit behavior =
+ While creating a database connection, the library doesn't explicitly set the _autocommit_ behavior -
+ so the default value of the Python DB module is used.
+ According to Python DB API specification it should be disabled by default -
+ which means each SQL transaction (even a simple _SELECT_) must contain a dedicated commit statement, if necessary.
+
+ The library manages it for you - keywords like `Query` or `Execute SQL String`
+ perform automatically a commit after running the query (or a rollback in case of error).
+
+ You can turn off this automatic commit/rollback behavior using the ``no_transaction`` parameter.
+ See docs of a particular keyword.
+
+ It's also possible to explicitly set the _autocommit_ behavior on the Python DB module level -
+ using the `Set Auto Commit` keyword.
+ This has no impact on the automatic commit/rollback behavior in library keywords (described above).
+
+ = Omitting trailing semicolon behavior =
+ Some databases (e.g. Oracle) throw an exception, if you leave a semicolon (;) at the SQL string end.
+ However, there are exceptional cases, when you need it even for Oracle - e.g. at the end of a PL/SQL block.
+
+ The library can handle it for you and remove the semicolon at the end of the SQL string.
+ By default, it's decided based on the current database module in use:
+ - For `oracle_db` and `cx_Oracle`, the trailing semicolon is removed
+ - For other modules, the trailing semicolon is left as it is
+
+ You can also set this behavior explicitly:
+ - Using the `Set Omit Trailing Semicolon` keyword
+ - Using the `omit_trailing_semicolon` parameter in the `Execute SQL String` keyword.
+
+ = Database modules compatibility =
+ The library is basically compatible with any [https://peps.python.org/pep-0249|Python Database API Specification 2.0] module.
+
+ However, the actual implementation in existing Python modules is sometimes quite different, which requires custom handling in the library.
+ Therefore, there are some modules, which are "natively" supported in the library - and others, which may work and may not.
+
+ == Python modules currently "natively" supported ==
+ === Oracle ===
+ [https://oracle.github.io/python-oracledb/|oracledb]
+ - Both thick and thin client modes are supported - you can select one using the `oracle_driver_mode` parameter.
+ - However, due to current limitations of the oracledb module, *it's not possible to switch between thick and thin modes during a test execution session* - even in different suites.
+
+ [https://oracle.github.io/python-cx_Oracle/|cx_Oracle]
+
+ === MySQL ===
+ - [https://github.com/PyMySQL/PyMySQL|pymysql]
+ - [https://mysqlclient.readthedocs.io/index.html|MySQLdb]
+ === PostgreSQL ===
+ - [https://www.psycopg.org/docs/|psycopg2]
+ === MS SQL Server ===
+ - [https://github.com/pymssql/pymssql|pymssql]
+ === SQLite ===
+ - [https://docs.python.org/3/library/sqlite3.html|sqlite3]
+ === Teradata ===
+ - [https://github.com/teradata/PyTd|teradata]
+ === IBM DB2 ===
+ - The Python package to be installed is [https://github.com/ibmdb/python-ibmdb|ibm_db]. It includes two modules - `ibm_db` and `ibm_db_dbi`.
+ - Using *`ibm_db_dbi` is highly recommended* as only this module is Python DB API 2.0 compatible. See [https://www.ibm.com/docs/en/db2/12.1?topic=applications-python-sqlalchemy-django-framework|official docs].
+ === ODBC ===
+ - [https://github.com/mkleehammer/pyodbc|pyodbc]
+ - [https://github.com/pypyodbc/pypyodbc|pypyodbc]
+ === Kingbase ===
+ - ksycopg2
"""
- ROBOT_LIBRARY_SCOPE = 'GLOBAL'
+ ROBOT_LIBRARY_SCOPE = "GLOBAL"
+ ROBOT_LIBRARY_VERSION = __version__
+
+ def __init__(self, log_query_results=True, log_query_results_head=50, warn_on_connection_overwrite=True):
+ """
+ The library can be imported without any arguments:
+ | *** Settings ***
+ | Library DatabaseLibrary
+
+ Use optional library import parameters:
+ - ``log_query_results`` and ``log_query_results_head`` to disable `Logging query results` or setup the log head
+ - ``warn_on_connection_overwrite`` to disable the warning about overwriting an existing connection
+ """
+ ConnectionManager.__init__(self, warn_on_connection_overwrite=warn_on_connection_overwrite)
+ if log_query_results_head < 0:
+ raise ValueError(f"Wrong log head value provided: {log_query_results_head}. The value can't be negative!")
+ Query.__init__(self, log_query_results=log_query_results, log_query_results_head=log_query_results_head)
diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py
index 70ed6dd8..3d2fd565 100644
--- a/src/DatabaseLibrary/assertion.py
+++ b/src/DatabaseLibrary/assertion.py
@@ -11,209 +11,466 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+from typing import Any, Optional, Tuple
+from assertionengine import AssertionOperator, verify_assertion
from robot.api import logger
+from robot.libraries.BuiltIn import BuiltIn
+from robot.utils import timestr_to_secs
+from .params_decorator import renamed_args
-class Assertion(object):
+
+class Assertion:
"""
Assertion handles all the assertions of Database Library.
"""
- def check_if_exists_in_database(self, selectStatement, sansTran=False):
+ def check_if_exists_in_database(
+ self,
+ select_statement: str,
+ *,
+ no_transaction: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ ):
"""
- Check if any row would be returned by given the input `selectStatement`. If there are no results, then this will
- throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit transaction
+ *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
+ The deprecated keyword will be removed in future versions.
+
+ Check if any row would be returned by given the input ``select_statement``. If there are no results, then this will
+ throw an AssertionError.
+
+ Set optional input ``no_transaction`` to _True_ to run command without an explicit transaction
commit or rollback.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
+ The default error message can be overridden with the ``msg`` argument.
- When you have the following assertions in your robot
- | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' |
- | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' |
+ Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
+ than one connection open.
- Then you will get the following:
- | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | # PASS |
- | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | # FAIL |
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | True |
+ Examples:
+ | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' |
+ | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message |
+ | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias |
+ | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | no_transaction=True |
+ | @{parameters} | Create List | John |
+ | Check If Exists In Database | SELECT id FROM person WHERE first_name = %s | parameters=${parameters} |
"""
- logger.info ('Executing : Check If Exists In Database | %s ' % selectStatement)
- if not self.query(selectStatement, sansTran):
- raise AssertionError("Expected to have have at least one row from '%s' "
- "but got 0 rows." % selectStatement)
-
- def check_if_not_exists_in_database(self, selectStatement, sansTran=False):
+ if not self.query(select_statement, no_transaction, alias=alias, parameters=parameters):
+ raise AssertionError(
+ msg or f"Expected to have have at least one row, but got 0 rows from: '{select_statement}'"
+ )
+
+ def check_if_not_exists_in_database(
+ self,
+ selectStatement: str,
+ sansTran: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ ):
"""
+ *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
+ The deprecated keyword will be removed in future versions.
+
This is the negation of `check_if_exists_in_database`.
- Check if no rows would be returned by given the input `selectStatement`. If there are any results, then this
- will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
- transaction commit or rollback.
+ Check if no rows would be returned by given the input ``selectStatement``. If there are any results, then this
+ will throw an AssertionError.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
+ Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback.
- When you have the following assertions in your robot
- | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' |
- | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' |
+ The default error message can be overridden with the ``msg`` argument.
- Then you will get the following:
- | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' | # PASS |
- | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | # FAIL |
+ Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
+ than one connection open.
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' | True |
- """
- logger.info('Executing : Check If Not Exists In Database | %s ' % selectStatement)
- queryResults = self.query(selectStatement, sansTran)
- if queryResults:
- raise AssertionError("Expected to have have no rows from '%s' "
- "but got some rows : %s." % (selectStatement, queryResults))
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- def row_count_is_0(self, selectStatement, sansTran=False):
+ Examples:
+ | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' |
+ | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message |
+ | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias |
+ | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' | sansTran=True |
+ | @{parameters} | Create List | John |
+ | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = %s | parameters=${parameters} |
+ """
+ query_results = self.query(selectStatement, sansTran, alias=alias, parameters=parameters)
+ if query_results:
+ raise AssertionError(
+ msg or f"Expected to have have no rows from '{selectStatement}', but got some rows: {query_results}"
+ )
+
+ def row_count_is_0(
+ self,
+ selectStatement: str,
+ sansTran: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ ):
"""
- Check if any rows are returned from the submitted `selectStatement`. If there are, then this will throw an
- AssertionError. Set optional input `sansTran` to True to run command without an explicit transaction commit or
+ *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
+ The deprecated keyword will be removed in future versions.
+
+ Check if any rows are returned from the submitted ``selectStatement``. If there are, then this will throw an
+ AssertionError.
+
+ Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or
rollback.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
+ The default error message can be overridden with the ``msg`` argument.
- When you have the following assertions in your robot
- | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' |
- | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' |
+ Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
+ than one connection open.
- Then you will get the following:
- | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | # FAIL |
- | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | # PASS |
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | True |
+ Examples:
+ | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' |
+ | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message |
+ | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | alias=my_alias |
+ | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | sansTran=True |
+ | @{parameters} | Create List | John |
+ | Row Count is 0 | SELECT id FROM person WHERE first_name = %s | parameters=${parameters} |
"""
- logger.info('Executing : Row Count Is 0 | %s ' % selectStatement)
- num_rows = self.row_count(selectStatement, sansTran)
+ num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
if num_rows > 0:
- raise AssertionError("Expected zero rows to be returned from '%s' "
- "but got rows back. Number of rows returned was %s" % (selectStatement, num_rows))
-
- def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False):
+ raise AssertionError(msg or f"Expected 0 rows, but {num_rows} were returned from: '{selectStatement}'")
+
+ def row_count_is_equal_to_x(
+ self,
+ selectStatement: str,
+ numRows: str,
+ sansTran: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ ):
"""
- Check if the number of rows returned from `selectStatement` is equal to the value submitted. If not, then this
- will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
- transaction commit or rollback.
+ *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
+ The deprecated keyword will be removed in future versions.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
- | 2 | Jerry | Schneider |
+ Check if the number of rows returned from ``selectStatement`` is equal to the value submitted. If not, then this
+ will throw an AssertionError.
- When you have the following assertions in your robot
- | Row Count Is Equal To X | SELECT id FROM person | 1 |
- | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 |
+ Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback.
- Then you will get the following:
- | Row Count Is Equal To X | SELECT id FROM person | 1 | # FAIL |
- | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | # PASS |
+ The default error message can be overridden with the ``msg`` argument.
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | True |
- """
- logger.info('Executing : Row Count Is Equal To X | %s | %s ' % (selectStatement, numRows))
- num_rows = self.row_count(selectStatement, sansTran)
- if num_rows != int(numRows.encode('ascii')):
- raise AssertionError("Expected same number of rows to be returned from '%s' "
- "than the returned rows of %s" % (selectStatement, num_rows))
+ Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
+ than one connection open.
- def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False):
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
+
+ Examples:
+ | Row Count Is Equal To X | SELECT id FROM person | 1 |
+ | Row Count Is Equal To X | SELECT id FROM person | 3 | msg=my error message |
+ | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | alias=my_alias |
+ | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | sansTran=True |
+ | @{parameters} | Create List | John |
+ | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = %s | 0 | parameters=${parameters} |
+ """
+ num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
+ if num_rows != int(numRows.encode("ascii")):
+ raise AssertionError(
+ msg or f"Expected {numRows} rows, but {num_rows} were returned from: '{selectStatement}'"
+ )
+
+ def row_count_is_greater_than_x(
+ self,
+ selectStatement: str,
+ numRows: str,
+ sansTran: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ ):
"""
- Check if the number of rows returned from `selectStatement` is greater than the value submitted. If not, then
- this will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
- transaction commit or rollback.
+ *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
+ The deprecated keyword will be removed in future versions.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
- | 2 | Jerry | Schneider |
+ Check if the number of rows returned from ``selectStatement`` is greater than the value submitted. If not, then
+ this will throw an AssertionError.
- When you have the following assertions in your robot
- | Row Count Is Greater Than X | SELECT id FROM person | 1 |
- | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 |
+ Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback.
- Then you will get the following:
- | Row Count Is Greater Than X | SELECT id FROM person | 1 | # PASS |
- | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | # FAIL |
+ The default error message can be overridden with the ``msg`` argument.
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Row Count Is Greater Than X | SELECT id FROM person | 1 | True |
- """
- logger.info('Executing : Row Count Is Greater Than X | %s | %s ' % (selectStatement, numRows))
- num_rows = self.row_count(selectStatement, sansTran)
- if num_rows <= int(numRows.encode('ascii')):
- raise AssertionError("Expected more rows to be returned from '%s' "
- "than the returned rows of %s" % (selectStatement, num_rows))
+ Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
+ than one connection open.
+
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False):
+ Examples:
+ | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 |
+ | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | msg=my error message |
+ | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | alias=my_alias |
+ | Row Count Is Greater Than X | SELECT id FROM person | 1 | sansTran=True |
+ | @{parameters} | Create List | John |
+ | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = %s | 0 | parameters=${parameters} |
+ """
+ num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
+ if num_rows <= int(numRows.encode("ascii")):
+ raise AssertionError(
+ msg or f"Expected more than {numRows} rows, but {num_rows} were returned from '{selectStatement}'"
+ )
+
+ def row_count_is_less_than_x(
+ self,
+ selectStatement: str,
+ numRows: str,
+ sansTran: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ ):
"""
- Check if the number of rows returned from `selectStatement` is less than the value submitted. If not, then this
- will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
- transaction commit or rollback.
+ *DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
+ The deprecated keyword will be removed in future versions.
+
+ Check if the number of rows returned from ``selectStatement`` is less than the value submitted. If not, then this
+ will throw an AssertionError.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
- | 2 | Jerry | Schneider |
+ Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback.
- When you have the following assertions in your robot
- | Row Count Is Less Than X | SELECT id FROM person | 3 |
+ Using optional ``msg`` to override the default error message:
+
+ Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
+ than one connection open.
+
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
+
+ Examples:
| Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 |
+ | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 2 | msg=my error message |
+ | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 3 | alias=my_alias |
+ | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 4 | sansTran=True |
+ | @{parameters} | Create List | John |
+ | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = %s | 5 | parameters=${parameters} |
+ """
+ num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
+ if num_rows >= int(numRows.encode("ascii")):
+ raise AssertionError(
+ msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'"
+ )
+
+ @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"})
+ def check_row_count(
+ self,
+ select_statement: str,
+ assertion_operator: AssertionOperator,
+ expected_value: int,
+ assertion_message: Optional[str] = None,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ retry_timeout="0 seconds",
+ retry_pause="0.5 seconds",
+ *,
+ selectStatement: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ ):
+ """
+ Check the number of rows returned from ``select_statement`` using ``assertion_operator``
+ and ``expected_value``. See `Inline assertions` for more details.
+
+ Use ``assertion_message`` to override the default error message.
+
+ Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error.
+ See `Commit behavior` for details.
- Then you will get the following:
- | Row Count Is Less Than X | SELECT id FROM person | 3 | # PASS |
- | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 | # FAIL |
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Row Count Is Less Than X | SELECT id FROM person | 3 | True |
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
+
+ Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass.
+ See `Retry mechanism` for more details.
+
+ === 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.
+
+ *The old parameters will be removed in future versions.*
+
+ === Examples ===
+ | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *==* | 1 |
+ | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *>=* | 2 | assertion_message=my error message |
+ | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *inequal* | 3 | alias=my_alias |
+ | Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *less than* | 4 | no_transaction=True |
+ | @{parameters} | Create List | John |
+ | Check Row Count | SELECT id FROM person WHERE first_name = %s | *equals* | 5 | parameters=${parameters} |
"""
- logger.info('Executing : Row Count Is Less Than X | %s | %s ' % (selectStatement, numRows))
- num_rows = self.row_count(selectStatement, sansTran)
- if num_rows >= int(numRows.encode('ascii')):
- raise AssertionError("Expected less rows to be returned from '%s' "
- "than the returned rows of %s" % (selectStatement, num_rows))
+ check_ok = False
+ time_counter = 0
+ while not check_ok:
+ try:
+ num_rows = self.row_count(
+ select_statement, no_transaction=no_transaction, alias=alias, parameters=parameters
+ )
+ verify_assertion(num_rows, assertion_operator, expected_value, "Wrong row count:", assertion_message)
+ check_ok = True
+ except AssertionError as e:
+ if time_counter >= timestr_to_secs(retry_timeout):
+ logger.info(f"Timeout '{retry_timeout}' reached")
+ raise e
+ BuiltIn().sleep(retry_pause)
+ time_counter += timestr_to_secs(retry_pause)
+
+ @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"})
+ def check_query_result(
+ self,
+ select_statement: str,
+ assertion_operator: AssertionOperator,
+ expected_value: Any,
+ row=0,
+ col=0,
+ assertion_message: Optional[str] = None,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ retry_timeout="0 seconds",
+ retry_pause="0.5 seconds",
+ *,
+ selectStatement: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ ):
+ """
+ Check value in query result returned from ``select_statement`` using ``assertion_operator`` and ``expected_value``.
+ The value position in results can be adjusted using ``row`` and ``col`` parameters (0-based).
+ See `Inline assertions` for more details.
+
+ *The assertion in this keyword is type sensitive!*
+ The ``expected_value`` is taken as a string, no argument conversion is performed.
+ Use RF syntax like ``${1}`` for numeric values.
+
+ Use optional ``assertion_message`` to override the default error message.
- def table_must_exist(self, tableName, sansTran=False):
+ Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error.
+ See `Commit behavior` for details.
+
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
+
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
+
+ Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass.
+ See `Retry mechanism` for more details.
+
+ === 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.
+
+ *The old parameters will be removed in future versions.*
+
+ === Examples ===
+ | Check Query Result | SELECT first_name FROM person | *contains* | Allan |
+ | Check Query Result | SELECT first_name, last_name FROM person | *==* | Schneider | row=1 | col=1 |
+ | Check Query Result | SELECT id FROM person WHERE first_name = 'John' | *==* | 2 | # Fails, if query returns an integer value |
+ | Check Query Result | SELECT id FROM person WHERE first_name = 'John' | *==* | ${2} | # Works, if query returns an integer value |
+ | Check Query Result | SELECT first_name FROM person | *equal* | Franz Allan | assertion_message=my error message |
+ | Check Query Result | SELECT first_name FROM person | *inequal* | John | alias=my_alias |
+ | Check Query Result | SELECT first_name FROM person | *contains* | Allan | no_transaction=True |
+ | @{parameters} | Create List | John |
+ | Check Query Result | SELECT first_name FROM person | *contains* | Allan | parameters=${parameters} |
+ """
+ check_ok = False
+ time_counter = 0
+ while not check_ok:
+ try:
+ query_results = self.query(
+ select_statement, no_transaction=no_transaction, alias=alias, parameters=parameters
+ )
+ row_count = len(query_results)
+ assert (
+ row < row_count
+ ), f"Checking row '{row}' is not possible, as query results contain {row_count} rows only!"
+ col_count = len(query_results[row])
+ assert (
+ col < col_count
+ ), f"Checking column '{col}' is not possible, as query results contain {col_count} columns only!"
+ actual_value = query_results[row][col]
+ verify_assertion(
+ actual_value, assertion_operator, expected_value, "Wrong query result:", assertion_message
+ )
+ check_ok = True
+ except AssertionError as e:
+ if time_counter >= timestr_to_secs(retry_timeout):
+ logger.info(f"Timeout '{retry_timeout}' reached")
+ raise e
+ BuiltIn().sleep(retry_pause)
+ time_counter += timestr_to_secs(retry_pause)
+
+ @renamed_args(mapping={"tableName": "table_name", "sansTran": "no_transaction"})
+ def table_must_exist(
+ self,
+ table_name: str,
+ no_transaction: bool = False,
+ msg: Optional[str] = None,
+ alias: Optional[str] = None,
+ *,
+ tableName: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ ):
"""
- Check if the table given exists in the database. Set optional input `sansTran` to True to run command without an
- explicit transaction commit or rollback.
+ Check if the table with `table_name` exists in the database.
- For example, given we have a table `person` in a database
+ Use ``msg`` for custom error message.
- When you do the following:
- | Table Must Exist | person |
+ Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error.
+ See `Commit behavior` for details.
+
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
+
+ === Some parameters were renamed in version 2.0 ===
+ The old parameters ``tableName`` and ``sansTran`` are *deprecated*,
+ please use new parameters ``table_name`` and ``no_transaction`` instead.
- Then you will get the following:
- | Table Must Exist | person | # PASS |
- | Table Must Exist | first_name | # FAIL |
-
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Table Must Exist | person | True |
- """
- logger.info('Executing : Table Must Exist | %s ' % tableName)
- if self.db_api_module_name in ["cx_Oracle"]:
- selectStatement = ("SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('%s')" % tableName)
- elif self.db_api_module_name in ["sqlite3"]:
- selectStatement = ("SELECT name FROM sqlite_master WHERE type='table' AND name='%s' COLLATE NOCASE" % tableName)
- elif self.db_api_module_name in ["ibm_db", "ibm_db_dbi"]:
- selectStatement = ("SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('%s')" % tableName)
- elif self.db_api_module_name in ["teradata"]:
- selectStatement = ("SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='%s'" % tableName)
+ *The old parameters will be removed in future versions.*
+
+ === Examples ===
+ | Table Must Exist | person |
+ | Table Must Exist | person | msg=my error message |
+ | Table Must Exist | person | alias=my_alias |
+ | Table Must Exist | person | no_transaction=True |
+ """
+ db_connection = self.connection_store.get_connection(alias)
+ if db_connection.module_name in ["cx_Oracle", "oracledb"]:
+ query = (
+ "SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND "
+ f"owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('{table_name}')"
+ )
+ table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0
+ elif db_connection.module_name in ["sqlite3"]:
+ query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}' COLLATE NOCASE"
+ table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0
+ elif db_connection.module_name in ["ibm_db", "ibm_db_dbi"]:
+ query = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{table_name}')"
+ table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0
+ elif db_connection.module_name in ["teradata"]:
+ query = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{table_name}'"
+ table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0
else:
- selectStatement = ("SELECT * FROM information_schema.tables WHERE table_name='%s'" % tableName)
- num_rows = self.row_count(selectStatement, sansTran)
- if num_rows == 0:
- raise AssertionError("Table '%s' does not exist in the db" % tableName)
+ try:
+ query = f"SELECT * FROM information_schema.tables WHERE table_name='{table_name}'"
+ table_exists = self.row_count(query, no_transaction=no_transaction, alias=alias) > 0
+ except:
+ logger.info("Database doesn't support information schema, try using a simple SQL request")
+ try:
+ query = f"SELECT 1 from {table_name} where 1=0"
+ self.row_count(query, no_transaction=no_transaction, alias=alias)
+ table_exists = True
+ except:
+ table_exists = False
+ assert table_exists, msg or f"Table '{table_name}' does not exist in the db"
diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py
index 3060a451..87926ab5 100644
--- a/src/DatabaseLibrary/connection_manager.py
+++ b/src/DatabaseLibrary/connection_manager.py
@@ -13,191 +13,664 @@
# limitations under the License.
import importlib
-
-try:
- import ConfigParser
-except:
- import configparser as ConfigParser
+import os
+from configparser import ConfigParser, NoOptionError, NoSectionError
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, Dict, Optional
from robot.api import logger
+from .params_decorator import renamed_args
+
+
+@dataclass
+class Connection:
+ client: Any
+ module_name: str
+ omit_trailing_semicolon: bool
+
+
+class ConnectionStore:
+ def __init__(self, warn_on_overwrite=True):
+ self._connections: Dict[str, Connection] = {}
+ self.default_alias: str = "default"
+ self.warn_on_overwrite = warn_on_overwrite
+
+ def register_connection(self, client: Any, module_name: str, alias: str, omit_trailing_semicolon=False):
+ if alias in self._connections and self.warn_on_overwrite:
+ if alias == self.default_alias:
+ logger.warn("Overwriting not closed connection.")
+ else:
+ logger.warn(f"Overwriting not closed connection for alias = '{alias}'")
+ self._connections[alias] = Connection(client, module_name, omit_trailing_semicolon)
+
+ def get_connection(self, alias: Optional[str]) -> Connection:
+ """
+ Return connection with given alias.
+
+ If alias is not provided, it will return default connection.
+ If there is no default connection, it will return last opened connection.
+ """
+ if not self._connections:
+ raise ValueError(f"No database connection is open.")
+ if not alias:
+ if self.default_alias in self._connections:
+ return self._connections[self.default_alias]
+ return list(self._connections.values())[-1]
+ if alias not in self._connections:
+ raise ValueError(f"Alias '{alias}' not found in existing connections.")
+ return self._connections[alias]
+
+ def pop_connection(self, alias: Optional[str]) -> Connection:
+ if not self._connections:
+ return None
+ if not alias:
+ alias = self.default_alias
+ if alias not in self._connections:
+ alias = list(self._connections.keys())[-1]
+ return self._connections.pop(alias, None)
+
+ def clear(self):
+ self._connections = {}
+
+ def switch(self, alias: str):
+ if alias not in self._connections:
+ raise ValueError(f"Alias '{alias}' not found in existing connections.")
+ self.default_alias = alias
-class ConnectionManager(object):
+ def __iter__(self):
+ return iter(self._connections.values())
+
+
+class ConfigReader:
+ def __init__(self, config_file: Optional[str], alias: str):
+ if config_file is None:
+ config_file = "./resources/db.cfg"
+ self.alias = alias
+ self.config = self._load_config(config_file)
+
+ @staticmethod
+ def _load_config(config_file: str) -> Optional[ConfigParser]:
+ config_path = Path(config_file)
+ logger.info(f"Looking for configuration file: '{config_path}'")
+ if not config_path.exists():
+ logger.info("Configuration file doesn't exist")
+ return None
+ config = ConfigParser()
+ config.read([config_path])
+ logger.info("Successfully loaded configuration file")
+ return config
+
+ def pop(self, param: str) -> Optional[str]:
+ """
+ Returns the `param` value read from the config file and deletes it from the list of all params read
+ """
+ if self.config is None:
+ logger.debug("Configuration file not loaded")
+ return None
+ try:
+ logger.debug(f"Looking for parameter '{param}' in configuration file")
+ param_value = self.config.get(self.alias, param)
+ logger.info(f"Found parameter '{param}' in configuration file")
+ self.config.remove_option(self.alias, param)
+ return param_value
+ except NoSectionError:
+ logger.debug(f"Configuration file does not have [{self.alias}] section.")
+ except NoOptionError:
+ logger.debug(f"Parameter '{param}' missing in configuration file.")
+ return None
+
+ def get_all_available_params(self) -> Dict:
+ """
+ Returns a dictionary of all params read from the config file, which are currently available
+ (some of them might have been removed using the `pop` function)
+ """
+ if self.config is None:
+ logger.debug("Configuration file not loaded")
+ return {}
+ try:
+ all_options = dict(self.config.items(self.alias))
+ return all_options
+ except NoSectionError:
+ logger.debug(f"Configuration file does not have [{self.alias}] section.")
+ return {}
+
+
+class ConnectionManager:
"""
Connection Manager handles the connection & disconnection to the database.
"""
- def __init__(self):
- """
- Initializes _dbconnection to None.
- """
- self._dbconnection = None
- self.db_api_module_name = None
+ def __init__(self, warn_on_connection_overwrite=True):
+ self.connection_store: ConnectionStore = ConnectionStore(warn_on_overwrite=warn_on_connection_overwrite)
+ self.ibmdb_driver_already_added_to_path: bool = False
+
+ @staticmethod
+ def _hide_password_values(string_with_pass, params_separator=","):
+ string_with_hidden_pass = string_with_pass
+ for pass_param_name in ["pass", "passwd", "password", "pwd", "PWD"]:
+ pass_param_name += "="
+ splitted = string_with_hidden_pass.split(pass_param_name)
+ if len(splitted) < 2:
+ continue
+ splitted = splitted[1].split(params_separator)
+ value_to_hide = splitted[0]
+ string_with_hidden_pass = string_with_hidden_pass.replace(
+ f"{pass_param_name}{value_to_hide}", f"{pass_param_name}***"
+ )
+ return string_with_hidden_pass
- def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None, dbPassword=None, dbHost=None, dbPort=None, dbCharset=None, dbDriver=None, dbConfigFile="./resources/db.cfg"):
+ @renamed_args(
+ mapping={
+ "dbapiModuleName": "db_module",
+ "dbName": "db_name",
+ "dbUsername": "db_user",
+ "dbPassword": "db_password",
+ "dbHost": "db_host",
+ "dbPort": "db_port",
+ "dbCharset": "db_charset",
+ "dbDriver": "odbc_driver",
+ "dbConfigFile": "config_file",
+ "driverMode": "oracle_driver_mode",
+ }
+ )
+ def connect_to_database(
+ self,
+ db_module: Optional[str] = None,
+ db_name: Optional[str] = None,
+ db_user: Optional[str] = None,
+ db_password: Optional[str] = None,
+ db_host: Optional[str] = None,
+ db_port: Optional[int] = None,
+ db_charset: Optional[str] = None,
+ odbc_driver: Optional[str] = None,
+ config_file: Optional[str] = None,
+ oracle_driver_mode: Optional[str] = None,
+ alias: str = "default",
+ **custom_connection_params,
+ ):
"""
- Loads the DB API 2.0 module given `dbapiModuleName` then uses it to
- connect to the database using `dbName`, `dbUsername`, and `dbPassword`.
+ Creates a database connection using the DB API 2.0 ``db_module`` and the parameters provided.
+ Along with listed commonly used arguments (`db_name`, `db_host` etc.)
+ you can set any other DB module specific parameters as key/value pairs.
- Optionally, you can specify a `dbConfigFile` wherein it will load the
- default property values for `dbapiModuleName`, `dbName` `dbUsername`
- and `dbPassword` (note: specifying `dbapiModuleName`, `dbName`
- `dbUsername` or `dbPassword` directly will override the properties of
- the same key in `dbConfigFile`). If no `dbConfigFile` is specified, it
- defaults to `./resources/db.cfg`.
+ Use ``config_file`` to provide a path to configuration file with connection parameters
+ to be used along with / instead of keyword arguments.
+ If no specified, it defaults to `./resources/db.cfg`.
+ See `Using configuration file` for more details.
- The `dbConfigFile` is useful if you don't want to check into your SCM
- your database credentials.
+ All params are optional, although ``db_module`` must be set - either as keyword argument or in config file.
+ If some of the listed keyword arguments (`db_name`, `db_host` etc.) are not provided (i.e. left on default value `None`),
+ they are normally not passed to the Python DB module at all, except:
+ - _db_port_ - commonly used port number for known databases is set as fallback
+ - _db_charset_ - _UTF8_ is used as fallback for _pymysql_, _pymssql_ and _pyodbc_
+ - _oracle_driver_mode_ - _thin_ is used as fallback for _oracledb_
- Example db.cfg file
- | [default]
- | dbapiModuleName=pymysqlforexample
- | dbName=yourdbname
- | dbUsername=yourusername
- | dbPassword=yourpassword
- | dbHost=yourhost
- | dbPort=yourport
+ Other custom params from keyword arguments and config file are passed to the Python DB module as provided -
+ normally as arguments for the _connect()_ function.
+ However, when using *pyodbc* or *ibm_db_dbi*, the connection is established using a *connection string* -
+ so all the custom params are added into it instead of function arguments.
- Example usage:
- | # explicitly specifies all db property values |
- | Connect To Database | psycopg2 | my_db | postgres | s3cr3t | tiger.foobar.com | 5432 |
+ Set ``alias`` for `Handling multiple database connections`.
+ If the same alias is given twice, then previous connection will be overridden.
- | # loads all property values from default.cfg |
- | Connect To Database | dbConfigFile=default.cfg |
+ The ``oracle_driver_mode`` is used to select the *oracledb* client mode.
+ Allowed values are:
+ - _thin_ (default if omitted)
+ - _thick_
+ - _thick,lib_dir=_
- | # loads all property values from ./resources/db.cfg |
- | Connect To Database |
+ By default, there is a warning when overwriting an existing connection (i.e. not closing it properly).
+ This can be disabled by setting the ``warn_on_connection_overwrite`` parameter to *False* in the library import.
- | # uses explicit `dbapiModuleName` and `dbName` but uses the `dbUsername` and `dbPassword` in 'default.cfg' |
- | Connect To Database | psycopg2 | my_db_test | dbConfigFile=default.cfg |
+ === Some parameters were renamed in version 2.0 ===
+ The old parameters ``dbapiModuleName``, ``dbName``, ``dbUsername``,
+ ``dbPassword``, ``dbHost``, ``dbPort``, ``dbCharset``, ``dbDriver``,
+ ``dbConfigFile`` and ``driverMode`` are *deprecated*,
+ please use new parameters ``db_module``, ``db_name``, ``db_user``,
+ ``db_password``, ``db_host``, ``db_port``, ``db_charset``, ``odbc_driver``,
+ ``config_file`` and ``oracle_driver_mode`` instead.
- | # uses explicit `dbapiModuleName` and `dbName` but uses the `dbUsername` and `dbPassword` in './resources/db.cfg' |
- | Connect To Database | psycopg2 | my_db_test |
+ *The old parameters will be removed in future versions.*
+
+ == Basic examples ==
+ | Connect To Database | psycopg2 | my_db | user | pass | 127.0.0.1 | 5432 |
+ | Connect To Database | psycopg2 | my_db | user | pass | 127.0.0.1 | 5432 | my_custom_param=value |
+ | Connect To Database | psycopg2 | my_db | user | pass | 127.0.0.1 | 5432 | alias=my_alias |
+ | Connect To Database | config_file=my_db_params.cfg |
+
+ See `Connection examples for different DB modules`.
"""
+ config = ConfigReader(config_file, alias)
+
+ def _build_connection_params(custom_params=True, **basic_params):
+ con_params = basic_params.copy()
+ for param_name, param_val in basic_params.items():
+ if param_val is None:
+ con_params.pop(param_name, None)
+ if custom_params:
+ con_params.update(custom_connection_params)
+ con_params.update(other_config_file_params)
+
+ return con_params
+
+ def _log_all_connection_params(*, connection_object=None, connection_string=None, **connection_params):
+ connection_object = connection_object or db_module
+ msg = f"Connect to DB using : {connection_object}.connect("
+ params_separator = ","
+ if connection_string:
+ msg += f'"{connection_string}"'
+ params_separator = ";"
+ for param_name, param_value in connection_params.items():
+ msg += f", {param_name}="
+ if isinstance(param_value, str):
+ msg += f"'{param_value}'"
+ else:
+ msg += f"{param_value}"
+ if db_password:
+ msg = msg.replace(f"'{db_password}'", "***")
+ msg = self._hide_password_values(msg, params_separator)
+ msg = msg.replace("connect(, ", "connect(")
+ msg += ")"
+ logger.info(msg)
+
+ def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=False):
+ val_from_config = config.pop(param_name)
+
+ # support deprecated old param names
+ if val_from_config is None and old_param_name is not None:
+ val_from_config = config.pop(old_param_name)
+ if val_from_config is not None:
+ logger.warn(f"Config file: argument '{old_param_name}' is deprecated, use '{param_name}' instead")
+
+ if arg_value is not None:
+ final_value = arg_value
+ if val_from_config is not None:
+ logger.info(
+ f"Parameter '{param_name}' set both as keyword argument and in config file, "
+ "but keyword arguments take precedence"
+ )
+ else:
+ final_value = val_from_config
+ if final_value is None and mandatory:
+ raise ValueError(
+ f"Required parameter '{param_name}' was not provided - "
+ "neither in keyword arguments nor in config file"
+ )
+ return final_value
+
+ # mandatory parameter
+ db_module = _arg_or_config(db_module, "db_module", mandatory=True, old_param_name="dbapiModuleName")
+ # optional named params - named because of custom module specific handling
+ db_name = _arg_or_config(db_name, "db_name", old_param_name="dbName")
+ db_user = _arg_or_config(db_user, "db_user", old_param_name="dbUsername")
+ db_password = _arg_or_config(db_password, "db_password", old_param_name="dbPassword")
+ db_host = _arg_or_config(db_host, "db_host", old_param_name="dbHost")
+ db_port = _arg_or_config(db_port, "db_port", old_param_name="dbPort")
+ if db_port is not None:
+ db_port = int(db_port)
+ db_charset = _arg_or_config(db_charset, "db_charset", old_param_name="dbCharset")
+ odbc_driver = _arg_or_config(odbc_driver, "odbc_driver", old_param_name="dbDriver")
+ oracle_driver_mode = _arg_or_config(oracle_driver_mode, "oracle_driver_mode", old_param_name="driverMode")
- config = ConfigParser.ConfigParser()
- config.read([dbConfigFile])
+ for param_name, param_value in custom_connection_params.items():
+ _arg_or_config(param_value, param_name)
+ other_config_file_params = config.get_all_available_params()
+ if other_config_file_params:
+ logger.info(f"Other params from configuration file: {list(other_config_file_params.keys())}")
- dbapiModuleName = dbapiModuleName or config.get('default', 'dbapiModuleName')
- dbName = dbName or config.get('default', 'dbName')
- dbUsername = dbUsername or config.get('default', 'dbUsername')
- dbPassword = dbPassword if dbPassword is not None else config.get('default', 'dbPassword')
- dbHost = dbHost or config.get('default', 'dbHost') or 'localhost'
- dbPort = int(dbPort or config.get('default', 'dbPort'))
+ omit_trailing_semicolon = False
- if dbapiModuleName == "excel" or dbapiModuleName == "excelrw":
- self.db_api_module_name = "pyodbc"
- db_api_2 = importlib.import_module("pyodbc")
+ if db_module == "excel" or db_module == "excelrw":
+ db_api_module_name = "pyodbc"
else:
- self.db_api_module_name = dbapiModuleName
- db_api_2 = importlib.import_module(dbapiModuleName)
- if dbapiModuleName in ["MySQLdb", "pymysql"]:
- dbPort = dbPort or 3306
- logger.info('Connecting using : %s.connect(db=%s, user=%s, passwd=%s, host=%s, port=%s, charset=%s) ' % (dbapiModuleName, dbName, dbUsername, dbPassword, dbHost, dbPort, dbCharset))
- self._dbconnection = db_api_2.connect(db=dbName, user=dbUsername, passwd=dbPassword, host=dbHost, port=dbPort, charset=dbCharset)
- elif dbapiModuleName in ["psycopg2"]:
- dbPort = dbPort or 5432
- logger.info('Connecting using : %s.connect(database=%s, user=%s, password=%s, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbPassword, dbHost, dbPort))
- self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort)
- elif dbapiModuleName in ["pyodbc", "pypyodbc"]:
- dbPort = dbPort or 1433
- dbDriver = dbDriver or "{SQL Server}"
- logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword))
- self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=%s' % ( dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword))
- elif dbapiModuleName in ["excel"]:
- logger.info(
- 'Connecting using : %s.connect(DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=%s;ReadOnly=1;Extended Properties="Excel 8.0;HDR=YES";)' % (
- dbapiModuleName, dbName))
- self._dbconnection = db_api_2.connect(
- 'DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=%s;ReadOnly=1;Extended Properties="Excel 8.0;HDR=YES";)' % (
- dbName), autocommit=True)
- elif dbapiModuleName in ["excelrw"]:
- logger.info(
- 'Connecting using : %s.connect(DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=%s;ReadOnly=0;Extended Properties="Excel 8.0;HDR=YES";)' % (
- dbapiModuleName, dbName))
- self._dbconnection = db_api_2.connect(
- 'DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=%s;ReadOnly=0;Extended Properties="Excel 8.0;HDR=YES";)' % (
- dbName), autocommit=True)
- elif dbapiModuleName in ["ibm_db", "ibm_db_dbi"]:
- dbPort = dbPort or 50000
- logger.info('Connecting using : %s.connect(DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=%s;) ' % (dbapiModuleName, dbName, dbHost, dbPort, dbUsername, dbPassword))
- self._dbconnection = db_api_2.connect('DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=%s;' % (dbName, dbHost, dbPort, dbUsername, dbPassword), '', '')
- elif dbapiModuleName in ["cx_Oracle"]:
- dbPort = dbPort or 1521
- oracle_dsn = db_api_2.makedsn(host=dbHost, port=dbPort, service_name=dbName)
- logger.info('Connecting using: %s.connect(user=%s, password=%s, dsn=%s) ' % (dbapiModuleName, dbUsername, dbPassword, oracle_dsn))
- self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, dsn=oracle_dsn)
- elif dbapiModuleName in ["teradata"]:
- dbPort = dbPort or 1025
+ db_api_module_name = db_module
+
+ if db_api_module_name in ["ibm_db", "ibm_db_dbi"]:
+ if os.name == "nt":
+ if not self.ibmdb_driver_already_added_to_path:
+ spec = importlib.util.find_spec(db_api_module_name)
+ if spec is not None:
+ logger.info(
+ f"Importing DB module '{db_api_module_name}' on Windows requires configuring the DLL directory for CLI driver"
+ )
+ site_packages_path = os.path.dirname(spec.origin)
+ clidriver_bin_path = os.path.join(site_packages_path, "clidriver", "bin")
+ if os.path.exists(clidriver_bin_path):
+ os.add_dll_directory(clidriver_bin_path)
+ self.ibmdb_driver_already_added_to_path = True
+ logger.info(f"Added default CLI driver location to DLL search path: '{clidriver_bin_path}'")
+ else:
+ logger.info(f"Default CLI driver location folder not found: '{clidriver_bin_path}'")
+
+ db_api_2 = importlib.import_module(db_api_module_name)
+
+ if db_module in ["MySQLdb", "pymysql"]:
+ db_port = db_port or 3306
+ db_charset = db_charset or "utf8mb4"
+ con_params = _build_connection_params(
+ db=db_name, user=db_user, passwd=db_password, host=db_host, port=db_port, charset=db_charset
+ )
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+
+ elif db_module in ["pymssql"]:
+ db_port = db_port or 1433
+ db_charset = db_charset or "UTF-8"
+ con_params = _build_connection_params(
+ database=db_name, user=db_user, password=db_password, host=db_host, port=db_port, charset=db_charset
+ )
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+
+ elif db_module in ["psycopg2"]:
+ db_port = db_port or 5432
+ con_params = _build_connection_params(
+ database=db_name, user=db_user, password=db_password, host=db_host, port=db_port
+ )
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+
+ elif db_module in ["pyodbc", "pypyodbc"]:
+ db_port = db_port or 1433
+ db_charset = db_charset or "utf8mb4"
+
+ if odbc_driver:
+ con_str = f"DRIVER={odbc_driver};"
+ else:
+ con_str = ""
+ logger.info("No ODBC driver specified")
+ logger.info(f"List of installed ODBC drivers: {db_api_2.drivers()}")
+ if db_name:
+ con_str += f"DATABASE={db_name};"
+ if db_user:
+ con_str += f"UID={db_user};"
+ if db_password:
+ con_str += f"PWD={db_password};"
+ if db_charset:
+ con_str += f"charset={db_charset};"
+ if db_host and db_port:
+ con_str_server = f"SERVER={db_host},{db_port};" # default for most databases
+ if odbc_driver:
+ driver_lower = odbc_driver.lower()
+ if "mysql" in driver_lower:
+ con_str_server = f"SERVER={db_host}:{db_port};"
+ elif "saphana" in driver_lower or "hdbodbc" in driver_lower or "sap hana" in driver_lower:
+ con_str_server = f"SERVERNODE={db_host}:{db_port};"
+ con_str += con_str_server
+
+ for param_name, param_value in custom_connection_params.items():
+ con_str += f"{param_name}={param_value};"
+
+ for param_name, param_value in other_config_file_params.items():
+ con_str += f"{param_name}={param_value};"
+
+ _log_all_connection_params(connection_string=con_str)
+ db_connection = db_api_2.connect(con_str)
+
+ elif db_module in ["excel", "excelrw"]:
+ con_str = f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={db_name};"
+ con_str += "ReadOnly="
+ if db_module == "excel":
+ con_str += "1;"
+ elif db_module == "excelrw":
+ con_str += "0;"
+ con_str += 'Extended Properties="Excel 8.0;HDR=YES";)'
+ logger.info(f"Connecting using : {db_api_module_name}.connect({con_str}, autocommit=True)")
+ db_connection = db_api_2.connect(con_str, autocommit=True)
+
+ elif db_module in ["ibm_db", "ibm_db_dbi"]:
+ db_port = db_port or 50000
+ con_str = ""
+ if db_name:
+ con_str += f"DATABASE={db_name};"
+ if db_user:
+ con_str += f"UID={db_user};"
+ if db_password:
+ con_str += f"PWD={db_password};"
+ if db_host:
+ con_str += f"HOSTNAME={db_host};"
+ if db_port:
+ con_str += f"PORT={db_port};"
+
+ for param_name, param_value in custom_connection_params.items():
+ con_str += f"{param_name}={param_value};"
+
+ for param_name, param_value in other_config_file_params.items():
+ con_str += f"{param_name}={param_value};"
+
+ con_params = _build_connection_params(custom_params=False, user="", password="")
+ _log_all_connection_params(connection_string=con_str, **con_params)
+ db_connection = db_api_2.connect(con_str, **con_params)
+
+ elif db_module in ["cx_Oracle"]:
+ db_port = db_port or 1521
+ oracle_dsn = db_api_2.makedsn(host=db_host, port=db_port, service_name=db_name)
+ con_params = _build_connection_params(user=db_user, password=db_password, dsn=oracle_dsn)
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+ omit_trailing_semicolon = True
+
+ elif db_module in ["oracledb"]:
+ db_port = db_port or 1521
+ oracle_driver_mode = oracle_driver_mode or "thin"
+ oracle_connection_params = db_api_2.ConnectParams(host=db_host, port=db_port, service_name=db_name)
+ if "thick" in oracle_driver_mode.lower():
+ logger.info("Using thick Oracle client mode")
+ mode_param = oracle_driver_mode.lower().split(",lib_dir=")
+ if len(mode_param) == 2 and mode_param[0].lower() == "thick":
+ lib_dir = mode_param[1]
+ logger.info(f"Oracle client lib dir specified: {lib_dir}")
+ db_api_2.init_oracle_client(lib_dir=lib_dir)
+ else:
+ logger.info("No Oracle client lib dir specified, oracledb will search it in usual places")
+ db_api_2.init_oracle_client()
+ oracle_thin_mode = False
+ elif "thin" in oracle_driver_mode.lower():
+ oracle_thin_mode = True
+ logger.info("Using thin Oracle client mode")
+ else:
+ raise ValueError(f"Invalid Oracle client mode provided: {oracle_driver_mode}")
+ con_params = _build_connection_params(user=db_user, password=db_password, params=oracle_connection_params)
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+ assert db_connection.thin == oracle_thin_mode, (
+ f"Expected oracledb to run in thin mode: {oracle_thin_mode}, "
+ f"but the connection has thin mode: {db_connection.thin}"
+ )
+ omit_trailing_semicolon = True
+
+ elif db_module in ["teradata"]:
+ db_port = db_port or 1025
teradata_udaExec = db_api_2.UdaExec(appName="RobotFramework", version="1.0", logConsole=False)
- logger.info('Connecting using : %s.connect(database=%s, user=%s, password=%s, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbPassword, dbHost, dbPort))
- self._dbconnection = teradata_udaExec.connect(
+ con_params = _build_connection_params(
method="odbc",
- system=dbHost,
- database=dbName,
- username=dbUsername,
- password=dbPassword,
- host=dbHost,
- port=dbPort
+ system=db_host,
+ database=db_name,
+ username=db_user,
+ password=db_password,
+ host=db_host,
+ port=db_port,
)
- elif dbapiModuleName in ["ksycopg2"]:
- dbPort = dbPort or 54321
- logger.info('Connecting using : %s.connect(database=%s, user=%s, password=%s, host=%s, port=%s) ' % (
- dbapiModuleName, dbName, dbUsername, dbPassword, dbHost, dbPort))
- self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost,
- port=dbPort)
+ _log_all_connection_params(connection_object=f"{db_module}.UdaExec", **con_params)
+ db_connection = teradata_udaExec.connect(**con_params)
+
+ elif db_module in ["ksycopg2"]:
+ db_port = db_port or 54321
+ con_params = _build_connection_params(
+ database=db_name, user=db_user, password=db_password, host=db_host, port=db_port
+ )
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+
else:
- logger.info('Connecting using : %s.connect(database=%s, user=%s, password=%s, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbPassword, dbHost, dbPort))
- self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort)
+ con_params = _build_connection_params(
+ database=db_name, user=db_user, password=db_password, host=db_host, port=db_port
+ )
+ _log_all_connection_params(**con_params)
+ db_connection = db_api_2.connect(**con_params)
+
+ self.connection_store.register_connection(db_connection, db_api_module_name, alias, omit_trailing_semicolon)
- def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=''):
+ @renamed_args(mapping={"dbapiModuleName": "db_module"})
+ def connect_to_database_using_custom_params(
+ self,
+ db_module: Optional[str] = None,
+ db_connect_string: str = "",
+ alias: str = "default",
+ *,
+ dbapiModuleName: Optional[str] = None,
+ ):
"""
- Loads the DB API 2.0 module given `dbapiModuleName` then uses it to
- connect to the database using the map string `db_custom_param_string`.
+ *DEPRECATED* Use new `Connect To Database` keyword with custom parameters instead.
+ The deprecated keyword will be removed in future versions.
- Example usage:
- | # for psycopg2 |
- | Connect To Database Using Custom Params | psycopg2 | database='my_db_test', user='postgres', password='s3cr3t', host='tiger.foobar.com', port=5432 |
+ Loads the DB API 2.0 module given ``db_module`` then uses it to
+ connect to the database using the map string ``db_connect_string``
+ (parsed as a list of named arguments).
+
+ Use `connect_to_database_using_custom_connection_string` for passing
+ all params in a single connection string or URI.
+
+ === Some parameters were renamed in version 2.0 ===
+ The old parameter ``dbapiModuleName`` is *deprecated*,
+ please use new parameter ``db_module`` instead.
- | # for JayDeBeApi |
+ *The old parameter will be removed in future versions.*
+
+ === Examples ===
+ | Connect To Database Using Custom Params | psycopg2 | database='my_db_test', user='postgres', password='s3cr3t', host='tiger.foobar.com', port=5432 |
| Connect To Database Using Custom Params | jaydebeapi | 'oracle.jdbc.driver.OracleDriver', 'my_db_test', 'system', 's3cr3t' |
+ | Connect To Database Using Custom Params | oracledb | user="username", password="pass", dsn="localhost/orclpdb" |
+ | Connect To Database Using Custom Params | sqlite3 | database="./my_database.db", isolation_level=None |
+ """
+ db_api_2 = importlib.import_module(db_module)
+ db_api_module_name = db_module
+ db_connect_string = f"db_api_2.connect({db_connect_string})"
+ logger.info(
+ f"Executing : Connect To Database Using Custom Params : {db_module}.connect("
+ f"{self._hide_password_values(db_connect_string)})"
+ )
+
+ db_connection = eval(db_connect_string)
+ self.connection_store.register_connection(db_connection, db_api_module_name, alias)
+
+ @renamed_args(mapping={"dbapiModuleName": "db_module"})
+ def connect_to_database_using_custom_connection_string(
+ self,
+ db_module: Optional[str] = None,
+ db_connect_string: str = "",
+ alias: str = "default",
+ *,
+ dbapiModuleName: Optional[str] = None,
+ ):
"""
- db_api_2 = importlib.import_module(dbapiModuleName)
+ Loads the DB API 2.0 module given ``db_module`` then uses it to
+ connect to the database using the ``db_connect_string``
+ (parsed as single connection string or URI).
+
+ Use `Connect To Database` for passing custom connection params as named arguments.
- db_connect_string = 'db_api_2.connect(%s)' % db_connect_string
+ === Some parameters were renamed in version 2.0 ===
+ The old parameter ``dbapiModuleName`` is *deprecated*,
+ please use new parameter ``db_module`` instead.
- self.db_api_module_name = dbapiModuleName
- logger.info('Executing : Connect To Database Using Custom Params : %s.connect(%s) ' % (dbapiModuleName, db_connect_string))
- self._dbconnection = eval(db_connect_string)
+ *The old parameter will be removed in future versions.*
- def disconnect_from_database(self):
+ Example usage:
+ | Connect To Database Using Custom Connection String | psycopg2 | postgresql://postgres:s3cr3t@tiger.foobar.com:5432/my_db_test |
+ | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb |
+ """
+ db_api_2 = importlib.import_module(db_module)
+ db_api_module_name = db_module
+ logger.info(
+ f"Executing : Connect To Database Using Custom Connection String : {db_module}.connect("
+ f"'{db_connect_string}')"
+ )
+ db_connection = db_api_2.connect(db_connect_string)
+ self.connection_store.register_connection(db_connection, db_api_module_name, alias)
+
+ def disconnect_from_database(self, error_if_no_connection: bool = False, alias: Optional[str] = None):
"""
Disconnects from the database.
- For example:
- | Disconnect From Database | # disconnects from current connection to the database |
+ By default, it's not an error if there was no open database connection -
+ suitable for usage as a teardown.
+ However, you can enforce it using the ``error_if_no_connection`` parameter.
+
+ Use ``alias`` to specify what connection should be closed if `Handling multiple database connections`.
+
+ === Examples ===
+ | Disconnect From Database |
+ | Disconnect From Database | alias=postgres |
"""
- logger.info('Executing : Disconnect From Database')
- if self._dbconnection==None:
- return 'No open connection to close'
+ db_connection = self.connection_store.pop_connection(alias)
+ if db_connection is None:
+ log_msg = "No open database connection to close"
+ if error_if_no_connection:
+ raise ConnectionError(log_msg) from None
+ logger.info(log_msg)
else:
- self._dbconnection.close()
-
- def set_auto_commit(self, autoCommit=True):
- """
- Turn the autocommit on the database connection ON or OFF.
-
- The default behaviour on a newly created database connection is to automatically start a
- transaction, which means that database actions that won't work if there is an active
- transaction will fail. Common examples of these actions are creating or deleting a database
- or database snapshot. By turning on auto commit on the database connection these actions
- can be performed.
-
- Example:
- | # Default behaviour, sets auto commit to true
+ db_connection.client.close()
+
+ def disconnect_from_all_databases(self):
+ """
+ Disconnects from all the databases -
+ useful when testing with multiple database connections (aliases).
+ """
+ for db_connection in self.connection_store:
+ db_connection.client.close()
+ self.connection_store.clear()
+
+ @renamed_args(mapping={"autoCommit": "auto_commit"})
+ def set_auto_commit(
+ self, auto_commit: bool = True, alias: Optional[str] = None, *, autoCommit: Optional[bool] = None
+ ):
+ """
+ Explicitly sets the autocommit behavior of the database connection to ``auto_commit``.
+ See `Commit behavior` for details.
+
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
+
+ === Some parameters were renamed in version 2.0 ===
+ The old parameter ``autoCommit`` is *deprecated*,
+ please use new parameter ``auto_commit`` instead.
+
+ *The old parameter will be removed in future versions.*
+
+ === Examples ===
| Set Auto Commit
- | # Explicitly set the desired state
- | Set Auto Commit | False
+ | Set Auto Commit | False |
+ | Set Auto Commit | True | alias=postgres |
+ """
+ db_connection = self.connection_store.get_connection(alias)
+ if db_connection.module_name == "jaydebeapi":
+ db_connection.client.jconn.setAutoCommit(auto_commit)
+ elif db_connection.module_name in ["ibm_db", "ibm_db_dbi"]:
+ raise ValueError(f"Setting autocommit for {db_connection.module_name} is not supported")
+ else:
+ db_connection.client.autocommit = auto_commit
+
+ def switch_database(self, alias: str):
+ """
+ Switch the default database connection to ``alias``.
+
+ Examples:
+ | Switch Database | my_alias |
+ | Switch Database | alias=my_alias |
+ """
+ self.connection_store.switch(alias)
+
+ def set_omit_trailing_semicolon(self, omit_trailing_semicolon=True, alias: Optional[str] = None):
+ """
+ Set the ``omit_trailing_semicolon`` to control the `Omitting trailing semicolon behavior` for the connection.
+
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
+
+ Examples:
+ | Set Omit Trailing Semicolon | True |
+ | Set Omit Trailing Semicolon | False | alias=my_alias |
"""
- logger.info('Executing : Set Auto Commit')
- self._dbconnection.autocommit = autoCommit
+ db_connection = self.connection_store.get_connection(alias)
+ db_connection.omit_trailing_semicolon = omit_trailing_semicolon
diff --git a/src/DatabaseLibrary/params_decorator.py b/src/DatabaseLibrary/params_decorator.py
new file mode 100644
index 00000000..b122b3f9
--- /dev/null
+++ b/src/DatabaseLibrary/params_decorator.py
@@ -0,0 +1,33 @@
+"""
+These decorators are introduced for the transition from old argument naming / positioning to the new one.
+"""
+from functools import wraps
+
+from robot.api import logger
+
+
+def renamed_args(mapping):
+ """
+ Decorator to rename arguments and warn users about deprecated argument names.
+
+ :param mapping: Dictionary mapping old argument names to new argument names.
+ :return: The decorated function with remapped arguments.
+ """
+
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ # Check if any old argument names are used
+ for old_name, new_name in mapping.items():
+ if old_name in kwargs:
+ # Issue a warning to the user
+ logger.warn(f"Argument '{old_name}' is deprecated, use '{new_name}' instead")
+ # Move the argument value to the new name
+ logger.info(f"Replacing '{old_name}' with '{new_name}'")
+ kwargs[new_name] = kwargs.pop(old_name)
+ # Call the original function with updated kwargs
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return decorator
diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py
index 289d0482..36492030 100644
--- a/src/DatabaseLibrary/query.py
+++ b/src/DatabaseLibrary/query.py
@@ -12,360 +12,827 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import importlib
+import inspect
+import re
import sys
+from typing import List, Optional, Tuple
+
from robot.api import logger
+from robot.utils.dotdict import DotDict
+
+from .connection_manager import Connection
+from .params_decorator import renamed_args
-class Query(object):
+class Query:
"""
Query handles all the querying done by the Database Library.
"""
- def query(self, selectStatement, sansTran=False, returnAsDict=False):
+ def __init__(self, log_query_results, log_query_results_head):
+ self.LOG_QUERY_RESULTS = log_query_results
+ self.LOG_QUERY_RESULTS_HEAD = log_query_results_head
+
+ @renamed_args(
+ mapping={"selectStatement": "select_statement", "sansTran": "no_transaction", "returnAsDict": "return_dict"}
+ )
+ def query(
+ self,
+ select_statement: str,
+ no_transaction: bool = False,
+ return_dict: bool = False,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ *,
+ selectStatement: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ returnAsDict: Optional[bool] = None,
+ ):
"""
- Uses the input `selectStatement` to query for the values that will be returned as a list of tuples. Set optional
- input `sansTran` to True to run command without an explicit transaction commit or rollback.
- Set optional input `returnAsDict` to True to return values as a list of dictionaries.
+ Runs a query with the ``select_statement`` and returns the result as list of rows.
+ The type of row values depends on the database module -
+ usually they are tuples or tuple-like objects.
- Tip: Unless you want to log all column values of the specified rows,
- try specifying the column names in your select statements
- as much as possible to prevent any unnecessary surprises with schema
- changes and to easily see what your [] indexing is trying to retrieve
- (i.e. instead of `"select * from my_table"`, try
- `"select id, col_1, col_2 from my_table"`).
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit or rollback in case of error.
+ See `Commit behavior` for details.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
+ Set ``return_dict`` to _True_ to explicitly convert the return values into list of dictionaries.
- When you do the following:
- | @{queryResults} | Query | SELECT * FROM person |
- | Log Many | @{queryResults} |
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
- You will get the following:
- [1, 'Franz Allan', 'See']
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- Also, you can do something like this:
- | ${queryResults} | Query | SELECT first_name, last_name FROM person |
- | Log | ${queryResults[0][1]}, ${queryResults[0][0]} |
+ === 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.
- And get the following
- See, Franz Allan
+ *The old parameters will be removed in future versions.*
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | @{queryResults} | Query | SELECT * FROM person | True |
+ === Examples ===
+ | ${Results}= | Query | select LAST_NAME from person |
+ | ${Results}= | Query | select LAST_NAME from person | no_transaction=True |
+ | ${Results}= | Query | select LAST_NAME from person | return_dict=True |
+ | ${Results}= | Query | select LAST_NAME from person | alias=postgres |
+ | @{parameters} | Create List | person |
+ | ${Results}= | Query | SELECT * FROM %s | parameters=${parameters} |
"""
+ db_connection = self.connection_store.get_connection(alias)
cur = None
try:
- cur = self._dbconnection.cursor()
- logger.info('Executing : Query | %s ' % selectStatement)
- self.__execute_sql(cur, selectStatement)
- allRows = cur.fetchall()
-
- if returnAsDict:
- mappedRows = []
- col_names = [c[0] for c in cur.description]
-
- for rowIdx in range(len(allRows)):
- d = {}
- for colIdx in range(len(allRows[rowIdx])):
- d[col_names[colIdx]] = allRows[rowIdx][colIdx]
- mappedRows.append(d)
- return mappedRows
-
- return allRows
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
-
- def row_count(self, selectStatement, sansTran=False):
+ cur = db_connection.client.cursor()
+ self._execute_sql(
+ cur,
+ select_statement,
+ parameters=parameters,
+ omit_trailing_semicolon=db_connection.omit_trailing_semicolon,
+ )
+ all_rows = cur.fetchall()
+ self._commit_if_needed(db_connection, no_transaction)
+ col_names = [c[0] for c in cur.description]
+ self._log_query_results(col_names, all_rows)
+ if return_dict:
+ return [DotDict(zip(col_names, row)) for row in all_rows]
+ return all_rows
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"})
+ def row_count(
+ self,
+ select_statement: str,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ *,
+ selectStatement: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ ):
"""
- Uses the input `selectStatement` to query the database and returns the number of rows from the query. Set
- optional input `sansTran` to True to run command without an explicit transaction commit or rollback.
+ Runs a query with the ``select_statement`` and returns the number of rows in the result.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
- | 2 | Jerry | Schneider |
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit or rollback in case of error.
+ See `Commit behavior` for details.
- When you do the following:
- | ${rowCount} | Row Count | SELECT * FROM person |
- | Log | ${rowCount} |
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
- You will get the following:
- 2
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- Also, you can do something like this:
- | ${rowCount} | Row Count | SELECT * FROM person WHERE id = 2 |
- | Log | ${rowCount} |
+ === 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.
- And get the following
- 1
+ *The old parameters will be removed in future versions.*
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | ${rowCount} | Row Count | SELECT * FROM person | True |
+ === Examples ===
+ | ${Rows}= | Row Count | select LAST_NAME from person |
+ | ${Rows}= | Row Count | select LAST_NAME from person | no_transaction=True |
+ | ${Rows}= | Row Count | select LAST_NAME from person | alias=postgres |
+ | @{parameters} | Create List | person |
+ | ${Rows}= | Row Count | SELECT * FROM %s | parameters=${parameters} |
"""
+ db_connection = self.connection_store.get_connection(alias)
cur = None
try:
- cur = self._dbconnection.cursor()
- logger.info('Executing : Row Count | %s ' % selectStatement)
- self.__execute_sql(cur, selectStatement)
+ cur = db_connection.client.cursor()
+ self._execute_sql(
+ cur,
+ select_statement,
+ parameters=parameters,
+ omit_trailing_semicolon=db_connection.omit_trailing_semicolon,
+ )
data = cur.fetchall()
- if self.db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]:
- rowCount = len(data)
+ self._commit_if_needed(db_connection, no_transaction)
+ col_names = [c[0] for c in cur.description]
+ if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc", "jaydebeapi"]:
+ current_row_count = len(data)
else:
- rowCount = cur.rowcount
- return rowCount
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
-
- def description(self, selectStatement, sansTran=False):
+ current_row_count = cur.rowcount
+ logger.info(f"Retrieved {current_row_count} rows")
+ self._log_query_results(col_names, data)
+ return current_row_count
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"})
+ def description(
+ self,
+ select_statement: str,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ *,
+ selectStatement: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ ):
"""
- Uses the input `selectStatement` to query a table in the db which will be used to determine the description. Set
- optional input `sansTran` to True to run command without an explicit transaction commit or rollback.
+ Runs a query with the ``select_statement`` to determine the table description.
+
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit or rollback in case of error.
+ See `Commit behavior` for details.
- For example, given we have a table `person` with the following data:
- | id | first_name | last_name |
- | 1 | Franz Allan | See |
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
- When you do the following:
- | @{queryResults} | Description | SELECT * FROM person |
- | Log Many | @{queryResults} |
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- You will get the following:
- [Column(name='id', type_code=1043, display_size=None, internal_size=255, precision=None, scale=None, null_ok=None)]
- [Column(name='first_name', type_code=1043, display_size=None, internal_size=255, precision=None, scale=None, null_ok=None)]
- [Column(name='last_name', type_code=1043, display_size=None, internal_size=255, precision=None, scale=None, null_ok=None)]
+ === 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.
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | @{queryResults} | Description | SELECT * FROM person | True |
+ *The old parameters will be removed in future versions.*
+
+ === Examples ===
+ | ${Person table description}= | Description | select LAST_NAME from person |
+ | ${Person table description}= | Description | select LAST_NAME from person | no_transaction=True |
+ | ${Person table description}= | Description | select LAST_NAME from person | alias=postgres |
+ | @{parameters} | Create List | person |
+ | ${Person table description}= | Description | SELECT * FROM %s | parameters=${parameters} |
"""
+ db_connection = self.connection_store.get_connection(alias)
cur = None
try:
- cur = self._dbconnection.cursor()
- logger.info('Executing : Description | %s ' % selectStatement)
- self.__execute_sql(cur, selectStatement)
+ cur = db_connection.client.cursor()
+ self._execute_sql(
+ cur,
+ select_statement,
+ parameters=parameters,
+ omit_trailing_semicolon=db_connection.omit_trailing_semicolon,
+ )
+ self._commit_if_needed(db_connection, no_transaction)
description = list(cur.description)
if sys.version_info[0] < 3:
for row in range(0, len(description)):
- description[row] = (description[row][0].encode('utf-8'),) + description[row][1:]
+ description[row] = (description[row][0].encode("utf-8"),) + description[row][1:]
return description
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
-
- def delete_all_rows_from_table(self, tableName, sansTran=False):
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ @renamed_args(mapping={"tableName": "table_name", "sansTran": "no_transaction"})
+ def delete_all_rows_from_table(
+ self,
+ table_name: str,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ *,
+ tableName: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ ):
"""
- Delete all the rows within a given table. Set optional input `sansTran` to True to run command without an
- explicit transaction commit or rollback.
+ Deletes all rows from table with ``table_name``.
- For example, given we have a table `person` in a database
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit
+ or rollback in case of error.
+ See `Commit behavior` for details.
- When you do the following:
- | Delete All Rows From Table | person |
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
+
+ === Some parameters were renamed in version 2.0 ===
+ The old parameters ``tableName`` and ``sansTran`` are *deprecated*,
+ please use new parameters ``table_name`` and ``no_transaction`` instead.
- If all the rows can be successfully deleted, then you will get:
- | Delete All Rows From Table | person | # PASS |
- If the table doesn't exist or all the data can't be deleted, then you
- will get:
- | Delete All Rows From Table | first_name | # FAIL |
+ *The old parameters will be removed in future versions.*
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Delete All Rows From Table | person | True |
+ === Examples ===
+ | Delete All Rows From Table | person |
+ | Delete All Rows From Table | person | no_transaction=True |
+ | Delete All Rows From Table | person | alias=my_alias |
"""
+ db_connection = self.connection_store.get_connection(alias)
cur = None
- selectStatement = ("DELETE FROM %s;" % tableName)
+ query = f"DELETE FROM {table_name}"
try:
- cur = self._dbconnection.cursor()
- logger.info('Executing : Delete All Rows From Table | %s ' % selectStatement)
- result = self.__execute_sql(cur, selectStatement)
+ cur = db_connection.client.cursor()
+ result = self._execute_sql(cur, query)
+ self._commit_if_needed(db_connection, no_transaction)
if result is not None:
- if not sansTran:
- self._dbconnection.commit()
return result
- if not sansTran:
- self._dbconnection.commit()
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
-
- def execute_sql_script(self, sqlScriptFileName, sansTran=False):
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ @renamed_args(mapping={"sqlScriptFileName": "script_path", "sansTran": "no_transaction"})
+ def execute_sql_script(
+ self,
+ script_path: str,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ split: bool = True,
+ *,
+ sqlScriptFileName: Optional[str] = None,
+ sansTran: Optional[bool] = 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. Set optional input
- `sansTran` to True to run command without an explicit transaction commit or rollback.
-
- Sample usage :
- | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql |
- | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-setup.sql |
- | #interesting stuff here |
- | 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 semi-colon (';').
-
- For example:
- DELETE FROM person_employee_table;
- DELETE FROM person_table;
- DELETE FROM employee_table;
-
- Also, the last SQL command can optionally omit its trailing semi-colon.
-
- For example:
- DELETE FROM person_employee_table;
- DELETE FROM person_table;
- DELETE FROM employee_table
-
- Given this, that means you can create spread your SQL commands in several
- lines.
-
- For example:
- DELETE
- FROM person_employee_table;
- DELETE
- FROM person_table;
- DELETE
- FROM employee_table
-
- However, lines that starts with a number sign (`#`) are treated as a
- commented line. Thus, none of the contents of that line will be executed.
-
- For example:
- # Delete the bridging table first...
- DELETE
- FROM person_employee_table;
- # ...and then the bridged tables.
- DELETE
- FROM person_table;
- DELETE
- FROM employee_table
-
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True |
- """
- sqlScriptFile = open(sqlScriptFileName ,encoding='UTF-8')
+ Executes the content of the SQL script file loaded from `script_path` as SQL commands.
- cur = None
- try:
- cur = self._dbconnection.cursor()
- logger.info('Executing : Execute SQL Script | %s ' % sqlScriptFileName)
- sqlStatement = ''
- for line in sqlScriptFile:
- PY3K = sys.version_info >= (3, 0)
- if not PY3K:
- #spName = spName.encode('ascii', 'ignore')
- line = line.strip().decode("utf-8")
- if line.startswith('#'):
- continue
- elif line.startswith('--'):
- continue
-
- sqlFragments = line.split(';')
- if len(sqlFragments) == 1:
- sqlStatement += line + ' '
- else:
- for sqlFragment in sqlFragments:
- sqlFragment = sqlFragment.strip()
- if len(sqlFragment) == 0:
- continue
+ SQL commands are expected to be delimited by a semicolon (';') - they will be split and executed separately.
+ Set ``split`` to _False_ to disable this behavior - in this case the entire script content
+ will be passed to the database module for execution as a single command.
- sqlStatement += sqlFragment + ' '
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit
+ or rollback in case of error.
+ See `Commit behavior` for details.
- self.__execute_sql(cur, sqlStatement)
- sqlStatement = ''
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
- sqlStatement = sqlStatement.strip()
- if len(sqlStatement) != 0:
- self.__execute_sql(cur, sqlStatement)
+ === 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.
- if not sansTran:
- self._dbconnection.commit()
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
+ *The old parameters will be removed in future versions.*
- def execute_sql_string(self, sqlString, sansTran=False):
+ === Examples ===
+ | Execute SQL Script | insert_data_in_person_table.sql |
+ | Execute SQL Script | insert_data_in_person_table.sql | no_transaction=True |
+ | Execute SQL Script | insert_data_in_person_table.sql | alias=postgres |
+ | Execute SQL Script | insert_data_in_person_table.sql | split=False |
"""
- Executes the sqlString as SQL commands. Useful to pass arguments to your sql. Set optional input `sansTran` to
- True to run command without an explicit transaction commit or rollback.
+ db_connection = self.connection_store.get_connection(alias)
+ with open(script_path, encoding="UTF-8") as sql_file:
+ cur = None
+ try:
+ cur = db_connection.client.cursor()
+ if not split:
+ logger.info("Statements splitting disabled - pass entire script content to the database module")
+ self._execute_sql(
+ cur,
+ sql_file.read(),
+ omit_trailing_semicolon=db_connection.omit_trailing_semicolon,
+ )
+ 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
- SQL commands are expected to be delimited by a semi-colon (';').
+ # check if the line matches the creating procedure regexp pattern
+ if 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:
+ 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:
+ 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._commit_if_needed(db_connection, no_transaction)
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ @renamed_args(
+ mapping={
+ "sqlString": "sql_string",
+ "sansTran": "no_transaction",
+ "omitTrailingSemicolon": "omit_trailing_semicolon",
+ }
+ )
+ def execute_sql_string(
+ self,
+ sql_string: str,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ parameters: Optional[Tuple] = None,
+ omit_trailing_semicolon: Optional[bool] = None,
+ *,
+ sqlString: Optional[str] = None,
+ sansTran: Optional[bool] = None,
+ omitTrailingSemicolon: Optional[bool] = None,
+ ):
+ """
+ Executes the ``sql_string`` as a single SQL command.
- For example:
- | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table |
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit
+ or rollback in case of error.
+ See `Commit behavior` for details.
- For example with an argument:
- | Execute Sql String | SELECT * FROM person WHERE first_name = ${FIRSTNAME} |
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | True |
- """
- cur = None
- try:
- cur = self._dbconnection.cursor()
- logger.info('Executing : Execute SQL String | %s ' % sqlString)
- self.__execute_sql(cur, sqlString)
- if not sansTran:
- self._dbconnection.commit()
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
-
- def call_stored_procedure(self, spName, spParams=None, sansTran=False):
- """
- Uses the inputs of `spName` and 'spParams' to call a stored procedure. Set optional input `sansTran` to
- True to run command without an explicit transaction commit or rollback.
-
- spName should be the stored procedure name itself
- spParams [Optional] should be a List of the parameters being sent in. The list can be one or multiple items.
+ Use ``parameters`` for query variable substitution (variable substitution syntax may be different
+ depending on the database client).
- The return from this keyword will always be a list.
+ Set the ``omit_trailing_semicolon`` to explicitly control the `Omitting trailing semicolon behavior` for the command.
- Example:
- | @{ParamList} = | Create List | FirstParam | SecondParam | ThirdParam |
- | @{QueryResults} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | List of Parameters |
+ === Some parameters were renamed in version 2.0 ===
+ The old parameters ``sqlString``, ``sansTran`` and ``omitTrailingSemicolon`` are *deprecated*,
+ please use new parameters ``sql_string``, ``no_transaction`` and ``omit_trailing_semicolon`` instead.
- Example:
- | @{ParamList} = | Create List | Testing | LastName |
- | Set Test Variable | ${SPName} = | DBTest.DBSchema.MyStoredProc |
- | @{QueryResults} = | Call Stored Procedure | ${SPName} | ${ParamList} |
- | Log List | @{QueryResults} |
+ *The old parameters will be removed in future versions.*
- Using optional `sansTran` to run command without an explicit transaction commit or rollback:
- | @{QueryResults} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | List of Parameters | True |
+ === Examples ===
+ | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table |
+ | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | no_transaction=True |
+ | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias |
+ | Execute Sql String | CREATE PROCEDURE proc AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; | omit_trailing_semicolon=False |
+ | @{parameters} | Create List | person_employee_table |
+ | Execute Sql String | DELETE FROM %s | parameters=${parameters} |
+ """
+ db_connection = self.connection_store.get_connection(alias)
+ cur = None
+ try:
+ 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._commit_if_needed(db_connection, no_transaction)
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ @renamed_args(mapping={"spName": "procedure_name", "spParams": "procedure_params", "sansTran": "no_transaction"})
+ def call_stored_procedure(
+ self,
+ procedure_name: str,
+ procedure_params: Optional[List] = None,
+ no_transaction: bool = False,
+ alias: Optional[str] = None,
+ additional_output_params: Optional[List] = None,
+ *,
+ spName: Optional[str] = None,
+ spParams: Optional[List] = None,
+ sansTran: Optional[bool] = None,
+ ):
+ """
+ Calls a stored procedure `procedure_name` with the `procedure_params` - a *list* of parameters the procedure requires.
+ *Returns two lists* - the _parameter values_ and the _result sets_.
+
+ Use the special *CURSOR* value for OUT params, which should receive result sets - relevant only for some databases (e.g. Oracle or PostgreSQL).
+
+ Set ``no_transaction`` to _True_ to run command without explicit transaction commit
+ or rollback in case of error.
+ See `Commit behavior` for details.
+
+ Use ``alias`` to specify what connection should be used if `Handling multiple database connections`.
+
+ Use the ``additional_output_params`` list for OUT params of a procedure in MSSQL.
+
+ === Some parameters were renamed in version 2.0 ===
+ The old parameters ``spName``, ``spParams`` and ``sansTran`` are *deprecated*, please use
+ new parameters ``procedure_name``, ``procedure_params`` and ``no_transaction`` instead.
+
+ *The old parameters will be removed in future versions.*
+
+ = Handling parameters and result sets =
+ Handling the input and output parameters and the result sets is very different
+ depending on the database itself and on the Python database driver - i.e. how it implements the `cursor.callproc()` function.
+
+ == Common case (e.g. MySQL) ==
+ Generally a procedure call requires all parameter values (IN and OUT) put together in a list - `procedure_params`.
+
+ Calling the procedure returns *two lists*:
+ - *Param values* - the copy of procedure parameters (modified, if the procedure changes the OUT params). The list is empty, if procedures receives no params.
+ - *Result sets* - the list of lists, each of them containing results of some query, if the procedure returns them.
+
+ == Oracle (oracledb, cx_Oracle) ==
+ Oracle procedures work fine with simple IN and OUT params, but require some special handling of result sets.
+
+ === Simple case with IN and OUT params (no result sets) ===
+ Consider the following procedure:
+ | CREATE OR REPLACE PROCEDURE
+ | get_second_name (person_first_name IN VARCHAR, person_second_name OUT VARCHAR) AS
+ | BEGIN
+ | SELECT last_name
+ | INTO person_second_name
+ | FROM person
+ | WHERE first_name = person_first_name;
+ | END;
+
+ Calling the procedure in Robot Framework:
+ | @{params}= Create List Jerry OUTPUT
+ | # Second parameter value can be anything, it will be replaced anyway
+ |
+ | ${param values} ${result sets}= Call Stored Procedure get_second_name ${params}
+ | # ${param values} = ['Jerry', 'Schneider']
+ | # ${result sets} = []
+
+ === Oracle procedure returning a result set ===
+ If a procedure in Oracle should return a result set, it must take OUT parameters of a special type -
+ _SYS_REFCURSOR_.
+
+ Consider the following procedure:
+ | get_all_second_names (second_names_cursor OUT SYS_REFCURSOR) AS
+ | BEGIN
+ | OPEN second_names_cursor for
+ | SELECT LAST_NAME FROM person;
+ | END;
+
+ Calling the procedure in Robot Framework requires the special value *CURSOR* for the OUT parameters,
+ they will be converted to appropriate DB variables before calling the procedure.
+ | @{params}= Create List CURSOR
+ | # The parameter must have this special value CURSOR
+ |
+ | ${param values} ${result sets}= Call Stored Procedure get_all_second_names ${params}
+ | # ${param values} = [>]
+ | # ${result sets} = [[('Franz Allan',), ('Jerry',)], [('See',), ('Schneider',)]]
+
+ === Oracle procedure returning multiple result sets ===
+ If a procedure takes multiple OUT parameters of the _SYS_REFCURSOR_ type, they all must have
+ the special *CURSOR* value when calling the procedure:
+ | @{params} = Create List CURSOR CURSOR
+ | ${param values} ${result sets} = Call Stored Procedure Get_all_first_and_second_names ${params}
+ | # ${param values} = [>, >]
+ | # ${result sets} = [[('Franz Allan',), ('Jerry',)], [('See',), ('Schneider',)]]
+
+ == PostgreSQL (psycopg2, psycopg3) ==
+ PostgreSQL doesn't return single values as params, only as result sets.
+ It also supports special handling of result sets over OUT params of a special type (like Oracle).
+
+ === Simple case with IN and OUT params (no CURSOR parameters) ===
+ Consider the following procedure:
+ | CREATE FUNCTION
+ | get_second_name (IN person_first_name VARCHAR(20),
+ | OUT person_second_name VARCHAR(20))
+ | LANGUAGE plpgsql
+ | AS
+ | '
+ | BEGIN
+ | SELECT LAST_NAME INTO person_second_name
+ | FROM person
+ | WHERE FIRST_NAME = person_first_name;
+ | END
+ | ';
+
+ Calling the procedure in Robot Framework:
+ | @{params}= Create List Jerry
+ | ${param values} ${result sets}= Call Stored Procedure get_second_name ${params}
+ | # ${param values} = ['Jerry']
+ | # ${result sets} = [[('Schneider',)]]
+
+ === PostgreSQL procedure with CURSOR parameters ===
+ If a procedure in PostgreSQL should return a proper result set, it must take OUT parameters of a special type -
+ _refcursor_.
+
+ Consider the following procedure:
+ | CREATE FUNCTION
+ | get_all_first_and_second_names(result1 refcursor, result2 refcursor)
+ | RETURNS SETOF refcursor
+ | LANGUAGE plpgsql
+ | AS
+ | '
+ | BEGIN
+ | OPEN result1 FOR SELECT FIRST_NAME FROM person;
+ | RETURN NEXT result1;
+ | OPEN result2 FOR SELECT LAST_NAME FROM person;
+ | RETURN NEXT result2;
+ | END
+ | ';
+
+ Calling the procedure in Robot Framework requires the special value *CURSOR* for the OUT parameters,
+ they will be converted to appropriate DB variables before calling the procedure.
+ | @{params}= Create List CURSOR CURSOR
+ | # The parameters must have this special value CURSOR
+ |
+ | ${param values} ${result sets}= Call Stored Procedure get_all_first_and_second_names ${params}
+ | # ${param values} = ['CURSOR_0', 'CURSOR_1']
+ | # ${result sets} = [[('Franz Allan',), ('Jerry',)], [('See',), ('Schneider',)]
+
+ == MS SQL Server (pymssql) ==
+ The _pymssql_ driver doesn't natively support getting the OUT parameter values after calling a procedure.
+ - This requires special handling of OUT parameters using the `additional_output_params` argument.
+ - Furthermore, it's not possible to fetch the OUT parameter values for a procedure, which returns a result set AND has OUT parameters.
+
+ === Simple case with IN and OUT params (no result sets) ===
+ Consider the following procedure:
+ | CREATE PROCEDURE
+ | return_out_param_without_result_sets
+ | @my_input VARCHAR(20),
+ | @my_output INT OUTPUT
+ | AS
+ | BEGIN
+ | IF @my_input = 'give me 1'
+ | BEGIN
+ | SELECT @my_output = 1;
+ | END
+ | ELSE
+ | BEGIN
+ | SELECT @my_output = 0;
+ | END
+ | END;
+
+ Calling the procedure in Robot Framework requires putting the IN parameters as usual in the `procedure_params` argument,
+ but the sample values of OUT parameters must be put in the argument `additional_output_params`.
+
+ | @{params}= Create List give me 1
+ | @{out_params}= Create List ${9}
+ | ${param values} ${result sets}= Call Stored Procedure return_out_param_without_result_sets
+ | ... ${params} additional_output_params=${out_params}
+ | # ${result sets} = []
+ | # ${param values} = ('give me 1', 1)
+
+ The library uses the sample values in the `additional_output_params` list to determine the number and the type
+ of OUT parameters - so they are type-sensitive, the type must be the same as in the procedure itself.
+
+ === MS SQL procedure returning a result set (no OUT params) ===
+ If a procedure doesn't have any OUT params and returns only result sets, they are handled in a normal way.
+ Consider the following procedure:
+ | CREATE PROCEDURE get_all_first_and_second_names
+ | AS
+ | BEGIN
+ | SELECT FIRST_NAME FROM person;
+ | SELECT LAST_NAME FROM person;
+ | RETURN;
+ | END;
+
+ Calling the procedure in Robot Framework:
+ | ${param values} ${result sets}= Call Stored Procedure get_all_first_and_second_names
+ | ${param values} = ()
+ | ${result sets} = [[('Franz Allan',), ('Jerry',)], [('See',), ('Schneider',)]]
+
+ === MS SQL procedure returning result sets AND OUT params ===
+ This case is *not fully supported* by the library - the OUT params won't be fetched.
"""
- if spParams is None:
- spParams = []
+ db_connection = self.connection_store.get_connection(alias)
+ if procedure_params is None:
+ procedure_params = []
+ if additional_output_params is None:
+ additional_output_params = []
cur = None
try:
- if self.db_api_module_name in ["cx_Oracle"]:
- cur = self._dbconnection.cursor()
+ if db_connection.module_name == "pymssql":
+ cur = db_connection.client.cursor(as_dict=False)
else:
- cur = self._dbconnection.cursor(as_dict=False)
- PY3K = sys.version_info >= (3, 0)
- if not PY3K:
- spName = spName.encode('ascii', 'ignore')
- logger.info('Executing : Call Stored Procedure | %s | %s ' % (spName, spParams))
- cur.callproc(spName, spParams)
- cur.nextset()
- retVal=list()
- for row in cur:
- #logger.info ( ' %s ' % (row))
- retVal.append(row)
- if not sansTran:
- self._dbconnection.commit()
- return retVal
- finally:
- if cur:
- if not sansTran:
- self._dbconnection.rollback()
-
- def __execute_sql(self, cur, sqlStatement):
- return cur.execute(sqlStatement)
+ cur = db_connection.client.cursor()
+
+ param_values = []
+ result_sets = []
+
+ if db_connection.module_name == "pymysql":
+ cur.callproc(procedure_name, procedure_params)
+
+ # first proceed the result sets if available
+ result_sets_available = True
+ while result_sets_available:
+ result_sets.append(list(cur.fetchall()))
+ result_sets_available = cur.nextset()
+ # last result set is always empty
+ # https://pymysql.readthedocs.io/en/latest/modules/cursors.html#pymysql.cursors.Cursor.callproc
+ result_sets.pop()
+
+ # now go on with single values - modified input params
+ for i in range(0, len(procedure_params)):
+ cur.execute(f"select @_{procedure_name}_{i}")
+ param_values.append(cur.fetchall()[0][0])
+
+ elif db_connection.module_name in ["oracledb", "cx_Oracle"]:
+ # check if "CURSOR" params were passed - they will be replaced
+ # with cursor variables for storing the result sets
+ params_substituted = procedure_params.copy()
+ cursor_params = []
+ for i in range(0, len(procedure_params)):
+ if procedure_params[i] == "CURSOR":
+ cursor_param = db_connection.client.cursor()
+ params_substituted[i] = cursor_param
+ cursor_params.append(cursor_param)
+ param_values = cur.callproc(procedure_name, params_substituted)
+ for result_set in cursor_params:
+ result_sets.append(list(result_set))
+
+ elif db_connection.module_name in ["psycopg2", "psycopg3"]:
+ # check if "CURSOR" params were passed - they will be replaced
+ # with cursor variables for storing the result sets
+ params_substituted = procedure_params.copy()
+ cursor_params = []
+ for i in range(0, len(procedure_params)):
+ if procedure_params[i] == "CURSOR":
+ cursor_param = f"CURSOR_{i}"
+ params_substituted[i] = cursor_param
+ cursor_params.append(cursor_param)
+ param_values = cur.callproc(procedure_name, params_substituted)
+ if cursor_params:
+ for cursor_param in cursor_params:
+ cur.execute(f'FETCH ALL IN "{cursor_param}"')
+ result_set = cur.fetchall()
+ result_sets.append(list(result_set))
+ else:
+ if db_connection.module_name in ["psycopg3"]:
+ result_sets_available = True
+ while result_sets_available:
+ result_sets.append(list(cur.fetchall()))
+ result_sets_available = cur.nextset()
+ else:
+ result_set = cur.fetchall()
+ result_sets.append(list(result_set))
+
+ else:
+ if db_connection.module_name == "pymssql":
+ mssql = importlib.import_module("pymssql")
+ procedure_params = procedure_params.copy()
+ for param in additional_output_params:
+ procedure_params.append(mssql.output(type(param), param))
+
+ else:
+ logger.info(
+ f"Calling a stored procedure for '{db_connection.module_name}'. "
+ "No special handling is known, so trying the common way with return params and result sets."
+ )
+
+ param_values = cur.callproc(procedure_name, procedure_params)
+ logger.info("Reading the procedure result sets..")
+ result_sets_available = True
+ while result_sets_available:
+ result_set = []
+ for row in cur:
+ result_set.append(row)
+ if result_set:
+ result_sets.append(list(result_set))
+ if hasattr(cur, "nextset") and inspect.isroutine(cur.nextset):
+ result_sets_available = cur.nextset()
+ else:
+ result_sets_available = False
+
+ self._commit_if_needed(db_connection, no_transaction)
+ return param_values, result_sets
+ except Exception as e:
+ self._rollback_and_raise(db_connection, no_transaction, e)
+
+ def set_logging_query_results(self, enabled: Optional[bool] = None, log_head: Optional[int] = None):
+ """
+ Allows to enable/disable logging of query results and to adjust the log head value.
+ - Overrides the values, which were set during the library import.
+ - See `Logging query results` for details.
+
+ Examples:
+ | Set Logging Query Results | enabled=False |
+ | Set Logging Query Results | enabled=True | log_head=0 |
+ | Set Logging Query Results | log_head=10 |
+ """
+ if enabled is not None:
+ self.LOG_QUERY_RESULTS = enabled
+ if log_head is not None:
+ if log_head < 0:
+ raise ValueError(f"Wrong log head value provided: {log_head}. The value can't be negative!")
+ self.LOG_QUERY_RESULTS_HEAD = log_head
+
+ def _execute_sql(
+ self,
+ cur,
+ sql_statement: str,
+ omit_trailing_semicolon: Optional[bool] = False,
+ parameters: Optional[Tuple] = None,
+ ):
+ """
+ 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).
+ Otherwise, it's decided based on the current database module in use.
+ """
+ if omit_trailing_semicolon:
+ sql_statement = sql_statement.rstrip(";")
+ if parameters is None:
+ logger.info(f'Executing sql: {sql_statement}', html=True)
+ return cur.execute(sql_statement)
+ else:
+ logger.info(
+ f'Executing sql: {sql_statement} Parameters: {parameters}',
+ html=True,
+ )
+ return cur.execute(sql_statement, parameters)
+
+ def _commit_if_needed(self, db_connection: Connection, no_transaction):
+ if no_transaction:
+ logger.info(f"Perform no commit, because 'no_transaction' set to {no_transaction}")
+ else:
+ logger.info("Commit the transaction")
+ db_connection.client.commit()
+
+ def _rollback_and_raise(self, db_connection: Connection, no_transaction, e):
+ logger.info(f"Error occurred: {e}")
+ if no_transaction:
+ logger.info(f"Perform no rollback, because 'no_transaction' set to {no_transaction}")
+ else:
+ logger.info("Rollback the transaction")
+ db_connection.client.rollback()
+ raise e
+
+ def _log_query_results(self, col_names, result_rows, log_head: Optional[int] = None):
+ """
+ Logs the `result_rows` of a query in RF log as a HTML table.
+ The `col_names` are needed for the table header.
+ Max. `log_head` rows are logged (`0` disables the limit).
+ """
+ if not self.LOG_QUERY_RESULTS:
+ return
+
+ if log_head is None:
+ log_head = self.LOG_QUERY_RESULTS_HEAD
+ cell_border_and_align = "border: 1px solid rgb(160 160 160);padding: 8px 10px;text-align: center;"
+ table_border = "2px solid rgb(140 140 140)"
+ row_index_background_color = "#d6ecd4"
+ row_index_text_color = "black"
+ msg = '
'
+ msg += f'
'
+ msg += f'
Query returned {len(result_rows)} rows
'
+ msg += "
"
+ msg += f'
Row
'
+ for col in col_names:
+ msg += f'
{col}
'
+ msg += "
"
+ table_truncated = False
+ for i, row in enumerate(result_rows):
+ if log_head and i >= log_head:
+ table_truncated = True
+ break
+ row_style = ""
+ if i % 2 == 0:
+ row_style = ' style="background-color: var(--secondary-color, #eee)"'
+ msg += f"
"
+ msg += f'
{i}
'
+ for cell in row:
+ msg += f'
{cell}
'
+ msg += "
"
+ msg += "
"
+ if table_truncated:
+ msg += (
+ f'
Log limit of {log_head} rows was reached, the table was truncated
'
+ )
+ msg += "
"
+ logger.info(msg, html=True)
diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py
index 69378709..4260069c 100644
--- a/src/DatabaseLibrary/version.py
+++ b/src/DatabaseLibrary/version.py
@@ -1 +1 @@
-VERSION = '1.2.4'
+VERSION = "2.1.3"
diff --git a/test/DB2SQL_DB_Conf.txt b/test/DB2SQL_DB_Conf.txt
deleted file mode 100644
index 3a22bea0..00000000
--- a/test/DB2SQL_DB_Conf.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-*** Variables ***
-${DBName} dbname
-${DBUser} user
-${DBPass} password
-${DBHost} host
-${DBPort} port
diff --git a/test/DB2SQL_DB_Tests.robot b/test/DB2SQL_DB_Tests.robot
deleted file mode 100644
index 46e50f5e..00000000
--- a/test/DB2SQL_DB_Tests.robot
+++ /dev/null
@@ -1,99 +0,0 @@
-*** Settings ***
-Suite Setup Connect To Database ibm_db_dbi ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
-Suite Teardown Disconnect From Database
-Resource DB2SQL_DB_Conf.txt
-Library DatabaseLibrary
-
-*** Test Cases ***
-Create person table
- ${output} = Execute SQL String CREATE TABLE person (id decimal(10,0),first_name varchar(30),last_name varchar(30));
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- Comment ${output} = Execute SQL Script ./my_db_test_insertData.sql
- ${output} = Execute SQL Script ../test/my_db_test_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL String - Create Table
- ${output} = Execute SQL String create table foobar (id integer , firstname varchar(20) )
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- Table Must Exist person
-
-Verify Row Count is 0
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Verify person Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM person fetch first 1 rows only;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ['ID', DBAPITypeObject(['NUM', 'DECIMAL', 'DEC', 'NUMERIC']), 12, 12, 10, 0, True]
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ['FIRST_NAME', DBAPITypeObject(['CHARACTER VARYING', 'CHAR VARYING', 'VARCHAR', 'STRING', 'CHARACTER', 'CHAR']), 30, 30, 30, 0, True]
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} ['LAST_NAME', DBAPITypeObject(['CHARACTER VARYING', 'CHAR VARYING', 'VARCHAR', 'STRING', 'CHARACTER', 'CHAR']), 30, 30, 30, 0, True]
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify Query - Row Count person table
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} [(2,)]
-
-Verify Query - Row Count foobar table
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(0,)]
-
-Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Insert Data Into Table foobar
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(1,)]
-
-Verify Delete All Rows From Table - foobar
- Delete All Rows From Table foobar
-
-Verify Query - Row Count foobar table 0 row
- Row Count Is 0 SELECT * FROM foobar;
-
-Drop person and foobar table
- Execute SQL String DROP TABLE person;
- Execute SQL String DROP TABLE foobar;
diff --git a/test/MSSQL_DB_Tests.robot b/test/MSSQL_DB_Tests.robot
deleted file mode 100644
index f5870d20..00000000
--- a/test/MSSQL_DB_Tests.robot
+++ /dev/null
@@ -1,210 +0,0 @@
-*** Settings ***
-Suite Setup Connect To Database pymssql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
-Suite Teardown Disconnect From Database
-Library DatabaseLibrary
-Library OperatingSystem
-
-*** Variables ***
-${DBHost} ${EMPTY}
-${DBName} ${EMPTY}
-${DBPass} ${EMPTY}
-${DBPort} ${EMPTY}
-${DBUser} ${EMPTY}
-
-*** Test Cases ***
-Create person table
- [Tags] db smoke
- ${output} = Execute SQL String CREATE TABLE person (id integer unique, first_name varchar(20), last_name varchar(20));
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- [Tags] db smoke
- ${output} = Execute SQL Script ./my_db_test_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL String - Create Table
- [Tags] db smoke
- ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar(20) unique)
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- [Tags] db smoke
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- [Tags] db smoke
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- [Tags] db smoke
- Table Must Exist person
-
-Verify Row Count is 0
- [Tags] db smoke
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- [Tags] db smoke
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- [Tags] db smoke
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- [Tags] db smoke
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- [Tags] db smoke
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Retrieve records from person table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT * FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT TOP 1 * FROM person;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} (u'id', 3, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} (u'first_name', 1, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} (u'last_name', 1, None, None, None, None, None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify foobar Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT TOP 1 * FROM foobar;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} (u'id', 3, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} (u'firstname', 1, None, None, None, None, None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 2
-
-Verify Query - Row Count person table
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} [(2,)]
-
-Verify Query - Row Count foobar table
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(0,)]
-
-Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Verify Execute SQL String - Row Count person table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Execute SQL String - Row Count foobar table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Insert Data Into Table foobar
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(1,)]
-
-Verify Delete All Rows From Table - foobar
- [Tags] db smoke
- Delete All Rows From Table foobar
- Comment Sleep 2s
-
-Verify Query - Row Count foobar table 0 row
- [Tags] db smoke
- Row Count Is 0 SELECT * FROM foobar;
-
-Begin first transaction
- [Tags] db smoke
- ${output} = Execute SQL String SAVE TRANSACTION first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in first transaction
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO person VALUES(101,'Bilbo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person in first transaction
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Begin second transaction
- [Tags] db smoke
- ${output} = Execute SQL String SAVE TRANSACTION second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in second transaction
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO person VALUES(102,'Frodo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify persons in first and second transactions
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 2 True
-
-Rollback second transaction
- [Tags] db smoke
- ${output} = Execute SQL String ROLLBACK TRANSACTION second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify second transaction rollback
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Rollback first transaction
- [Tags] db smoke
- ${output} = Execute SQL String ROLLBACK TRANSACTION first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify first transaction rollback
- [Tags] db smoke
- Row Count is 0 SELECT * FROM person WHERE last_name = 'Baggins'; True
-
-Drop person and foobar tables
- [Tags] db smoke
- ${output} = Execute SQL String DROP TABLE IF EXISTS person;
- Log ${output}
- Should Be Equal As Strings ${output} None
- ${output} = Execute SQL String DROP TABLE IF EXISTS foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
diff --git a/test/MySQL_DB_Tests.robot b/test/MySQL_DB_Tests.robot
deleted file mode 100644
index d242df96..00000000
--- a/test/MySQL_DB_Tests.robot
+++ /dev/null
@@ -1,211 +0,0 @@
-*** Settings ***
-Suite Setup Connect To Database pymysql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
-Suite Teardown Disconnect From Database
-Library DatabaseLibrary
-Library OperatingSystem
-
-*** Variables ***
-${DBHost} 127.0.0.1
-${DBName} my_db_test
-${DBPass} ""
-${DBPort} 3306
-${DBUser} root
-
-*** Test Cases ***
-Create person table
- [Tags] db smoke
- ${output} = Execute SQL String CREATE TABLE person (id integer unique,first_name varchar(20),last_name varchar(20));
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- [Tags] db smoke
- Comment ${output} = Execute SQL Script ./${DBName}_insertData.sql
- ${output} = Execute SQL Script ./my_db_test_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL String - Create Table
- [Tags] db smoke
- ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar(20) unique)
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- [Tags] db smoke
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- [Tags] db smoke
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- [Tags] db smoke
- Table Must Exist person
-
-Verify Row Count is 0
- [Tags] db smoke
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- [Tags] db smoke
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- [Tags] db smoke
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- [Tags] db smoke
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- [Tags] db smoke
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Retrieve records from person table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT * FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM person LIMIT 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} (u'id', 3, None, 11, 11, 0, True)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} (u'first_name', 253, None, 20, 20, 0, True)
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} (u'last_name', 253, None, 20, 20, 0, True)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify foobar Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM foobar LIMIT 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} (u'id', 3, None, 11, 11, 0, False)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} (u'firstname', 253, None, 20, 20, 0, True)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 2
-
-Verify Query - Row Count person table
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} ((2,),)
-
-Verify Query - Row Count foobar table
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} ((0,),)
-
-Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Verify Execute SQL String - Row Count person table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Execute SQL String - Row Count foobar table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Insert Data Into Table foobar
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} ((1,),)
-
-Verify Delete All Rows From Table - foobar
- [Tags] db smoke
- Delete All Rows From Table foobar
- Comment Sleep 2s
-
-Verify Query - Row Count foobar table 0 row
- [Tags] db smoke
- Row Count Is 0 SELECT * FROM foobar;
- Comment ${output} = Query SELECT COUNT(*) FROM foobar;
- Comment Log ${output}
- Comment Should Be Equal As Strings ${output} [(0,)]
-
-Begin first transaction
- [Tags] db smoke
- ${output} = Execute SQL String SAVEPOINT first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in first transaction
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO person VALUES(101,'Bilbo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person in first transaction
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Begin second transaction
- [Tags] db smoke
- ${output} = Execute SQL String SAVEPOINT second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in second transaction
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO person VALUES(102,'Frodo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify persons in first and second transactions
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 2 True
-
-Rollback second transaction
- [Tags] db smoke
- ${output} = Execute SQL String ROLLBACK TO SAVEPOINT second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify second transaction rollback
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Rollback first transaction
- [Tags] db smoke
- ${output} = Execute SQL String ROLLBACK TO SAVEPOINT first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify first transaction rollback
- [Tags] db smoke
- Row Count is 0 SELECT * FROM person WHERE last_name = 'Baggins'; True
-
-Drop person and foobar tables
- [Tags] db smoke
- ${output} = Execute SQL String DROP TABLE IF EXISTS person,foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
diff --git a/test/PostgreSQL_DB_Tests.robot b/test/PostgreSQL_DB_Tests.robot
deleted file mode 100644
index fb458db9..00000000
--- a/test/PostgreSQL_DB_Tests.robot
+++ /dev/null
@@ -1,148 +0,0 @@
-*** Settings ***
-Suite Setup Connect To Database psycopg2 ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
-Suite Teardown Disconnect From Database
-Library DatabaseLibrary
-Library OperatingSystem
-Library Collections
-
-*** Variables ***
-${DBHost} localhost
-${DBName} travis_ci_test
-${DBPass} ""
-${DBPort} 5432
-${DBUser} postgres
-
-*** Test Cases ***
-Create person table
- ${output} = Execute SQL String CREATE TABLE person (id integer unique,first_name varchar,last_name varchar);
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- Comment ${output} = Execute SQL Script ./${DBName}_insertData.sql
- ${output} = Execute SQL Script ./my_db_test_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL String - Create Table
- ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar unique)
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- Table Must Exist person
-
-Verify Row Count is 0
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Retrieve records from person table
- ${output} = Execute SQL String SELECT * FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM person LIMIT 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} Column(name='id', type_code=23, display_size=None, internal_size=4, precision=None, scale=None, null_ok=None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} Column(name='first_name', type_code=1043, display_size=None, internal_size=-1, precision=None, scale=None, null_ok=None)
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} Column(name='last_name', type_code=1043, display_size=None, internal_size=-1, precision=None, scale=None, null_ok=None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify foobar Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM foobar LIMIT 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} Column(name='id', type_code=23, display_size=None, internal_size=4, precision=None, scale=None, null_ok=None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} Column(name='firstname', type_code=1043, display_size=None, internal_size=-1, precision=None, scale=None, null_ok=None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 2
-
-Verify Query - Row Count person table
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- ${val}= Get from list ${output} 0
- ${val}= Convert to list ${val}
- ${val}= Get from list ${val} 0
- Should be equal as Integers ${val} 2
-
-Verify Query - Row Count foobar table
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- ${val}= Get from list ${output} 0
- ${val}= Convert to list ${val}
- ${val}= Get from list ${val} 0
- Should be equal as Integers ${val} 0
-
-Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Verify Execute SQL String - Row Count person table
- ${output} = Execute SQL String SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Execute SQL String - Row Count foobar table
- ${output} = Execute SQL String SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Insert Data Into Table foobar
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- ${val}= Get from list ${output} 0
- ${val}= Convert to list ${val}
- ${val}= Get from list ${val} 0
- Should be equal as Integers ${val} 1
-
-Verify Delete All Rows From Table - foobar
- Delete All Rows From Table foobar
- Comment Sleep 2s
-
-Verify Query - Row Count foobar table 0 row
- Row Count Is 0 SELECT * FROM foobar;
- Comment ${output} = Query SELECT COUNT(*) FROM foobar;
- Comment Log ${output}
- Comment Should Be Equal As Strings ${output} [(0,)]
-
-Drop person and foobar tables
- ${output} = Execute SQL String DROP TABLE IF EXISTS person,foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
diff --git a/test/PyODBC_DB_Tests.robot b/test/PyODBC_DB_Tests.robot
deleted file mode 100644
index 554b2945..00000000
--- a/test/PyODBC_DB_Tests.robot
+++ /dev/null
@@ -1,177 +0,0 @@
-*** Settings ***
-Suite Setup Connect To Database pyodbc ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} dbDriver=${dbDriver}
-Suite Teardown Disconnect From Database
-Library DatabaseLibrary
-Library OperatingSystem
-
-*** Variables ***
-${DBHost} ${EMPTY}
-${DBName} ${EMPTY}
-${DBPass} ${EMPTY}
-${DBPort} ${EMPTY}
-${DBUser} ${EMPTY}
-${dbDriver} ${EMPTY}
-
-*** Test Cases ***
-Create person table
- ${output} = Execute SQL String CREATE TABLE person (id integer unique, first_name varchar(20), last_name varchar(20));
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- ${output} = Execute SQL Script ./my_db_test_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL String - Create Table
- ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar(20) unique)
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- Table Must Exist person
-
-Verify Row Count is 0
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Retrieve records from person table
- ${output} = Execute SQL String SELECT * FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person Description
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT TOP 1 * FROM person;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} (u'id', 3, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} (u'first_name', 1, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} (u'last_name', 1, None, None, None, None, None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify foobar Description
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT TOP 1 * FROM foobar;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} (u'id', 3, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} (u'firstname', 1, None, None, None, None, None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 2
-
-Verify Query - Row Count person table
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} [(2,)]
-
-Verify Query - Row Count foobar table
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(0,)]
-
-Verify Query - Get results as a list of dictionaries
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Verify Execute SQL String - Row Count person table
- ${output} = Execute SQL String SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Execute SQL String - Row Count foobar table
- ${output} = Execute SQL String SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Insert Data Into Table foobar
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(1,)]
-
-Verify Delete All Rows From Table - foobar
- Delete All Rows From Table foobar
- Comment Sleep 2s
-
-Verify Query - Row Count foobar table 0 row
- Row Count Is 0 SELECT * FROM foobar;
-
-Begin first transaction
- ${output} = Execute SQL String SAVE TRANSACTION first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in first transaction
- ${output} = Execute SQL String INSERT INTO person VALUES(101,'Bilbo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person in first transaction
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Begin second transaction
- ${output} = Execute SQL String SAVE TRANSACTION second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in second transaction
- ${output} = Execute SQL String INSERT INTO person VALUES(102,'Frodo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify persons in first and second transactions
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 2 True
-
-Rollback second transaction
- ${output} = Execute SQL String ROLLBACK TRANSACTION second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify second transaction rollback
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Rollback first transaction
- ${output} = Execute SQL String ROLLBACK TRANSACTION first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify first transaction rollback
- Row Count is 0 SELECT * FROM person WHERE last_name = 'Baggins'; True
-
-Drop person and foobar tables
- ${output} = Execute SQL String DROP TABLE IF EXISTS person;
- Log ${output}
- Should Be Equal As Strings ${output} None
- ${output} = Execute SQL String DROP TABLE IF EXISTS foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
diff --git a/test/SQLite3_DB_Tests.robot b/test/SQLite3_DB_Tests.robot
deleted file mode 100644
index 3b63684e..00000000
--- a/test/SQLite3_DB_Tests.robot
+++ /dev/null
@@ -1,216 +0,0 @@
-*** Settings ***
-Library DatabaseLibrary
-Library OperatingSystem
-
-*** Variables ***
-${DBName} my_db_test
-
-*** Test Cases ***
-Remove old DB if exists
- [Tags] db smoke
- ${Status} ${value} = Run Keyword And Ignore Error File Should Not Exist ./${DBName}.db
- Run Keyword If "${Status}" == "FAIL" Run Keyword And Ignore Error Remove File ./${DBName}.db
- File Should Not Exist ./${DBName}.db
- Comment Sleep 1s
-
-Connect to SQLiteDB
- [Tags] db smoke
- Comment Connect To Database Using Custom Params sqlite3 database='path_to_dbfile\dbname.db'
- Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None
-
-Create person table
- [Tags] db smoke
- ${output} = Execute SQL String CREATE TABLE person (id integer unique,first_name varchar,last_name varchar);
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- [Tags] db smoke
- ${output} = Execute SQL Script ./${DBName}_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL String - Create Table
- [Tags] db smoke
- ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar unique)
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- [Tags] db smoke
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- [Tags] db smoke
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- [Tags] db smoke
- Table Must Exist person
-
-Verify Row Count is 0
- [Tags] db smoke
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- [Tags] db smoke
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- [Tags] db smoke
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- [Tags] db smoke
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- [Tags] db smoke
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Retrieve records from person table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT * FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM person LIMIT 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', None, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('first_name', None, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} ('last_name', None, None, None, None, None, None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify foobar Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM foobar LIMIT 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', None, None, None, None, None, None)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('firstname', None, None, None, None, None, None)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 2
-
-Verify Query - Row Count person table
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} [(2,)]
-
-Verify Query - Row Count foobar table
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(0,)]
-
-Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Verify Execute SQL String - Row Count person table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Execute SQL String - Row Count foobar table
- [Tags] db smoke
- ${output} = Execute SQL String SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Insert Data Into Table foobar
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- [Tags] db smoke
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} [(1,)]
-
-Verify Delete All Rows From Table - foobar
- [Tags] db smoke
- Delete All Rows From Table foobar
- Comment Sleep 2s
-
-Verify Query - Row Count foobar table 0 row
- [Tags] db smoke
- Row Count Is 0 SELECT * FROM foobar;
-
-Begin first transaction
- [Tags] db smoke
- ${output} = Execute SQL String SAVEPOINT first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in first transaction
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO person VALUES(101,'Bilbo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person in first transaction
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Begin second transaction
- [Tags] db smoke
- ${output} = Execute SQL String SAVEPOINT second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Add person in second transaction
- [Tags] db smoke
- ${output} = Execute SQL String INSERT INTO person VALUES(102,'Frodo','Baggins'); True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify persons in first and second transactions
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 2 True
-
-Rollback second transaction
- [Tags] db smoke
- ${output} = Execute SQL String ROLLBACK TO SAVEPOINT second True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify second transaction rollback
- [Tags] db smoke
- Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True
-
-Rollback first transaction
- [Tags] db smoke
- ${output} = Execute SQL String ROLLBACK TO SAVEPOINT first True
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify first transaction rollback
- [Tags] db smoke
- Row Count is 0 SELECT * FROM person WHERE last_name = 'Baggins'; True
-
-Drop person and foobar tables
- [Tags] db smoke
- ${output} = Execute SQL String DROP TABLE IF EXISTS person;
- Log ${output}
- Should Be Equal As Strings ${output} None
- ${output} = Execute SQL String DROP TABLE IF EXISTS foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
diff --git a/test/Teradata_DB_Tests.robot b/test/Teradata_DB_Tests.robot
deleted file mode 100644
index 35c7f82b..00000000
--- a/test/Teradata_DB_Tests.robot
+++ /dev/null
@@ -1,154 +0,0 @@
-*** Settings ***
-Suite Setup Connect To Database teradata ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
-Suite Teardown Disconnect From Database
-Library DatabaseLibrary
-Library OperatingSystem
-Library Collections
-
-*** Variables ***
-${DBHost} 192.168.10.45
-${DBName} ADRIAN
-${DBPass} dbc
-${DBPort} 1025
-${DBUser} dbc
-
-*** Test Cases ***
-Create person table
- [Tags] db smoke
- ${output} = Execute SQL String CREATE TABLE person (id integer not null unique,first_name varchar(20),last_name varchar(20));
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Execute SQL Script - Insert Data person table
- Comment ${output} = Execute SQL Script ./${DBName}_insertData.sql
- ${output} = Execute SQL Script ./my_db_test_insertData.sql
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Create foobar table
- ${output} = Execute SQL String create table foobar (id integer not null primary key, firstname varchar(100) not null unique)
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Check If Exists In DB - Franz Allan
- Check If Exists In Database SELECT id FROM person WHERE first_name = 'Franz Allan';
-
-Check If Not Exists In DB - Joe
- Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe';
-
-Table Must Exist - person
- Table Must Exist person
-
-Verify Row Count is 0
- Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere';
-
-Verify Row Count is Equal to X
- Row Count is Equal to X SELECT id FROM person; 2
-
-Verify Row Count is Less Than X
- Row Count is Less Than X SELECT id FROM person; 3
-
-Verify Row Count is Greater Than X
- Row Count is Greater Than X SELECT * FROM person; 1
-
-Retrieve Row Count
- ${output} = Row Count SELECT id FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} 2
-
-Retrieve records from person table
- ${output} = Execute SQL String SELECT * FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify person Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM person SAMPLE 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', , None, 10, 0, None, 0)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('first_name', , None, 20, 0, None, 1)
- ${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} ('last_name', , None, 20, 0, None, 1)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 3
-
-Verify foobar Description
- [Tags] db smoke
- Comment Query db for table column descriptions
- @{queryResults} = Description SELECT * FROM foobar SAMPLE 1;
- Log Many @{queryResults}
- ${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', , None, 10, 0, None, 0)
- ${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('firstname', , None, 100, 0, None, 0)
- ${NumColumns} = Get Length ${queryResults}
- Should Be Equal As Integers ${NumColumns} 2
-
-Verify Query - Row Count person table
- ${output} = Query SELECT COUNT(*) FROM person;
- Log ${output}
- ${val}= Get from list ${output} 0
- ${val}= Convert to list ${val}
- ${val}= Get from list ${val} 0
- Should be equal as Integers ${val} 2
-
-Verify Query - Row Count foobar table
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- ${val}= Get from list ${output} 0
- ${val}= Convert to list ${val}
- ${val}= Get from list ${val} 0
- Should be equal as Integers ${val} 0
-
-Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
- ${output} = Query SELECT * FROM person; \ True
- Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
-
-Verify Execute SQL String - Row Count person table
- ${output} = Execute SQL String SELECT COUNT(*) FROM person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Execute SQL String - Row Count foobar table
- ${output} = Execute SQL String SELECT COUNT(*) FROM foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Insert Data Into Table foobar
- ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry');
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Verify Query - Row Count foobar table 1 row
- ${output} = Query SELECT COUNT(*) FROM foobar;
- Log ${output}
- ${val}= Get from list ${output} 0
- ${val}= Convert to list ${val}
- ${val}= Get from list ${val} 0
- Should be equal as Integers ${val} 1
-
-Verify Delete All Rows From Table - foobar
- Delete All Rows From Table foobar
- Comment Sleep 2s
-
-Verify Query - Row Count foobar table 0 row
- Row Count Is 0 SELECT * FROM foobar;
- Comment ${output} = Query SELECT COUNT(*) FROM foobar;
- Comment Log ${output}
- Comment Should Be Equal As Strings ${output} [(0,)]
-
-Drop person table
- ${output} = Execute SQL String DROP TABLE person;
- Log ${output}
- Should Be Equal As Strings ${output} None
-
-Drop foobar table
- ${output} = Execute SQL String DROP TABLE foobar;
- Log ${output}
- Should Be Equal As Strings ${output} None
diff --git a/test/readme.md b/test/readme.md
new file mode 100644
index 00000000..96870c43
--- /dev/null
+++ b/test/readme.md
@@ -0,0 +1,61 @@
+# Which tests run automatically in the pipeline?
+- Tests from the folder `common_tests` run automatically in the pipeline after pushing in the repository
+- The tests in the folder `custom_db_tests` are designed to run locally - they have to be triggered manually. I don't run them at all changes.
+- There are some unit tests with pytest, but mostly there are acceptance tests with RF
+- See the folder `.github/workflows`
+
+# Which databases / modules are covered?
+- The acceptance tests in the pipeline don't cover all possible DB's - here is a lot of room for improvement
+- Running tests locally require DB containers running - see below
+
+# Running tests locally from VS Code / terminal
+- Selecting a DB module works via a global variable `GLOBAL_DB_SELECTOR` - set it from VSC or CLI
+- Current debug/launch configs are implemented for old LSP plugin - still need to update to Robotcode from Daniel
+
+# Here are some advices for local testing of the library with different Python DB modules
+## Oracle:
+- https://github.com/gvenzl/oci-oracle-free
+- https://hub.docker.com/r/gvenzl/oracle-free
+- docker pull gvenzl/oracle-free
+- docker run --rm --name oracle -d -p 1521:1521 -e ORACLE_PASSWORD=pass -e ORACLE_DATABASE=db -e APP_USER=db_user -e APP_USER_PASSWORD=pass gvenzl/oracle-free
+
+## PostgreSQL
+- https://hub.docker.com/_/postgres
+- docker pull postgres
+- docker run --rm --name postgres -e POSTGRES_USER=db_user -e POSTGRES_PASSWORD=pass -e POSTGRES_DB=db -p 5432:5432 -d postgres
+
+## Teradata
+- use VM image, e.g. in VirtualBox
+- https://downloads.teradata.com/download/database/teradata-express/vmware
+- use network bridge mode
+- create new DB
+ CREATE DATABASE db
+ AS PERMANENT = 60e6, -- 60MB
+ SPOOL = 120e6; -- 120MB
+- Install Teradata driver for your OS
+ https://downloads.teradata.com/download/connectivity/odbc-driver/windows
+
+- DEPRECATED: https://github.com/teradata/PyTd
+ -> new: https://github.com/Teradata/python-driver
+- docs: https://quickstarts.teradata.com/getting.started.vbox.html
+
+## IBM Db2
+- https://hub.docker.com/r/ibmcom/db2
+- docker pull ibmcom/db2
+- docker run --rm -itd --name mydb2 --privileged=true -p 50000:50000 -e LICENSE=accept -e DB2INSTANCE=db_user -e DB2INST1_PASSWORD=pass -e DBNAME=db ibmcom/db2
+--> needs some minutes to start the DB !!!
+
+## MySQL
+- https://hub.docker.com/_/mysql
+- docker run --rm --name mysql -e MYSQL_ROOT_PASSWORD=pass -e MYSQL_DATABASE=db -e MYSQL_USER=db_user -e MYSQL_PASSWORD=pass -p 3306:3306 -d mysql
+- For tests with pyodbc install the ODBC driver https://learn.microsoft.com/en-us/sql/connect/odbc/windows/system-requirements-installation-and-driver-files?view=sql-server-ver16#installing-microsoft-odbc-driver-for-sql-server
+
+## 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
+--> login and create DB:
+ - docker exec -it mssql bash
+ - /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'MyPass1234!'
+ - CREATE DATABASE db
+ - go
+- docs: https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16&pivots=cs1-bash
\ No newline at end of file
diff --git a/test/resources/common.resource b/test/resources/common.resource
new file mode 100644
index 00000000..260002ca
--- /dev/null
+++ b/test/resources/common.resource
@@ -0,0 +1,125 @@
+*** Settings ***
+Documentation Global variables, which are used in all test common tests
+... and which should be set outside at the test execution start (e.g. in CI pipeline)
+
+Library Collections
+Library OperatingSystem
+Library DatabaseLibrary
+Library DateTime
+Resource config_files/connect_config_file.resource
+
+
+*** Variables ***
+${DB_MODULE_MODE} standard
+${DB_MODULE} psycopg2
+${DB_HOST} 127.0.0.1
+${DB_NAME} db
+${DB_PASS} pass
+${DB_PORT} 5432
+${DB_USER} db_user
+
+# used for MySQL via PyODBC only
+${DB_DRIVER} ODBC Driver 18 for SQL Server
+
+# Oracle via Jaydebeapi
+${DRIVER_PATH} ${CURDIR}/ojdbc17.jar
+${DRIVER_CLASSNAME} oracle.jdbc.driver.OracleDriver
+&{DRIVER_ARGS} user=${DB_USER} password=${DB_PASS}
+${JDBC_URL} jdbc:oracle:thin:@${DB_HOST}:${DB_PORT}/${DB_NAME}
+
+
+*** Keywords ***
+Connect To DB
+ [Documentation] Connects to the database based on the current DB module under test
+ ... and connection params set in global variables with alias
+ [Arguments] ${alias}=${None}
+ ${DB_KWARGS}= Create Dictionary
+ IF $alias is not None
+ Set To Dictionary ${DB_KWARGS} alias=${alias}
+ END
+ IF "${DB_MODULE_MODE}" == "custom"
+ IF "${DB_MODULE}" == "sqlite3"
+ Remove File ${DBName}.db
+ Connect To Database sqlite3 database=./${DBName}.db isolation_level=${None}
+ ... &{DB_KWARGS}
+ ELSE IF "${DB_MODULE}" == "jaydebeapi"
+ Connect To Database ${DB_MODULE} jclassname=${DRIVER_CLASSNAME} url=${JDBC_URL}
+ ... driver_args=${DRIVER_ARGS} jars=${DRIVER_PATH} &{DB_KWARGS}
+ Set Auto Commit False alias=${alias}
+ Set Omit Trailing Semicolon True alias=${alias}
+ ELSE
+ ${Connection String}= Build Connection String
+ Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} &{DB_KWARGS}
+ END
+ ELSE IF "${DB_MODULE_MODE}" == "standard"
+ ${DB_ARGS}= Create List
+ ... ${DB_MODULE}
+ ... ${DB_NAME}
+ ... ${DB_USER}
+ ... ${DB_PASS}
+ ... ${DB_HOST}
+ ... ${DB_PORT}
+ IF "${DB_MODULE}" == "pyodbc"
+ Set To Dictionary ${DB_KWARGS} odbc_driver=${DB_DRIVER}
+ END
+ Connect To Database @{DB_ARGS} &{DB_KWARGS}
+ ELSE
+ Fail Unexpected mode - ${DB_MODULE_MODE}
+ END
+
+Build Connection String
+ [Documentation] Returns the connection string variable depending on the DB module
+ ... currently under test.
+ IF "${DB_MODULE}" == "oracledb"
+ ${Result}= Set Variable
+ ... ${DB_USER}/${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}
+ ELSE IF "${DB_MODULE}" == "psycopg2"
+ ${Result}= Set Variable
+ ... postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}
+ ELSE
+ Skip Don't know how to build a connection string for '${DB_MODULE}'
+ END
+ RETURN ${Result}
+
+Create Person Table
+ ${sql}= Catenate
+ ... CREATE TABLE person
+ ... (id integer not null unique, FIRST_NAME varchar(20), LAST_NAME varchar(20))
+ ${output}= Execute Sql String ${sql}
+ RETURN ${output}
+
+Create Person Table And Insert Data
+ Create Person Table
+ Insert Data In Person Table Using SQL Script
+
+Insert Data In Person Table Using SQL Script
+ [Arguments] ${alias}=${None}
+ IF $alias is None
+ ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql
+ ELSE
+ ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql alias=${alias}
+ END
+ RETURN ${output}
+
+Create Foobar Table
+ ${sql}= Catenate
+ ... CREATE TABLE foobar
+ ... (id integer not null primary key, FIRST_NAME varchar(30) not null unique)
+ ${output}= Execute Sql String ${sql}
+ RETURN ${output}
+
+Create Foobar Table And Insert Data
+ Create Foobar Table
+ Execute SQL String INSERT INTO foobar VALUES(1,'Jerry')
+
+Create Tables Person And Foobar
+ Create Person Table
+ Create Foobar Table
+
+Drop Tables Person And Foobar
+ Sleep 1s
+ FOR ${table} IN person foobar
+ ${exists}= Run Keyword And Return Status
+ ... Table Must Exist ${table}
+ IF ${exists} Execute Sql String DROP TABLE ${table}
+ END
diff --git a/test/resources/config_files/connect_config_file.resource b/test/resources/config_files/connect_config_file.resource
new file mode 100644
index 00000000..621ecd36
--- /dev/null
+++ b/test/resources/config_files/connect_config_file.resource
@@ -0,0 +1,11 @@
+*** Settings ***
+Resource ../common.resource
+
+
+*** Keywords ***
+Connect Using Config File
+ [Documentation] `File name` is only name without extension,
+ ... the path is build relative to the resource directory
+ [Arguments] ${File name}=${None} &{Params}
+ ${Path}= Set Variable ${CURDIR}/${File name}.cfg
+ Connect To Database config_file=${Path} &{Params}
diff --git a/test/resources/config_files/oracledb/custom_param_password.cfg b/test/resources/config_files/oracledb/custom_param_password.cfg
new file mode 100644
index 00000000..d6d93b96
--- /dev/null
+++ b/test/resources/config_files/oracledb/custom_param_password.cfg
@@ -0,0 +1,6 @@
+[default]
+db_module=oracledb
+db_name=db
+password=pass
+db_host=127.0.0.1
+db_port=1521
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/invalid_custom_params.cfg b/test/resources/config_files/oracledb/invalid_custom_params.cfg
new file mode 100644
index 00000000..1214b8be
--- /dev/null
+++ b/test/resources/config_files/oracledb/invalid_custom_params.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=oracledb
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=1521
+blah=blah
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/old_param_names.cfg b/test/resources/config_files/oracledb/old_param_names.cfg
new file mode 100644
index 00000000..2d81cd40
--- /dev/null
+++ b/test/resources/config_files/oracledb/old_param_names.cfg
@@ -0,0 +1,8 @@
+[default]
+dbapiModuleName=oracledb
+dbName=db
+dbUsername=db_user
+dbPassword=pass
+dbHost=127.0.0.1
+dbPort=1521
+driverMode=thin
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/simple_default_alias.cfg b/test/resources/config_files/oracledb/simple_default_alias.cfg
new file mode 100644
index 00000000..fe487e8e
--- /dev/null
+++ b/test/resources/config_files/oracledb/simple_default_alias.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=oracledb
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=1521
+oracle_driver_mode=thin
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/some_basic_params_missing.cfg b/test/resources/config_files/oracledb/some_basic_params_missing.cfg
new file mode 100644
index 00000000..7e845af3
--- /dev/null
+++ b/test/resources/config_files/oracledb/some_basic_params_missing.cfg
@@ -0,0 +1,3 @@
+[default]
+db_module=oracledb
+db_name=db
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/thick_mode.cfg b/test/resources/config_files/oracledb/thick_mode.cfg
new file mode 100644
index 00000000..bd1a4875
--- /dev/null
+++ b/test/resources/config_files/oracledb/thick_mode.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=oracledb
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=1521
+oracle_driver_mode=thick
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/valid_custom_params.cfg b/test/resources/config_files/oracledb/valid_custom_params.cfg
new file mode 100644
index 00000000..df3bffd1
--- /dev/null
+++ b/test/resources/config_files/oracledb/valid_custom_params.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=oracledb
+db_name=db
+user=db_user
+password=pass
+db_host=127.0.0.1
+db_port=1521
\ No newline at end of file
diff --git a/test/resources/config_files/oracledb/wrong_password.cfg b/test/resources/config_files/oracledb/wrong_password.cfg
new file mode 100644
index 00000000..87691ac7
--- /dev/null
+++ b/test/resources/config_files/oracledb/wrong_password.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=oracledb
+db_name=db
+db_user=db_user
+db_password=wrong
+db_host=127.0.0.1
+db_port=1521
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/custom_param_password.cfg b/test/resources/config_files/psycopg2/custom_param_password.cfg
new file mode 100644
index 00000000..1ae5cb93
--- /dev/null
+++ b/test/resources/config_files/psycopg2/custom_param_password.cfg
@@ -0,0 +1,6 @@
+[default]
+db_module=psycopg2
+db_name=db
+password=pass
+db_host=127.0.0.1
+db_port=5432
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/invalid_custom_params.cfg b/test/resources/config_files/psycopg2/invalid_custom_params.cfg
new file mode 100644
index 00000000..14969652
--- /dev/null
+++ b/test/resources/config_files/psycopg2/invalid_custom_params.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=psycopg2
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=5432
+blah=blah
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/old_param_names.cfg b/test/resources/config_files/psycopg2/old_param_names.cfg
new file mode 100644
index 00000000..d9faef72
--- /dev/null
+++ b/test/resources/config_files/psycopg2/old_param_names.cfg
@@ -0,0 +1,7 @@
+[default]
+dbapiModuleName=psycopg2
+dbName=db
+dbUsername=db_user
+dbPassword=pass
+dbHost=127.0.0.1
+dbPort=5432
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/simple_default_alias.cfg b/test/resources/config_files/psycopg2/simple_default_alias.cfg
new file mode 100644
index 00000000..a80ef74b
--- /dev/null
+++ b/test/resources/config_files/psycopg2/simple_default_alias.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=psycopg2
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=5432
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/some_basic_params_missing.cfg b/test/resources/config_files/psycopg2/some_basic_params_missing.cfg
new file mode 100644
index 00000000..49d1cdee
--- /dev/null
+++ b/test/resources/config_files/psycopg2/some_basic_params_missing.cfg
@@ -0,0 +1,3 @@
+[default]
+db_module=psycopg2
+db_name=db
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/valid_custom_params.cfg b/test/resources/config_files/psycopg2/valid_custom_params.cfg
new file mode 100644
index 00000000..fb15ffac
--- /dev/null
+++ b/test/resources/config_files/psycopg2/valid_custom_params.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=psycopg2
+db_name=db
+user=db_user
+password=pass
+db_host=127.0.0.1
+db_port=5432
\ No newline at end of file
diff --git a/test/resources/config_files/psycopg2/wrong_password.cfg b/test/resources/config_files/psycopg2/wrong_password.cfg
new file mode 100644
index 00000000..9e97614c
--- /dev/null
+++ b/test/resources/config_files/psycopg2/wrong_password.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=psycopg2
+db_name=db
+db_user=db_user
+db_password=wrong
+db_host=127.0.0.1
+db_port=5432
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/charset_invalid.cfg b/test/resources/config_files/pymssql/charset_invalid.cfg
new file mode 100644
index 00000000..dab69516
--- /dev/null
+++ b/test/resources/config_files/pymssql/charset_invalid.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pymssql
+db_name=db
+user=SA
+password=MyPass1234!
+db_host=127.0.0.1
+db_port=1433
+db_charset=wrong
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/custom_param_password.cfg b/test/resources/config_files/pymssql/custom_param_password.cfg
new file mode 100644
index 00000000..ff03d151
--- /dev/null
+++ b/test/resources/config_files/pymssql/custom_param_password.cfg
@@ -0,0 +1,6 @@
+[default]
+db_module=pymssql
+db_name=db
+password=MyPass1234!
+db_host=127.0.0.1
+db_port=1433
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/invalid_custom_params.cfg b/test/resources/config_files/pymssql/invalid_custom_params.cfg
new file mode 100644
index 00000000..1c6c801e
--- /dev/null
+++ b/test/resources/config_files/pymssql/invalid_custom_params.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pymssql
+db_name=db
+db_user=SA
+db_password=MyPass1234!
+db_host=127.0.0.1
+db_port=1433
+blah=blah
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/old_param_names.cfg b/test/resources/config_files/pymssql/old_param_names.cfg
new file mode 100644
index 00000000..00a68add
--- /dev/null
+++ b/test/resources/config_files/pymssql/old_param_names.cfg
@@ -0,0 +1,7 @@
+[default]
+dbapiModuleName=pymssql
+dbName=db
+dbUsername=SA
+dbPassword=MyPass1234!
+dbHost=127.0.0.1
+dbPort=1433
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/simple_default_alias.cfg b/test/resources/config_files/pymssql/simple_default_alias.cfg
new file mode 100644
index 00000000..cd111ab1
--- /dev/null
+++ b/test/resources/config_files/pymssql/simple_default_alias.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pymssql
+db_name=db
+db_user=SA
+db_password=MyPass1234!
+db_host=127.0.0.1
+db_port=1433
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/some_basic_params_missing.cfg b/test/resources/config_files/pymssql/some_basic_params_missing.cfg
new file mode 100644
index 00000000..20e4533b
--- /dev/null
+++ b/test/resources/config_files/pymssql/some_basic_params_missing.cfg
@@ -0,0 +1,3 @@
+[default]
+db_module=pymssql
+db_name=db
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/valid_custom_params.cfg b/test/resources/config_files/pymssql/valid_custom_params.cfg
new file mode 100644
index 00000000..47613a21
--- /dev/null
+++ b/test/resources/config_files/pymssql/valid_custom_params.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pymssql
+db_name=db
+user=SA
+password=MyPass1234!
+db_host=127.0.0.1
+db_port=1433
\ No newline at end of file
diff --git a/test/resources/config_files/pymssql/wrong_password.cfg b/test/resources/config_files/pymssql/wrong_password.cfg
new file mode 100644
index 00000000..04b37f2e
--- /dev/null
+++ b/test/resources/config_files/pymssql/wrong_password.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pymssql
+db_name=db
+db_user=SA
+db_password=wrong
+db_host=127.0.0.1
+db_port=1433
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/charset_invalid.cfg b/test/resources/config_files/pymysql/charset_invalid.cfg
new file mode 100644
index 00000000..9eb9a14f
--- /dev/null
+++ b/test/resources/config_files/pymysql/charset_invalid.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pymysql
+db_name=db
+user=db_user
+password=pass
+db_host=127.0.0.1
+db_port=3306
+db_charset=wrong
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/custom_param_password.cfg b/test/resources/config_files/pymysql/custom_param_password.cfg
new file mode 100644
index 00000000..52b68e76
--- /dev/null
+++ b/test/resources/config_files/pymysql/custom_param_password.cfg
@@ -0,0 +1,6 @@
+[default]
+db_module=pymysql
+db_name=db
+password=pass
+db_host=127.0.0.1
+db_port=3306
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/invalid_custom_params.cfg b/test/resources/config_files/pymysql/invalid_custom_params.cfg
new file mode 100644
index 00000000..46975eff
--- /dev/null
+++ b/test/resources/config_files/pymysql/invalid_custom_params.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pymysql
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=3306
+blah=blah
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/old_param_names.cfg b/test/resources/config_files/pymysql/old_param_names.cfg
new file mode 100644
index 00000000..0d73312d
--- /dev/null
+++ b/test/resources/config_files/pymysql/old_param_names.cfg
@@ -0,0 +1,7 @@
+[default]
+dbapiModuleName=pymysql
+dbName=db
+dbUsername=db_user
+dbPassword=pass
+dbHost=127.0.0.1
+dbPort=3306
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/simple_default_alias.cfg b/test/resources/config_files/pymysql/simple_default_alias.cfg
new file mode 100644
index 00000000..d4242af6
--- /dev/null
+++ b/test/resources/config_files/pymysql/simple_default_alias.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pymysql
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=3306
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/some_basic_params_missing.cfg b/test/resources/config_files/pymysql/some_basic_params_missing.cfg
new file mode 100644
index 00000000..f6c24e4e
--- /dev/null
+++ b/test/resources/config_files/pymysql/some_basic_params_missing.cfg
@@ -0,0 +1,3 @@
+[default]
+db_module=pymysql
+db_name=db
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/valid_custom_params.cfg b/test/resources/config_files/pymysql/valid_custom_params.cfg
new file mode 100644
index 00000000..dcd264a0
--- /dev/null
+++ b/test/resources/config_files/pymysql/valid_custom_params.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pymysql
+db_name=db
+user=db_user
+password=pass
+db_host=127.0.0.1
+db_port=3306
\ No newline at end of file
diff --git a/test/resources/config_files/pymysql/wrong_password.cfg b/test/resources/config_files/pymysql/wrong_password.cfg
new file mode 100644
index 00000000..5d8921ef
--- /dev/null
+++ b/test/resources/config_files/pymysql/wrong_password.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pymysql
+db_name=db
+db_user=db_user
+db_password=wrong
+db_host=127.0.0.1
+db_port=3306
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/charset_invalid.cfg b/test/resources/config_files/pyodbc/charset_invalid.cfg
new file mode 100644
index 00000000..7020a6fd
--- /dev/null
+++ b/test/resources/config_files/pyodbc/charset_invalid.cfg
@@ -0,0 +1,9 @@
+[default]
+db_module=pyodbc
+db_name=db
+user=db_user
+password=pass
+db_host=127.0.0.1
+db_port=3306
+odbc_driver={MySQL ODBC 9.2 ANSI Driver}
+db_charset=wrong
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/custom_param_password.cfg b/test/resources/config_files/pyodbc/custom_param_password.cfg
new file mode 100644
index 00000000..7f64dd2b
--- /dev/null
+++ b/test/resources/config_files/pyodbc/custom_param_password.cfg
@@ -0,0 +1,7 @@
+[default]
+db_module=pyodbc
+db_name=db
+PWD=pass
+db_host=127.0.0.1
+db_port=3306
+odbc_driver={MySQL ODBC 9.2 ANSI Driver}
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/invalid_custom_params.cfg b/test/resources/config_files/pyodbc/invalid_custom_params.cfg
new file mode 100644
index 00000000..e40c70a7
--- /dev/null
+++ b/test/resources/config_files/pyodbc/invalid_custom_params.cfg
@@ -0,0 +1,9 @@
+[default]
+db_module=pyodbc
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=3306
+odbc_driver={MySQL ODBC 9.2 ANSI Driver}
+blah=blah
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/old_param_names.cfg b/test/resources/config_files/pyodbc/old_param_names.cfg
new file mode 100644
index 00000000..3698fd26
--- /dev/null
+++ b/test/resources/config_files/pyodbc/old_param_names.cfg
@@ -0,0 +1,8 @@
+[default]
+dbapiModuleName=pyodbc
+dbName=db
+dbUsername=db_user
+dbPassword=pass
+dbHost=127.0.0.1
+dbPort=3306
+dbDriver={MySQL ODBC 9.2 ANSI Driver}
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/simple_default_alias.cfg b/test/resources/config_files/pyodbc/simple_default_alias.cfg
new file mode 100644
index 00000000..78cbcbfb
--- /dev/null
+++ b/test/resources/config_files/pyodbc/simple_default_alias.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pyodbc
+db_name=db
+db_user=db_user
+db_password=pass
+db_host=127.0.0.1
+db_port=3306
+odbc_driver={MySQL ODBC 9.2 ANSI Driver}
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/some_basic_params_missing.cfg b/test/resources/config_files/pyodbc/some_basic_params_missing.cfg
new file mode 100644
index 00000000..16a3448f
--- /dev/null
+++ b/test/resources/config_files/pyodbc/some_basic_params_missing.cfg
@@ -0,0 +1,3 @@
+[default]
+db_module=pyodbc
+db_name=db
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/valid_custom_params.cfg b/test/resources/config_files/pyodbc/valid_custom_params.cfg
new file mode 100644
index 00000000..fd441a7b
--- /dev/null
+++ b/test/resources/config_files/pyodbc/valid_custom_params.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pyodbc
+db_name=db
+UID=db_user
+PWD=pass
+db_host=127.0.0.1
+db_port=3306
+odbc_driver={MySQL ODBC 9.2 ANSI Driver}
\ No newline at end of file
diff --git a/test/resources/config_files/pyodbc/wrong_password.cfg b/test/resources/config_files/pyodbc/wrong_password.cfg
new file mode 100644
index 00000000..9f50b420
--- /dev/null
+++ b/test/resources/config_files/pyodbc/wrong_password.cfg
@@ -0,0 +1,8 @@
+[default]
+db_module=pyodbc
+db_name=db
+db_user=db_user
+db_password=wrong
+db_host=127.0.0.1
+db_port=3306
+odbc_driver={MySQL ODBC 9.2 ANSI Driver}
\ No newline at end of file
diff --git a/test/resources/config_files/sqlite3/old_param_names.cfg b/test/resources/config_files/sqlite3/old_param_names.cfg
new file mode 100644
index 00000000..4a8047b0
--- /dev/null
+++ b/test/resources/config_files/sqlite3/old_param_names.cfg
@@ -0,0 +1,4 @@
+[default]
+dbapiModuleName=sqlite3
+database=./${DBName}.db
+isolation_level=
\ No newline at end of file
diff --git a/test/resources/config_files/sqlite3/simple_default_alias.cfg b/test/resources/config_files/sqlite3/simple_default_alias.cfg
new file mode 100644
index 00000000..bdc48bd4
--- /dev/null
+++ b/test/resources/config_files/sqlite3/simple_default_alias.cfg
@@ -0,0 +1,4 @@
+[default]
+db_module=sqlite3
+database=./${DBName}.db
+isolation_level=
\ No newline at end of file
diff --git a/test/resources/create_stored_procedures_mssql.sql b/test/resources/create_stored_procedures_mssql.sql
new file mode 100644
index 00000000..14c2ae85
--- /dev/null
+++ b/test/resources/create_stored_procedures_mssql.sql
@@ -0,0 +1,69 @@
+DROP PROCEDURE IF EXISTS no_params;
+CREATE PROCEDURE no_params
+AS
+BEGIN
+-- Do nothing
+RETURN;
+END;
+
+DROP PROCEDURE IF EXISTS get_second_name;
+CREATE PROCEDURE
+get_second_name
+@person_first_name VARCHAR(20)
+AS
+BEGIN
+SELECT LAST_NAME
+FROM person
+WHERE FIRST_NAME = @person_first_name;
+RETURN;
+END;
+
+DROP PROCEDURE IF EXISTS get_all_second_names;
+CREATE PROCEDURE get_all_second_names
+AS
+BEGIN
+SELECT LAST_NAME FROM person;
+RETURN;
+END;
+
+DROP PROCEDURE IF EXISTS get_all_first_and_second_names;
+CREATE PROCEDURE get_all_first_and_second_names
+AS
+BEGIN
+SELECT FIRST_NAME FROM person;
+SELECT LAST_NAME FROM person;
+RETURN;
+END;
+
+DROP PROCEDURE IF EXISTS check_condition;
+CREATE PROCEDURE check_condition
+AS
+BEGIN
+DECLARE @v_condition BIT;
+SET @v_condition = 1;
+IF @v_condition = 1
+BEGIN
+PRINT 'Condition is true';
+END
+ELSE
+BEGIN
+PRINT 'Condition is false';
+END
+END;
+
+DROP PROCEDURE IF EXISTS return_out_param_without_result_sets;
+CREATE PROCEDURE
+return_out_param_without_result_sets
+@my_input VARCHAR(20),
+@my_output INT OUTPUT
+AS
+BEGIN
+ IF @my_input = 'give me 1'
+ BEGIN
+ SELECT @my_output = 1;
+ END
+ ELSE
+ BEGIN
+ SELECT @my_output = 0;
+ END
+END;
\ No newline at end of file
diff --git a/test/resources/create_stored_procedures_mysql.sql b/test/resources/create_stored_procedures_mysql.sql
new file mode 100644
index 00000000..5da8b262
--- /dev/null
+++ b/test/resources/create_stored_procedures_mysql.sql
@@ -0,0 +1,41 @@
+DROP PROCEDURE IF EXISTS no_params;
+CREATE PROCEDURE
+no_params()
+BEGIN
+-- Do nothing
+END;
+
+DROP PROCEDURE IF EXISTS get_second_name;
+CREATE PROCEDURE
+get_second_name (IN person_first_name VARCHAR(20),
+OUT person_second_name VARCHAR(20))
+BEGIN
+SELECT LAST_NAME
+INTO person_second_name
+FROM person
+WHERE FIRST_NAME = person_first_name;
+END;
+
+DROP PROCEDURE IF EXISTS get_all_second_names;
+CREATE PROCEDURE get_all_second_names()
+BEGIN
+SELECT LAST_NAME FROM person;
+END;
+
+DROP PROCEDURE IF EXISTS get_all_first_and_second_names;
+CREATE PROCEDURE get_all_first_and_second_names()
+BEGIN
+SELECT FIRST_NAME FROM person;
+SELECT LAST_NAME FROM person;
+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_procedures_oracle.sql b/test/resources/create_stored_procedures_oracle.sql
new file mode 100644
index 00000000..5e155da3
--- /dev/null
+++ b/test/resources/create_stored_procedures_oracle.sql
@@ -0,0 +1,43 @@
+CREATE OR REPLACE PROCEDURE
+no_params AS
+BEGIN
+DBMS_OUTPUT.PUT_LINE('Hello, World!');
+END;
+
+CREATE OR REPLACE PROCEDURE
+get_second_name (person_first_name IN VARCHAR, person_second_name OUT VARCHAR) AS
+BEGIN
+SELECT last_name
+INTO person_second_name
+FROM person
+WHERE first_name = person_first_name;
+END;
+
+CREATE OR REPLACE PROCEDURE
+get_all_second_names (second_names_cursor OUT SYS_REFCURSOR) AS
+BEGIN
+OPEN second_names_cursor for
+SELECT LAST_NAME FROM person;
+END;
+
+-- parsing the SQL file fails because of the semicolon before the opening of the second cursor
+-- , but it's needed
+CREATE OR REPLACE PROCEDURE
+get_all_first_and_second_names (first_names_cursor OUT SYS_REFCURSOR, second_names_cursor OUT SYS_REFCURSOR) AS
+BEGIN
+OPEN first_names_cursor for
+SELECT FIRST_NAME FROM person;
+OPEN second_names_cursor for
+SELECT LAST_NAME FROM person;
+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/resources/create_stored_procedures_postgres.sql b/test/resources/create_stored_procedures_postgres.sql
new file mode 100644
index 00000000..158f547e
--- /dev/null
+++ b/test/resources/create_stored_procedures_postgres.sql
@@ -0,0 +1,71 @@
+DROP ROUTINE IF EXISTS no_params;
+CREATE FUNCTION no_params()
+RETURNS VOID
+LANGUAGE plpgsql
+AS
+'
+BEGIN
+-- Do nothing
+END
+';
+
+DROP ROUTINE IF EXISTS get_second_name;
+CREATE FUNCTION
+get_second_name (IN person_first_name VARCHAR(20),
+OUT person_second_name VARCHAR(20))
+LANGUAGE plpgsql
+AS
+'
+BEGIN
+SELECT LAST_NAME INTO person_second_name
+FROM person
+WHERE FIRST_NAME = person_first_name;
+END
+';
+
+DROP ROUTINE IF EXISTS get_all_second_names;
+CREATE FUNCTION
+get_all_second_names()
+RETURNS TABLE (second_names VARCHAR(20))
+LANGUAGE plpgsql
+AS
+'
+BEGIN
+RETURN QUERY SELECT LAST_NAME FROM person;
+END
+';
+
+
+DROP ROUTINE IF EXISTS get_all_first_and_second_names;
+CREATE FUNCTION
+get_all_first_and_second_names(result1 refcursor, result2 refcursor)
+RETURNS SETOF refcursor
+LANGUAGE plpgsql
+AS
+'
+BEGIN
+OPEN result1 FOR SELECT FIRST_NAME FROM person;
+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
diff --git a/test/excel_db_test_insertData.sql b/test/resources/excel_db_test_insertData.sql
similarity index 100%
rename from test/excel_db_test_insertData.sql
rename to test/resources/excel_db_test_insertData.sql
diff --git a/test/my_db_test_insertData.sql b/test/resources/insert_data_in_person_table.sql
similarity index 100%
rename from test/my_db_test_insertData.sql
rename to test/resources/insert_data_in_person_table.sql
diff --git a/test/resources/insert_data_in_person_table_utf8.sql b/test/resources/insert_data_in_person_table_utf8.sql
new file mode 100644
index 00000000..7a15eeb9
--- /dev/null
+++ b/test/resources/insert_data_in_person_table_utf8.sql
@@ -0,0 +1 @@
+INSERT INTO person VALUES(1,'Jürgen','Gernegroß')
\ No newline at end of file
diff --git a/test/resources/script_file_tests/semicolons_and_quotes_in_values.sql b/test/resources/script_file_tests/semicolons_and_quotes_in_values.sql
new file mode 100644
index 00000000..d5a846a1
--- /dev/null
+++ b/test/resources/script_file_tests/semicolons_and_quotes_in_values.sql
@@ -0,0 +1 @@
+INSERT INTO person VALUES(5, 'Miles', 'O''Brian');
\ No newline at end of file
diff --git a/test/resources/script_file_tests/semicolons_in_values.sql b/test/resources/script_file_tests/semicolons_in_values.sql
new file mode 100644
index 00000000..47f6f3f4
--- /dev/null
+++ b/test/resources/script_file_tests/semicolons_in_values.sql
@@ -0,0 +1,2 @@
+INSERT INTO person VALUES(3, 'Hello; world', 'Another; value');
+INSERT INTO person VALUES(4, 'May the Force; ', 'be with you;');
\ No newline at end of file
diff --git a/test/resources/script_file_tests/statements_in_one_line.sql b/test/resources/script_file_tests/statements_in_one_line.sql
new file mode 100644
index 00000000..444900cf
--- /dev/null
+++ b/test/resources/script_file_tests/statements_in_one_line.sql
@@ -0,0 +1 @@
+INSERT INTO person VALUES(6, 'Julian', 'Bashir'); INSERT INTO person VALUES(7, 'Jadzia', 'Dax');
\ No newline at end of file
diff --git a/test/testing.sql b/test/testing.sql
deleted file mode 100644
index d63dd0ed..00000000
--- a/test/testing.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-# Simple sql file for testing with RobotFramework-DatabaseLibrary (Python)
-SELECT COUNT(*) FROM data_formats;
diff --git a/test/tests/__init__.py b/test/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/tests/__init__.robot b/test/tests/__init__.robot
new file mode 100644
index 00000000..e62e8551
--- /dev/null
+++ b/test/tests/__init__.robot
@@ -0,0 +1,100 @@
+*** Settings ***
+Documentation Set DB connection variables based on a single global variable
+... which can be passed from outside (e.g. VS Code lauch config)
+
+Suite Setup Set DB Variables
+
+
+*** Variables ***
+${GLOBAL_DB_SELECTOR} None
+
+
+*** Keywords ***
+Set DB Variables
+ [Documentation] These are custom connection params for databases,
+ ... running locally on the developer's machine.
+ ... You might need other values for your databases!
+ IF "${GLOBAL_DB_SELECTOR}" == "PostgreSQL"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} psycopg2
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 5432
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "oracledb"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} oracledb
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 1521
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "cx_Oracle"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} cx_Oracle
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 1521
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "SQLite"
+ Set Global Variable ${DB_MODULE_MODE} custom
+ Set Global Variable ${DB_MODULE} sqlite3
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "IBM_DB2"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} ibm_db_dbi
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 50000
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "Teradata"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} teradata
+ Set Global Variable ${DB_HOST} 192.168.0.231
+ Set Global Variable ${DB_PORT} 1025
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} dbc
+ Set Global Variable ${DB_PASS} dbc
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "MySQL_pymysql"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} pymysql
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 3306
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "MySQL_pyodbc"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} pyodbc
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 3306
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "Oracle_JDBC"
+ Set Global Variable ${DB_MODULE_MODE} custom
+ Set Global Variable ${DB_MODULE} jaydebeapi
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 1521
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} db_user
+ Set Global Variable ${DB_PASS} pass
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "MSSQL"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} pymssql
+ Set Global Variable ${DB_HOST} 127.0.0.1
+ Set Global Variable ${DB_PORT} 1433
+ Set Global Variable ${DB_NAME} db
+ Set Global Variable ${DB_USER} SA
+ Set Global Variable ${DB_PASS} MyPass1234!
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "Excel"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} excel
+ Set Global Variable ${DB_NAME} db
+ ELSE IF "${GLOBAL_DB_SELECTOR}" == "Excel_RW"
+ Set Global Variable ${DB_MODULE_MODE} standard
+ Set Global Variable ${DB_MODULE} excelrw
+ Set Global Variable ${DB_NAME} db
+ END
diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot
new file mode 100644
index 00000000..e61b3904
--- /dev/null
+++ b/test/tests/common_tests/aliased_connection.robot
@@ -0,0 +1,132 @@
+*** Settings ***
+Resource ../../resources/common.resource
+Suite Setup Skip If "${DB_MODULE}" == "sqlite3"
+... Aliases tests don't work for SQLite as each connection is always a new file
+
+Test Setup Connect, Create Some Data And Disconnect
+Test Teardown Connect, Clean Up Data And Disconnect
+
+
+*** Test Cases ***
+Connections Can Be Aliased
+ Connect To DB # default alias
+ Connect To DB alias=second
+
+Default Alias Can Be Empty
+ Connect To DB # default alias
+ Query SELECT * FROM person
+ Connect To DB alias=second
+ Query SELECT * FROM person
+ Query SELECT * FROM person alias=second
+
+Switch From Default And Disconnect
+ Connect To DB # default alias
+ Connect To DB alias=second
+ Switch Database second
+ Query SELECT * FROM person # query with 'second' connection
+ Disconnect From Database alias=second
+ Query SELECT * FROM person # query with 'default' connection
+
+Disconnect Not Existing Alias
+ Connect To DB # default alias
+ Disconnect From Database alias=idontexist # silent warning
+ Run Keyword And Expect Error ConnectionError: No open database connection to close
+ ... Disconnect From Database alias=idontexist error_if_no_connection=${True}
+ # default alias exist and can be closed
+ Disconnect From Database error_if_no_connection=${True}
+
+Switch Not Existing Alias
+ Run Keyword And Expect Error ValueError: Alias 'second' not found in existing connections.
+ ... Switch Database second
+
+Execute SQL Script - Insert Data In Person table
+ [Setup] Connect, Create Some Data And Disconnect Run SQL script=${False}
+ Connect To DB alias=aliased_conn
+ ${output} Insert Data In Person Table Using SQL Script alias=aliased_conn
+ Should Be Equal As Strings ${output} None
+
+Check If Exists In DB - Franz Allan
+ Connect To DB alias=aliased_conn
+ Check If Exists In Database SELECT id FROM person WHERE FIRST_NAME= 'Franz Allan' alias=aliased_conn
+
+Check If Not Exists In DB - Joe
+ Connect To DB alias=aliased_conn
+ Check If Not Exists In Database SELECT id FROM person WHERE FIRST_NAME= 'Joe' alias=aliased_conn
+
+Table Must Exist - person
+ Connect To DB alias=aliased_conn
+ Table Must Exist person alias=aliased_conn
+
+Verify Row Count is 0
+ Connect To DB alias=aliased_conn
+ Row Count is 0 SELECT * FROM person WHERE FIRST_NAME= 'NotHere' alias=aliased_conn
+
+Verify Row Count is Equal to X
+ Connect To DB alias=aliased_conn
+ Row Count is Equal to X SELECT id FROM person 2 alias=aliased_conn
+
+Verify Row Count is Less Than X
+ Connect To DB alias=aliased_conn
+ Row Count is Less Than X SELECT id FROM person 3 alias=aliased_conn
+
+Verify Row Count is Greater Than X
+ Connect To DB alias=aliased_conn
+ Row Count is Greater Than X SELECT * FROM person 1 alias=aliased_conn
+
+Retrieve Row Count
+ Connect To DB alias=aliased_conn
+ ${output} Row Count SELECT id FROM person alias=aliased_conn
+ Log ${output}
+ Should Be Equal As Strings ${output} 2
+
+Retrieve records from person table
+ Connect To DB alias=aliased_conn
+ ${output} Execute SQL String SELECT * FROM person
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Use Last Connected If Not Alias Provided
+ Connect To DB alias=aliased_conn
+ ${output} Query SELECT COUNT(*) FROM person
+ Log ${output}
+ Should Be Equal As Integers ${output}[0][0] 2
+
+Verify Query - Get results as a list of dictionaries
+ Connect To DB alias=aliased_conn
+ ${output} Query SELECT * FROM person returnAsDict=True alias=aliased_conn
+ Log ${output}
+ # some databases lower field names and you can't do anything about it
+ TRY
+ ${value 1} Get From Dictionary ${output}[0] FIRST_NAME
+ EXCEPT Dictionary does not contain key 'FIRST_NAME'.
+ ${value 1} Get From Dictionary ${output}[0] first_name
+ END
+ TRY
+ ${value 2} Get From Dictionary ${output}[1] FIRST_NAME
+ EXCEPT Dictionary does not contain key 'FIRST_NAME'.
+ ${value 2} Get From Dictionary ${output}[1] first_name
+ END
+ Should Be Equal As Strings ${value 1} Franz Allan
+ Should Be Equal As Strings ${value 2} Jerry
+
+Verify Delete All Rows From Table
+ Connect To DB alias=aliased_conn
+ Delete All Rows From Table person alias=aliased_conn
+ Row Count Is 0 SELECT * FROM person alias=aliased_conn
+
+
+*** Keywords ***
+Connect, Create Some Data And Disconnect
+ [Arguments] ${Run SQL script}=${True}
+ Connect To DB
+ Create Person Table
+ IF $Run_SQL_script
+ Insert Data In Person Table Using SQL Script
+ END
+ Disconnect From Database
+
+Connect, Clean Up Data And Disconnect
+ Disconnect From All Databases
+ Connect To DB
+ Drop Tables Person And Foobar
+ Disconnect From Database
diff --git a/test/tests/common_tests/assertion_error_messages.robot b/test/tests/common_tests/assertion_error_messages.robot
new file mode 100644
index 00000000..2ab45a3c
--- /dev/null
+++ b/test/tests/common_tests/assertion_error_messages.robot
@@ -0,0 +1,138 @@
+*** Settings ***
+Documentation Simulate keyword fails and check that
+... using custom and starndard error messages work as expected
+
+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 ***
+${Error Message} My error message
+${Non Existing Select} SELECT id FROM person WHERE first_name = 'Joe'
+${Existing Select} SELECT id FROM person WHERE first_name = 'Franz Allan'
+
+
+*** Test Cases ***
+Check If Exists In DB Fails
+ ${expected error}= Catenate
+ ... Expected to have have at least one row, but got 0 rows from:
+ ... '${Non Existing Select}'
+ Run Keyword And Expect Error ${expected error}
+ ... Check If Exists In Database ${Non Existing Select}
+
+Check If Exists In DB Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Check If Exists In Database ${Non Existing Select}
+ ... msg=${Error Message}
+
+Check If Not Exists In DB Fails
+ ${expected error}= Catenate
+ ... Expected to have have no rows from
+ ... '${Existing Select}',
+ ... but got some rows: *
+ Run Keyword And Expect Error ${expected error}
+ ... Check If Not Exists In Database ${Existing Select}
+
+Check If Not Exists In DB Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Check If Not Exists In Database ${Existing Select}
+ ... msg=${Error Message}
+
+Table Must Exist Fails
+ ${expected error}= Catenate
+ ... Table 'nonexistent' does not exist in the db
+ Run Keyword And Expect Error ${expected error}
+ ... Table Must Exist nonexistent
+
+Table Must Exist Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Table Must Exist nonexistent
+ ... msg=${Error Message}
+
+Verify Row Count Is 0 Fails
+ ${expected error}= Catenate
+ ... Expected 0 rows, but 1 were returned from:
+ ... '${Existing Select}'
+ Run Keyword And Expect Error ${expected error}
+ ... Row Count Is 0 ${Existing Select}
+
+Verify Row Count Is 0 Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Row Count Is 0 ${Existing Select}
+ ... msg=${Error Message}
+
+Verify Row Count Is Equal To X Fails
+ ${expected error}= Catenate
+ ... Expected 9 rows, but 1 were returned from:
+ ... '${Existing Select}'
+ Run Keyword And Expect Error ${expected error}
+ ... Row Count Is Equal To X ${Existing Select} 9
+
+Verify Row Count Is Equal To X Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Row Count Is Equal To X
+ ... ${Existing Select} 9 msg=${Error Message}
+
+Verify Row Count Is Less Than X Fails
+ ${expected error}= Catenate
+ ... Expected less than 1 rows, but 1 were returned from
+ ... '${Existing Select}'
+ Run Keyword And Expect Error ${expected error}
+ ... Row Count Is Less Than X
+ ... ${Existing Select} 1
+
+Verify Row Count Is Less Than X Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Row Count Is Less Than X
+ ... ${Existing Select} 1 msg=${Error Message}
+
+Verify Row Count Is Greater Than X Fails
+ ${expected error}= Catenate
+ ... Expected more than 1 rows, but 1 were returned from
+ ... '${Existing Select}'
+ Run Keyword And Expect Error ${expected error}
+ ... Row Count Is Greater Than X
+ ... ${Existing Select} 1
+
+Verify Row Count Is Greater Than X Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Row Count Is Greater Than X
+ ... ${Existing Select} 1
+ ... msg=${Error Message}
+
+Check Row Count With Assertion Engine Fails
+ ${expected value}= Set Variable 5
+ ${expected error}= Catenate
+ ... Wrong row count: '1' (int) should be '${expected value}' (int)
+ Run Keyword And Expect Error
+ ... ${expected error}
+ ... Check Row Count ${Existing Select} equals ${expected value}
+
+Check Row Count With Assertion Engine Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Check Row Count ${Existing Select} less than 1
+ ... assertion_message=${Error Message}
+
+
+Check Query Result With Assertion Engine Fails
+ ${expected value}= Set Variable ${5}
+ IF "${DB_MODULE}" == "jaydebeapi"
+ VAR ${Num Type}= jlong
+ ELSE
+ VAR ${Num Type}= int
+ END
+ ${expected error}= Catenate
+ ... Wrong query result: '1' (${Num Type}) should be '${expected value}' (int)
+ Run Keyword And Expect Error
+ ... ${expected error}
+ ... Check Query Result ${Existing Select} equals ${expected value}
+
+
+Check Query Result With Assertion Engine Fails With Message
+ Run Keyword And Expect Error ${Error Message}
+ ... Check Query Result ${Existing Select} less than ${1}
+ ... assertion_message=${Error Message}
diff --git a/test/tests/common_tests/assertion_retry.robot b/test/tests/common_tests/assertion_retry.robot
new file mode 100644
index 00000000..47921da3
--- /dev/null
+++ b/test/tests/common_tests/assertion_retry.robot
@@ -0,0 +1,60 @@
+*** Settings ***
+Documentation Tests for assertion keywords with retry mechanism
+
+Resource ../../resources/common.resource
+
+Suite Setup Connect To DB And Prepare Data
+Suite Teardown Delete Data And Disconnect
+Test Setup Save Start Time
+
+*** Variables ***
+${Timeout} ${3}
+${Tolerance} ${0.5}
+${Request} SELECT first_name FROM person
+
+*** Test Cases ***
+Check Query Results With Timeout - Fast If DB Ready
+ Check Query Result ${Request} contains Allan retry_timeout=${Timeout} seconds
+ ${End time}= Get Current Date
+ ${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
+ Should Be True 0 <= $Execution_time <= $Tolerance
+
+Check Query Results With Timeout - Slow If Result Wrong
+ Run Keyword And Expect Error Wrong query result: 'Franz Allan' (str) should contain 'Blah' (str)
+ ... Check Query Result ${Request} contains Blah retry_timeout=${Timeout} seconds retry_pause=1s
+ ${End time}= Get Current Date
+ ${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
+ Should Be True $Timeout <= $Execution_time <= $Timeout + $Tolerance
+
+Check Query Results With Timeout - Slow If Row Count Wrong
+ Run Keyword And Expect Error Checking row '5' is not possible, as query results contain 2 rows only!
+ ... Check Query Result ${Request} contains Blah row=5 retry_timeout=${Timeout} seconds
+ ${End time}= Get Current Date
+ ${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
+ Should Be True $Timeout <= $Execution_time <= $Timeout + $Tolerance
+
+Check Row Count With Timeout - Fast If DB Ready
+ Check Row Count ${Request} == 2 retry_timeout=${Timeout} seconds
+ ${End time}= Get Current Date
+ ${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
+ Should Be True 0 <= $Execution_time <= $Tolerance
+
+Check Row Count With Timeout - Slow If Result Wrong
+ Run Keyword And Expect Error Wrong row count: '2' (int) should be greater than '5' (int)
+ ... Check Row Count ${Request} > 5 retry_timeout=${Timeout} seconds retry_pause=1s
+ ${End time}= Get Current Date
+ ${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
+ Should Be True $Timeout <= $Execution_time <= $Timeout + $Tolerance
+
+*** Keywords ***
+Connect To DB And Prepare Data
+ Connect To DB
+ Create Person Table And Insert Data
+
+Delete Data And Disconnect
+ Drop Tables Person And Foobar
+ Disconnect From Database
+
+Save Start Time
+ ${START_TIME}= Get Current Date
+ Set Suite Variable ${START_TIME}
diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot
new file mode 100644
index 00000000..adbd93ed
--- /dev/null
+++ b/test/tests/common_tests/basic_tests.robot
@@ -0,0 +1,159 @@
+*** 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 Person Table And Insert Data
+Test Teardown Drop Tables Person And Foobar
+
+
+*** Test Cases ***
+SQL Statement Ending Without Semicolon Works
+ Query SELECT * FROM person
+
+SQL Statement Ending With Semicolon Works
+ Query SELECT * FROM person;
+
+Create Person Table
+ [Setup] Log No setup for this test
+ ${output}= Create Person Table
+ Should Be Equal As Strings ${output} None
+
+Execute SQL Script - Insert Data In Person table
+ [Setup] Create Person Table
+ ${output}= Insert Data In Person Table Using SQL Script
+ Should Be Equal As Strings ${output} None
+
+Execute SQL String - Create Foobar Table
+ [Setup] Log No setup for this test
+ ${output}= Create Foobar Table
+ Should Be Equal As Strings ${output} None
+
+Simple Select With Multiple Rows
+ ${output}= Query select LAST_NAME from person
+ Length Should Be ${output} 2
+ Should Be Equal ${output}[0][0] See
+ Should Be Equal ${output}[1][0] Schneider
+
+Check If Exists In DB - Franz Allan
+ Check If Exists In Database SELECT id FROM person WHERE FIRST_NAME= 'Franz Allan'
+
+Check If Not Exists In DB - Joe
+ Check If Not Exists In Database SELECT id FROM person WHERE FIRST_NAME= 'Joe'
+
+Table Must Exist - person
+ Table Must Exist person
+
+Verify Row Count is 0
+ Row Count is 0 SELECT * FROM person WHERE FIRST_NAME= 'NotHere'
+
+Verify Row Count is Equal to X
+ Row Count is Equal to X SELECT id FROM person 2
+
+Verify Row Count is Less Than X
+ Row Count is Less Than X SELECT id FROM person 3
+
+Verify Row Count is Greater Than X
+ Row Count is Greater Than X SELECT * FROM person 1
+
+Retrieve Row Count
+ ${output}= Row Count SELECT id FROM person
+ Log ${output}
+ Should Be Equal As Strings ${output} 2
+
+Check Row Count With Assertion Engine
+ Check Row Count SELECT id FROM person == 2
+
+Check Query Result With Assertion Engine
+ Check Query Result SELECT first_name FROM person contains Allan
+
+Check Query Result With Assertion Engine - Different Row And Col
+ Check Query Result SELECT first_name, last_name, id FROM person >= ${2} row=1 col=2
+
+Check Query Result With Assertion Engine - Row Out Of Range
+ Run Keyword And Expect Error Checking row '2' is not possible, as query results contain 2 rows only!
+ ... Check Query Result SELECT first_name FROM person == Blah row=2
+
+Check Query Result With Assertion Engine - Col Out Of Range
+ Run Keyword And Expect Error Checking column '5' is not possible, as query results contain 2 columns only!
+ ... Check Query Result SELECT id, first_name FROM person == Blah col=5
+
+Retrieve records from person table
+ ${output}= Execute SQL String SELECT * FROM person
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify Query - Row Count person table
+ ${output}= Query SELECT COUNT(*) FROM person
+ Log ${output}
+ Should Be Equal As Integers ${output}[0][0] 2
+
+Verify Query - Row Count foobar table
+ [Setup] Create Foobar Table
+ ${output}= Query SELECT COUNT(*) FROM foobar
+ Log ${output}
+ Should Be Equal As Integers ${output}[0][0] 0
+
+Verify Query - Get results as a list of dictionaries
+ ${output}= Query SELECT * FROM person returnAsDict=True
+ Log ${output}
+ # some databases lower field names and you can't do anything about it
+ TRY
+ ${value 1}= Get From Dictionary ${output}[0] FIRST_NAME
+ EXCEPT Dictionary does not contain key 'FIRST_NAME'.
+ ${value 1}= Get From Dictionary ${output}[0] first_name
+ END
+ TRY
+ ${value 2}= Get From Dictionary ${output}[1] FIRST_NAME
+ EXCEPT Dictionary does not contain key 'FIRST_NAME'.
+ ${value 2}= Get From Dictionary ${output}[1] first_name
+ END
+ Should Be Equal As Strings ${value 1} Franz Allan
+ Should Be Equal As Strings ${value 2} Jerry
+
+Return As Dictionary - Dotted Syntax
+ ${output}= Query SELECT * FROM person return_dict=True
+ ${field_names}= Get Dictionary Keys ${output}[0]
+ IF "FIRST_NAME" in $field_names
+ VAR ${field_name}= FIRST_NAME
+ ELSE IF "first_name" in $field_names
+ VAR ${field_name}= first_name
+ ELSE
+ FAIL Unexpected field name in dictionary
+ END
+ Should Be Equal As Strings ${output[0].${field_name}} Franz Allan
+ Should Be Equal As Strings ${output[1].${field_name}} Jerry
+
+Verify Execute SQL String - Row Count person table
+ ${output}= Execute SQL String SELECT COUNT(*) FROM person
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify Execute SQL String - Row Count foobar table
+ [Setup] Create Foobar Table
+ ${output}= Execute SQL String SELECT COUNT(*) FROM foobar
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Insert Data Into Table foobar
+ [Setup] Create Foobar Table
+ ${output}= Execute SQL String INSERT INTO foobar VALUES(1,'Jerry')
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify Query - Row Count foobar table 1 row
+ [Setup] Create Foobar Table And Insert Data
+ ${output}= Query SELECT COUNT(*) FROM foobar
+ Log ${output}
+ Should Be Equal As Integers ${output}[0][0] 1
+
+Verify Delete All Rows From Table - foobar
+ [Setup] Create Foobar Table And Insert Data
+ Delete All Rows From Table foobar
+
+Verify Query - Row Count foobar table 0 row
+ [Setup] Create Foobar Table And Insert Data
+ Delete All Rows From Table foobar
+ Row Count Is 0 SELECT * FROM foobar
diff --git a/test/tests/common_tests/connection_params.robot b/test/tests/common_tests/connection_params.robot
new file mode 100644
index 00000000..75f59ab5
--- /dev/null
+++ b/test/tests/common_tests/connection_params.robot
@@ -0,0 +1,174 @@
+*** Settings ***
+Documentation Tests for the basic _Connect To Database_ keyword - with and without config files.
+... The parameter handling is partly DB module specific.
+
+Resource ../../resources/common.resource
+
+Test Setup Skip If $DB_MODULE == "sqlite3" or $DB_MODULE == "jaydebeapi"
+Test Teardown Disconnect From Database
+
+*** Variables ***
+&{Errors psycopg2}
+... missing basic params=OperationalError: connection to server on socket *
+... invalid custom param=ProgrammingError: invalid dsn: invalid connection option "blah"*
+&{Errors oracledb}
+... missing basic params=DatabaseError: DPY-4001: no credentials specified
+... invalid custom param=TypeError: connect() got an unexpected keyword argument 'blah'
+&{Errors pymssql}
+... missing basic params=OperationalError: (20002, b'DB-Lib error message 20002, severity 9*
+... invalid custom param=TypeError: connect() got an unexpected keyword argument 'blah'
+&{Errors pymysql}
+... missing basic params=OperationalError: (1045, "Access denied*
+... invalid custom param=REGEXP: TypeError.*__init__.*got an unexpected keyword argument 'blah'
+&{Errors pyodbc}
+... missing basic params=REGEXP: InterfaceError.*Data source name not found and no default driver specified.*
+
+&{Errors}
+... psycopg2=${Errors psycopg2}
+... oracledb=${Errors oracledb}
+... pymssql=${Errors pymssql}
+... pymysql=${Errors pymysql}
+... pyodbc=${Errors pyodbc}
+
+
+*** Test Cases ***
+Mandatory params can't be missing
+ Run Keyword And Expect Error
+ ... ValueError: Required parameter 'db_module' was not provided*
+ ... Connect To Database db_name=${DB_NAME}
+
+All basic params, no config file
+ Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_user=${DB_USER}
+ ... db_password=${DB_PASS}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ ... odbc_driver=${DB_DRIVER}
+
+Missing basic params are accepted, error from Python DB module
+ Run Keyword And Expect Error
+ ... ${Errors}[${DB_MODULE}][missing basic params]
+ ... Connect To Database
+ ... db_module=${DB_MODULE}
+
+Custom params as keyword args - valid
+ Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ ... odbc_driver=${DB_DRIVER}
+ ... user=${DB_USER}
+ ... password=${DB_PASS}
+
+Custom params as keyword args - invalid, error from Python DB module
+ Skip If $DB_MODULE == "pyodbc"
+ ... pyodbc doesn't always throw an error if some wrong parameter was provided
+ Run Keyword And Expect Error
+ ... ${Errors}[${DB_MODULE}][invalid custom param]
+ ... Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ ... db_user=${DB_USER}
+ ... db_password=${DB_PASS}
+ ... odbc_driver=${DB_DRIVER}
+ ... blah=blah
+
+All basic params in config file
+ Connect Using Config File ${DB_MODULE}/simple_default_alias
+
+Deprecated basic params in config file
+ Connect Using Config File ${DB_MODULE}/old_param_names
+
+Missing basic params in config file are accepted, error from Python DB module
+ Run Keyword And Expect Error
+ ... ${Errors}[${DB_MODULE}][missing basic params]
+ ... Connect Using Config File
+ ... ${DB_MODULE}/some_basic_params_missing
+
+Custom params from config file - valid
+ Connect Using Config File ${DB_MODULE}/valid_custom_params
+
+Custom params from config file - invalid, error from Python DB module
+ Skip If $DB_MODULE == "pyodbc"
+ ... pyodbc doesn't always throw an error if some wrong parameter was provided
+ Run Keyword And Expect Error
+ ... ${Errors}[${DB_MODULE}][invalid custom param]
+ ... Connect Using Config File ${DB_MODULE}/invalid_custom_params
+
+Custom params as keyword args combined with custom params from config file
+ Connect Using Config File ${DB_MODULE}/custom_param_password
+ ... user=${DB_USER}
+
+
+Keyword args override config file values - basic params
+ Connect Using Config File ${DB_MODULE}/wrong_password
+ ... db_password=${DB_PASS}
+
+Keyword args override config file values - custom params
+ Connect Using Config File ${DB_MODULE}/valid_custom_params
+ ... user=${DB_USER}
+
+Oracle specific - basic params, no config file, oracle_driver_mode
+ Skip If $DB_MODULE != "oracledb"
+ Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_user=${DB_USER}
+ ... db_password=${DB_PASS}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ ... oracle_driver_mode=thin
+
+Oracle specific - thick mode in config file - invalid
+ [Documentation] Invalid as mode switch during test execution is not supported
+ ... This test must run the last one in the suite, after others used thin mode already.
+ Skip If $DB_MODULE != "oracledb"
+ Run Keyword And Expect Error ProgrammingError: DPY-2019: python-oracledb thick mode cannot be used *
+ ... Connect Using Config File ${DB_MODULE}/thick_mode
+
+
+MSSQL / MySQL / PyODBC specific - charset as keyword argument
+ Skip If $DB_MODULE not in ["pymssql", "pymysql", "pyodbc"]
+ Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_user=${DB_USER}
+ ... db_password=${DB_PASS}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ ... odbc_driver=${DB_DRIVER}
+ ... db_charset=LATIN1
+
+MSSQL specific - charset in config file - invalid
+ Skip If $DB_MODULE not in ["pymssql"]
+ Run Keyword And Expect Error OperationalError: (20002, b'Unknown error')
+ ... Connect Using Config File ${DB_MODULE}/charset_invalid
+
+MySQL specific - charset in config file - invalid
+ Skip If $DB_MODULE not in ["pymysql"]
+ Run Keyword And Expect Error AttributeError: 'NoneType' object has no attribute 'encoding'
+ ... Connect Using Config File ${DB_MODULE}/charset_invalid
+
+PyODBC specific - charset in config file - invalid
+ Skip If $DB_MODULE not in ["pyodbc"]
+ Run Keyword And Expect Error REGEXP: .*Unknown character set: 'wrong'.*
+ ... Connect Using Config File ${DB_MODULE}/charset_invalid
+
+
+SQlite specific - connection params as custom keyword args
+ [Setup] Skip If $DB_MODULE != "sqlite3"
+ Remove File ${DBName}.db
+ Connect To Database
+ ... db_module=${DB_MODULE}
+ ... database=./${DBName}.db
+ ... isolation_level=${EMPTY}
+
+SQlite specific - custom connection params in config file
+ [Setup] Skip If $DB_MODULE != "sqlite3"
+ Remove File ${DBName}.db
+ Connect Using Config File ${DB_MODULE}/simple_default_alias
diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot
new file mode 100644
index 00000000..1e27e8c9
--- /dev/null
+++ b/test/tests/common_tests/custom_connection.robot
@@ -0,0 +1,51 @@
+*** Settings ***
+Documentation Keyword 'Connect To Database Using Custom Params' should work properly
+... for different DB modules.
+
+Resource ../../resources/common.resource
+
+Test Teardown Disconnect From Database
+
+
+*** Variables ***
+${CONNECTION_STRING} ${EMPTY} # the variable is set dynamically depending on the current DB module
+
+
+*** Test Cases ***
+Connect Using Custom Connection String
+ [Documentation] Connection string provided without additional quotes should work properly.
+ ${Connection String}= Build Connection String
+ Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String}
+
+Connect Using Custom Params
+ IF "${DB_MODULE}" == "oracledb"
+ ${Params}= Catenate
+ ... user='${DB_USER}',
+ ... password='${DB_PASS}',
+ ... dsn='${DB_HOST}:${DB_PORT}/${DB_NAME}'
+ ELSE IF "${DB_MODULE}" == "pyodbc"
+ ${Params}= Catenate
+ ... driver='${DB_DRIVER}',
+ ... charset='${DB_CHARSET}',
+ ... database='${DB_NAME}',
+ ... user='${DB_USER}',
+ ... password='${DB_PASS}',
+ ... host='${DB_HOST}',
+ ... port=${DB_PORT}
+ ELSE IF "${DB_MODULE}" == "sqlite3"
+ ${Params}= Catenate
+ ... database="./${DBName}.db",
+ ... isolation_level=None
+ ELSE IF "${DB_MODULE}" == "jaydebeapi"
+ Skip Connecting with custom params for Jaydebeapi is already done in all other tests
+ ELSE
+ ${Params}= Catenate
+ ... database='${DB_NAME}',
+ ... user='${DB_USER}',
+ ... password='${DB_PASS}',
+ ... host='${DB_HOST}',
+ ... port=${DB_PORT}
+ END
+ Connect To Database Using Custom Params
+ ... ${DB_MODULE}
+ ... ${Params}
diff --git a/test/tests/common_tests/description.robot b/test/tests/common_tests/description.robot
new file mode 100644
index 00000000..70b0dcfe
--- /dev/null
+++ b/test/tests/common_tests/description.robot
@@ -0,0 +1,103 @@
+*** Settings ***
+Documentation The result of the "description" request is very different depending on the database
+
+Resource ../../resources/common.resource
+
+Suite Setup Connect To DB
+Suite Teardown Disconnect From Database
+Test Setup Create Tables Person And Foobar
+Test Teardown Drop Tables Person And Foobar
+
+
+*** Test Cases ***
+Verify Person Description
+ @{queryResults} = Description SELECT * FROM person
+ Log Many @{queryResults}
+ Length Should Be ${queryResults} 3
+ IF "${DB_MODULE}" == "oracledb"
+ Should Be Equal As Strings ${queryResults}[0] ('ID', , 39, None, 38, 0, False)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', , 20, 20, None, None, True)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', , 20, 20, None, None, True)
+ ELSE IF "${DB_MODULE}" == "jaydebeapi"
+ Should Be Equal As Strings ${queryResults}[0] ('ID', DBAPITypeObject('DECIMAL', 'NUMERIC'), 39, 39, 38, 0, 0)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', DBAPITypeObject('CHAR', 'NCHAR', 'NVARCHAR', 'VARCHAR', 'OTHER'), 20, 20, 20, 0, 1)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', DBAPITypeObject('CHAR', 'NCHAR', 'NVARCHAR', 'VARCHAR', 'OTHER'), 20, 20, 20, 0, 1)
+ ELSE IF "${DB_MODULE}" == "sqlite3"
+ Should Be Equal As Strings ${queryResults}[0] ('id', None, None, None, None, None, None)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', None, None, None, None, None, None)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', None, None, None, None, None, None)
+ ELSE IF "${DB_MODULE}" == "ibm_db_dbi"
+ Should Be True "${queryResults}[0]".startswith("['ID', DBAPITypeObject(")
+ Should Be True "${queryResults}[0]".endswith("), 11, 11, 10, 0, False]")
+ Should Be True "INT" in "${queryResults}[0]"
+ Should Be True "${queryResults}[1]".startswith("['FIRST_NAME', DBAPITypeObject(")
+ Should Be True "${queryResults}[1]".endswith("), 20, 20, 20, 0, True]")
+ Should Be True "VARCHAR" in "${queryResults}[1]"
+ Should Be True "${queryResults}[2]".startswith("['LAST_NAME', DBAPITypeObject(")
+ Should Be True "${queryResults}[2]".endswith("), 20, 20, 20, 0, True]")
+ Should Be True "VARCHAR" in "${queryResults}[2]"
+ ELSE IF "${DB_MODULE}" == "teradata"
+ Should Be Equal As Strings ${queryResults}[0] ('id', , None, 10, 0, None, 0)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', , None, 20, 0, None, 1)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', , None, 20, 0, None, 1)
+ ELSE IF "${DB_MODULE}" == "psycopg2"
+ Should Be Equal As Strings ${queryResults}[0] Column(name='id', type_code=23)
+ Should Be Equal As Strings ${queryResults}[1] Column(name='first_name', type_code=1043)
+ Should Be Equal As Strings ${queryResults}[2] Column(name='last_name', type_code=1043)
+ ELSE IF "${DB_MODULE}" == "pymysql"
+ Should Be Equal As Strings ${queryResults}[0] ('id', 3, None, 11, 11, 0, False)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', 253, None, 80, 80, 0, True)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', 253, None, 80, 80, 0, True)
+ ELSE IF "${DB_MODULE}" == "pyodbc"
+ Should Be Equal As Strings ${queryResults}[0] ('id', , None, 10, 10, 0, False)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', , None, 20, 20, 0, True)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', , None, 20, 20, 0, True)
+ ELSE IF "${DB_MODULE}" == "pymssql"
+ Should Be Equal As Strings ${queryResults}[0] ('id', 3, None, None, None, None, None)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', 1, None, None, None, None, None)
+ Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', 1, None, None, None, None, None)
+ ELSE
+ Should Be Equal As Strings ${queryResults}[0] Column(name='id', type_code=23)
+ Should Be Equal As Strings ${queryResults}[1] Column(name='first_name', type_code=1043)
+ Should Be Equal As Strings ${queryResults}[2] Column(name='last_name', type_code=1043)
+ END
+
+Verify Foobar Description
+ @{queryResults} = Description SELECT * FROM foobar
+ Log Many @{queryResults}
+ Length Should Be ${queryResults} 2
+ IF "${DB_MODULE}" == "oracledb"
+ Should Be Equal As Strings ${queryResults}[0] ('ID', , 39, None, 38, 0, False)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', , 30, 30, None, None, False)
+ ELSE IF "${DB_MODULE}" == "jaydebeapi"
+ Should Be Equal As Strings ${queryResults}[0] ('ID', DBAPITypeObject('DECIMAL', 'NUMERIC'), 39, 39, 38, 0, 0)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', DBAPITypeObject('CHAR', 'NCHAR', 'NVARCHAR', 'VARCHAR', 'OTHER'), 30, 30, 30, 0, 0)
+ ELSE IF "${DB_MODULE}" == "sqlite3"
+ Should Be Equal As Strings ${queryResults}[0] ('id', None, None, None, None, None, None)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', None, None, None, None, None, None)
+ ELSE IF "${DB_MODULE}" == "ibm_db_dbi"
+ Should Be True "${queryResults}[0]".startswith("['ID', DBAPITypeObject(")
+ Should Be True "${queryResults}[0]".endswith("), 11, 11, 10, 0, False]")
+ Should Be True "INT" in "${queryResults}[0]"
+ Should Be True "${queryResults}[1]".startswith("['FIRST_NAME', DBAPITypeObject(")
+ Should Be True "${queryResults}[1]".endswith("), 30, 30, 30, 0, False]")
+ Should Be True "VARCHAR" in "${queryResults}[1]"
+ ELSE IF "${DB_MODULE}" == "teradata"
+ Should Be Equal As Strings ${queryResults}[0] ('id', , None, 10, 0, None, 0)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', , None, 30, 0, None, 0)
+ ELSE IF "${DB_MODULE}" == "psycopg2"
+ Should Be Equal As Strings ${queryResults}[0] Column(name='id', type_code=23)
+ Should Be Equal As Strings ${queryResults}[1] Column(name='first_name', type_code=1043)
+ ELSE IF "${DB_MODULE}" == "pymysql"
+ Should Be Equal As Strings ${queryResults}[0] ('id', 3, None, 11, 11, 0, False)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', 253, None, 120, 120, 0, False)
+ ELSE IF "${DB_MODULE}" in "pyodbc"
+ Should Be Equal As Strings ${queryResults}[0] ('id', , None, 10, 10, 0, False)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', , None, 30, 30, 0, False)
+ ELSE IF "${DB_MODULE}" == "pymssql"
+ Should Be Equal As Strings ${queryResults}[0] ('id', 3, None, None, None, None, None)
+ Should Be Equal As Strings ${queryResults}[1] ('FIRST_NAME', 1, None, None, None, None, None)
+ ELSE
+ Should Be Equal As Strings ${queryResults}[0] Column(name='id', type_code=23)
+ Should Be Equal As Strings ${queryResults}[1] Column(name='first_name', type_code=1043)
+ END
diff --git a/test/tests/common_tests/disconnect_from_db.robot b/test/tests/common_tests/disconnect_from_db.robot
new file mode 100644
index 00000000..76ae94fd
--- /dev/null
+++ b/test/tests/common_tests/disconnect_from_db.robot
@@ -0,0 +1,33 @@
+*** Settings ***
+Documentation Keyword 'Disconnect From Database' should work properly if there was no connection at all
+... or if it was closed previously.
+... It can be also configured to raise an exception if no connection was open.
+
+Resource ../../resources/common.resource
+
+Suite Teardown Disconnect From Database
+
+
+*** Test Cases ***
+Disconnect If No Connection - No Error Expected
+ Disconnect From Database
+
+Disconnect If No Connection - Error Expected
+ Disconnect From Database
+ Run Keyword And Expect Error
+ ... ConnectionError: No open database connection to close
+ ... Disconnect From Database
+ ... error_if_no_connection=True
+
+Disconnect If Connection Was Closed - No Error Expected
+ Connect To DB
+ Disconnect From Database
+ Disconnect From Database
+
+Disconnect If Connection Was Closed - Error Expected
+ Connect To DB
+ Disconnect From Database
+ Run Keyword And Expect Error
+ ... ConnectionError: No open database connection to close
+ ... Disconnect From Database
+ ... error_if_no_connection=True
diff --git a/test/tests/common_tests/encoding.robot b/test/tests/common_tests/encoding.robot
new file mode 100644
index 00000000..b7dd2d9b
--- /dev/null
+++ b/test/tests/common_tests/encoding.robot
@@ -0,0 +1,27 @@
+*** Settings ***
+Documentation Different non ASCII characters work fine
+
+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 ***
+Non ASCII Characters In Values
+ Execute Sql String INSERT INTO person VALUES(1,'Jürgen','Gernegroß')
+ ${results}= Query
+ ... SELECT LAST_NAME FROM person WHERE FIRST_NAME='Jürgen'
+ Should Be Equal ${results}[0][0] Gernegroß
+
+Read SQL Script Files As UTF8
+ [Documentation] If the SQL script file contains non ASCII characters and saved in UTF8 encoding,
+ ... Pytho might have an issue opening this file on Windows, as it doesn't use UTF8 by default.
+ ... In this case you the library should excplicitely set the UTF8 encoding when opening the script file.
+ ... https://dev.to/methane/python-use-utf-8-mode-on-windows-212i
+ Execute Sql Script ${CURDIR}/../../resources/insert_data_in_person_table_utf8.sql
+ ${results}= Query
+ ... SELECT LAST_NAME FROM person WHERE FIRST_NAME='Jürgen'
+ Should Be Equal ${results}[0][0] Gernegroß
\ No newline at end of file
diff --git a/test/tests/common_tests/import_params.robot b/test/tests/common_tests/import_params.robot
new file mode 100644
index 00000000..f080bd7b
--- /dev/null
+++ b/test/tests/common_tests/import_params.robot
@@ -0,0 +1,42 @@
+*** Settings ***
+Documentation Tests for parameters used when importing the library
+
+*** Test Cases ***
+Import Without Parameters Is Valid
+ Import Library DatabaseLibrary
+
+Log Query Results Params Cause No Crash
+ Import Library DatabaseLibrary log_query_results=False log_query_results_head=0
+
+Log Query Results Head - Negative Value Not Allowed
+ Run Keyword And Expect Error
+ ... STARTS: Initializing library 'DatabaseLibrary' with arguments [ log_query_results_head=-1 ] failed: ValueError: Wrong log head value provided: -1. The value can't be negative!
+ ... Import Library DatabaseLibrary log_query_results_head=-1
+
+Warn On Connection Overwrite Enabled
+ Skip If '${DB_MODULE}' != 'psycopg2'
+ Import Library DatabaseLibrary warn_on_connection_overwrite=True AS MyDBLib
+ FOR ${counter} IN RANGE 0 2
+ MyDBLib.Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_user=${DB_USER}
+ ... db_password=${DB_PASS}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ END
+ [Teardown] MyDBLib.Disconnect From Database
+
+Warn On Connection Overwrite Disabled
+ Skip If '${DB_MODULE}' != 'psycopg2'
+ Import Library DatabaseLibrary warn_on_connection_overwrite=False AS MyDBLib2
+ FOR ${counter} IN RANGE 0 2
+ MyDBLib2.Connect To Database
+ ... db_module=${DB_MODULE}
+ ... db_name=${DB_NAME}
+ ... db_user=${DB_USER}
+ ... db_password=${DB_PASS}
+ ... db_host=${DB_HOST}
+ ... db_port=${DB_PORT}
+ END
+ [Teardown] MyDBLib2.Disconnect From Database
\ No newline at end of file
diff --git a/test/tests/common_tests/log_query_results.robot b/test/tests/common_tests/log_query_results.robot
new file mode 100644
index 00000000..4b4270bb
--- /dev/null
+++ b/test/tests/common_tests/log_query_results.robot
@@ -0,0 +1,15 @@
+*** Settings ***
+Documentation Tests for keywords controlling the logging query results
+
+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
+
+*** Test Cases ***
+Calling The Keyword Causes No Crash
+ Set Logging Query Results enabled=False
+ Set Logging Query Results enabled=True log_head=0
+ Set Logging Query Results log_head=30
\ No newline at end of file
diff --git a/test/tests/common_tests/query_params.robot b/test/tests/common_tests/query_params.robot
new file mode 100644
index 00000000..4761b73d
--- /dev/null
+++ b/test/tests/common_tests/query_params.robot
@@ -0,0 +1,71 @@
+*** Settings ***
+Documentation Keywords with query params as separate arguments work across all databases.
+
+Resource ../../resources/common.resource
+
+Suite Setup Connect To DB And Build Query
+Suite Teardown Disconnect From Database
+Test Setup Create Person Table And Insert Data
+Test Teardown Drop Tables Person And Foobar
+
+
+*** Variables ***
+@{SINGLE_PARAM} Franz Allan
+@{MULTI_PARAM} Jerry Schneider
+
+
+*** Keywords ***
+Connect To DB And Build Query
+ Connect To DB
+ Build Query Strings With Params
+
+Build Query Strings With Params
+ ${placeholder}= Set Variable %s
+ IF "${DB_MODULE}" in ["oracledb", "cx_Oracle", "jaydebeapi"]
+ ${placeholder}= Set Variable :id
+ ELSE IF "${DB_MODULE}" in ["sqlite3", "pyodbc"]
+ ${placeholder}= Set Variable ?
+ END
+ Set Suite Variable ${QUERY_SINGLE_PARAM} SELECT id FROM person WHERE FIRST_NAME=${placeholder}
+ Set Suite Variable ${QUERY_MULTI_PARAM} ${QUERY_SINGLE_PARAM} AND LAST_NAME=${placeholder}
+
+
+*** Test Cases ***
+Query Single Param
+ ${out}= Query ${QUERY_SINGLE_PARAM} parameters=${SINGLE_PARAM}
+ Length Should Be ${out} 1
+
+Query Multiple Params
+ ${out}= Query ${QUERY_MULTI_PARAM} parameters=${MULTI_PARAM}
+ Length Should Be ${out} 1
+
+Row Count
+ ${out}= Row Count ${QUERY_SINGLE_PARAM} parameters=${SINGLE_PARAM}
+ Should Be Equal As Strings ${out} 1
+
+Description
+ ${out}= Description ${QUERY_SINGLE_PARAM} parameters=${SINGLE_PARAM}
+ Length Should Be ${out} 1
+
+Execute SQL String
+ Execute Sql String ${QUERY_SINGLE_PARAM} parameters=${SINGLE_PARAM}
+
+Check If Exists In DB
+ Check If Exists In Database ${QUERY_SINGLE_PARAM} parameters=${SINGLE_PARAM}
+
+Check If Not Exists In DB
+ @{Wrong params}= Create List Joe
+ Check If Not Exists In Database ${QUERY_SINGLE_PARAM} parameters=${Wrong params}
+
+Row Count is 0
+ @{Wrong params}= Create List Joe
+ Row Count is 0 ${QUERY_SINGLE_PARAM} parameters=${Wrong params}
+
+Row Count is Equal to X
+ Row Count is Equal to X ${QUERY_SINGLE_PARAM} 1 parameters=${SINGLE_PARAM}
+
+Row Count is Less Than X
+ Row Count is Less Than X ${QUERY_SINGLE_PARAM} 5 parameters=${SINGLE_PARAM}
+
+Row Count is Greater Than X
+ Row Count is Greater Than X ${QUERY_SINGLE_PARAM} 0 parameters=${SINGLE_PARAM}
diff --git a/test/tests/common_tests/script_files.robot b/test/tests/common_tests/script_files.robot
new file mode 100644
index 00000000..2d067ec8
--- /dev/null
+++ b/test/tests/common_tests/script_files.robot
@@ -0,0 +1,42 @@
+*** Settings ***
+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 ***
+Semicolons As Statement Separators In One Line
+ Run SQL Script File statements_in_one_line
+ ${sql}= Catenate select * from person
+ ... where id=6 or id=7
+ ${results}= Query ${sql}
+ Length Should Be ${results} 2
+ Should Be Equal As Strings ${results}[0] (6, 'Julian', 'Bashir')
+ Should Be Equal As Strings ${results}[1] (7, 'Jadzia', 'Dax')
+
+Semicolons In Values
+ Run SQL Script File semicolons_in_values
+ ${sql}= Catenate select * from person
+ ... where id=3 or id=4
+ ${results}= Query ${sql}
+ Length Should Be ${results} 2
+ Should Be Equal As Strings ${results}[0] (3, 'Hello; world', 'Another; value')
+ Should Be Equal As Strings ${results}[1] (4, 'May the Force; ', 'be with you;')
+
+Semicolons And Quotes In Values
+ Run SQL Script File semicolons_and_quotes_in_values
+ ${sql}= Catenate select * from person
+ ... where id=5
+ ${results}= Query ${sql}
+ Length Should Be ${results} 1
+ Should Be Equal As Strings ${results}[0] (5, 'Miles', "O'Brian")
+
+
+*** Keywords ***
+Run SQL Script File
+ [Arguments] ${File Name}
+ ${Script files dir}= Set Variable ${CURDIR}/../../resources/script_file_tests
+ Execute Sql Script ${Script files dir}/${File Name}.sql
diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot
new file mode 100644
index 00000000..7cc136aa
--- /dev/null
+++ b/test/tests/common_tests/stored_procedures.robot
@@ -0,0 +1,124 @@
+*** Settings ***
+Resource ../../resources/common.resource
+
+Suite Setup Connect To DB
+Suite Teardown Disconnect From Database
+Test Setup Create And Fill Tables And Stored Procedures
+Test Teardown Drop Tables Person And Foobar
+
+
+*** Test Cases ***
+Procedure Takes No Params
+ ${param values} ${result sets}= Call Stored Procedure no_params
+ Length Should Be ${param values} 0
+ IF "${DB_MODULE}" in ["psycopg2", "psycopg3"]
+ Length Should Be ${result sets} 1
+ Should Be Equal As Strings ${result sets}[0][0][0] ${EMPTY}
+ ELSE
+ Length Should Be ${result sets} 0
+ END
+
+Procedure Returns Single Value As Param
+ IF "${DB_MODULE}" in ["psycopg2", "psycopg3"]
+ Skip PostgreSQL doesn't return single values as params, only as result sets
+ END
+ IF "${DB_MODULE}" in ["pymssql"]
+ Skip Returning values using OUT params in MS SQL is not supported, use result sets
+ END
+ @{params}= Create List Jerry OUTPUT
+ ${param values} ${result sets}= Call Stored Procedure get_second_name ${params}
+ Length Should Be ${result sets} 0
+ Should Be Equal ${param values}[1] Schneider
+
+Procedure Returns Single Value As Result Set
+ IF "${DB_MODULE}" not in ["psycopg2", "psycopg3", "pymssql"]
+ Skip This test is not valid for '${DB_MODULE}'
+ END
+ @{params}= Create List Jerry
+ ${param values} ${result sets}= Call Stored Procedure get_second_name ${params}
+ Length Should Be ${param values} 1
+ Should Be Equal ${param values}[0] Jerry
+ Length Should Be ${result sets} 1
+ ${First result set}= Set Variable ${result sets}[0]
+ Length Should Be ${First result set} 1
+ Should Be Equal ${First result set}[0][0] Schneider
+
+Procedure Returns Result Set Via CURSOR Param
+ IF "${DB_MODULE}" not in ["oracledb", "cx_Oracle"]
+ Skip This test is valid for Oracle only
+ END
+ @{params}= Create List CURSOR
+ ${param values} ${result sets}= Call Stored Procedure get_all_second_names ${params}
+ ${length of input params}= Get Length ${params}
+ Length Should Be ${param values} ${length of input params}
+ Length Should Be ${result sets} 1
+ ${first result set}= Set Variable ${result sets}[0]
+ Length Should Be ${first result set} 2
+ Should Be Equal ${first result set}[0][0] See
+ Should Be Equal ${first result set}[1][0] Schneider
+
+Procedure Returns Result Set Without CURSOR Param
+ IF "${DB_MODULE}" in ["oracledb", "cx_Oracle"]
+ Skip This test is not valid for Oracle
+ END
+ @{params}= Create List @{EMPTY}
+ ${param values} ${result sets}= Call Stored Procedure get_all_second_names ${params}
+ ${length of input params}= Get Length ${params}
+ Length Should Be ${param values} ${length of input params}
+ Length Should Be ${result sets} 1
+ ${first result set}= Set Variable ${result sets}[0]
+ Length Should Be ${first result set} 2
+ Should Be Equal ${first result set}[0][0] See
+ Should Be Equal ${first result set}[1][0] Schneider
+
+Procedure Returns Multiple Result Sets
+ IF "${DB_MODULE}" in ["oracledb", "cx_Oracle", "psycopg2", "psycopg3"]
+ @{params}= Create List CURSOR CURSOR
+ ELSE IF "${DB_MODULE}" in ["pymysql", "pymssql"]
+ @{params}= Create List @{EMPTY}
+ END
+ ${param values} ${result sets}= Call Stored Procedure get_all_first_and_second_names ${params}
+ ${length of input params}= Get Length ${params}
+ Length Should Be ${param values} ${length of input params}
+ Length Should Be ${result sets} 2
+ ${first result set}= Set Variable ${result sets}[0]
+ Should Be Equal ${first result set}[0][0] Franz Allan
+ Should Be Equal ${first result set}[1][0] Jerry
+ ${second result set}= Set Variable ${result sets}[1]
+ 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
+
+MSSQL Procedure Returns OUT Param Without Result Sets
+ IF "${DB_MODULE}" not in ["pymssql"]
+ Skip This test is valid for pymssql only
+ END
+ @{params}= Create List give me 1
+ @{out_params}= Create List ${9}
+ ${param values} ${result sets}= Call Stored Procedure return_out_param_without_result_sets
+ ... ${params} additional_output_params=${out_params}
+ Should Be Empty ${result sets}
+ Should Be Equal As Integers ${param values}[1] 1
+ @{params}= Create List give me 0
+ ${param values} ${result sets}= Call Stored Procedure return_out_param_without_result_sets
+ ... ${params} additional_output_params=${out_params}
+ Should Be Empty ${result sets}
+ Should Be Equal As Integers ${param values}[1] 0
+
+
+*** Keywords ***
+Create And Fill Tables And Stored Procedures
+ Create Person Table And Insert Data
+ 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_procedures_mysql.sql
+ ELSE IF "${DB_MODULE}" in ["psycopg2", "psycopg3"]
+ Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_postgres.sql
+ ELSE IF "${DB_MODULE}" in ["pymssql"]
+ Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_mssql.sql
+ ELSE
+ Skip Don't know how to create stored procedures for '${DB_MODULE}'
+ END
diff --git a/test/tests/common_tests/transaction.robot b/test/tests/common_tests/transaction.robot
new file mode 100644
index 00000000..cac6723b
--- /dev/null
+++ b/test/tests/common_tests/transaction.robot
@@ -0,0 +1,93 @@
+*** Settings ***
+Documentation Testing the transaction rollback requires savepoints -
+... setting them is diffferent depending on the database
+
+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 ***
+Transaction
+ IF "${DB_MODULE}" == "teradata"
+ Skip Teradata doesn't support savepoints
+ END
+ Begin first transaction
+ Add person in first transaction
+ Verify person in first transaction
+ Begin second transaction
+ Add person in second transaction
+ Verify persons in first and second transactions
+ Rollback second transaction
+ Verify second transaction rollback
+ Rollback first transaction
+ Verify first transaction rollback
+
+
+*** Keywords ***
+Begin first transaction
+ ${sql}= Set Variable SAVEPOINT first
+ IF "${DB_MODULE}" == "ibm_db_dbi"
+ ${sql}= Catenate ${sql}
+ ... ON ROLLBACK RETAIN CURSORS
+ ELSE IF "${DB_MODULE}" == "pymssql"
+ ${sql}= Set Variable SAVE TRANSACTION first
+ END
+ ${output}= Execute SQL String ${sql} True
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Add person in first transaction
+ ${output}= Execute SQL String INSERT INTO person VALUES(101,'Bilbo','Baggins') True
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify person in first transaction
+ Row Count is Equal to X SELECT * FROM person WHERE LAST_NAME= 'Baggins' 1 True
+
+Begin second transaction
+ ${sql}= Set Variable SAVEPOINT second
+ IF "${DB_MODULE}" == "ibm_db_dbi"
+ ${sql}= Catenate ${sql}
+ ... ON ROLLBACK RETAIN CURSORS
+ ELSE IF "${DB_MODULE}" == "pymssql"
+ ${sql}= Set Variable SAVE TRANSACTION second
+ END
+ ${output}= Execute SQL String ${sql} True
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Add person in second transaction
+ ${output}= Execute SQL String INSERT INTO person VALUES(102,'Frodo','Baggins') True
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify persons in first and second transactions
+ Row Count is Equal to X SELECT * FROM person WHERE LAST_NAME= 'Baggins' 2 True
+
+Rollback second transaction
+ ${sql}= Set Variable ROLLBACK TO SAVEPOINT second
+ IF "${DB_MODULE}" == "pymssql"
+ ${sql}= Set Variable ROLLBACK TRANSACTION second
+ END
+ ${output}= Execute SQL String ${sql} True
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify second transaction rollback
+ Row Count is Equal to X SELECT * FROM person WHERE LAST_NAME= 'Baggins' 1 True
+
+Rollback first transaction
+ ${sql}= Set Variable ROLLBACK TO SAVEPOINT first
+ IF "${DB_MODULE}" == "pymssql"
+ ${sql}= Set Variable ROLLBACK TRANSACTION first
+ END
+ ${output}= Execute SQL String ${sql}
+ Log ${output}
+ Should Be Equal As Strings ${output} None
+
+Verify first transaction rollback
+ Row Count is 0 SELECT * FROM person WHERE LAST_NAME= 'Baggins' True
diff --git a/test/tests/custom_db_tests/db_update_in_background_commit.robot b/test/tests/custom_db_tests/db_update_in_background_commit.robot
new file mode 100644
index 00000000..28ff9991
--- /dev/null
+++ b/test/tests/custom_db_tests/db_update_in_background_commit.robot
@@ -0,0 +1,17 @@
+*** Settings ***
+Documentation Check if the SQL statement returns new results, if DB is being updated in the background -
+... this requires a commit after each query.
+... See https://github.com/MarketSquare/Robotframework-Database-Library/issues/237
+
+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
+
+
+*** Test Cases ***
+Use Auto retry
+ [Documentation] Update the DB manually in the background and check if the query returns the new results
+ Check Query Result SELECT LAST_NAME FROM person ORDER BY id == Musk retry_timeout=30s
diff --git a/test/Excel_DB_Test.robot b/test/tests/custom_db_tests/excel.robot
similarity index 65%
rename from test/Excel_DB_Test.robot
rename to test/tests/custom_db_tests/excel.robot
index 0a4d2873..f61b793a 100644
--- a/test/Excel_DB_Test.robot
+++ b/test/tests/custom_db_tests/excel.robot
@@ -1,294 +1,245 @@
*** Settings ***
-Suite Setup Setup testing excel
-Suite Teardown Cleanup testing excel
-Library DatabaseLibrary
-Library OperatingSystem
-Library ExcelLibrary
+Documentation These tests are mostly different from common tests for other database
+
+Resource ../../resources/common.resource
+Library ExcelLibrary
+
+Suite Setup Setup testing excel
+Suite Teardown Cleanup testing excel
+
*** Variables ***
-${DBHost} dummy
-${DBName} ${EXECDIR}/test/Test_Excel.xls
-${DBPass} dummy
-${DBPort} 80
-${DBUser} dummy
+${DBHost} dummy
+${DBName} ${CURDIR}/Test_Excel.xlsx
+${DBPass} dummy
+${DBPort} 80
+${DBUser} dummy
+
*** Test Cases ***
Create person table
- [Tags] db smoke
- ${output} = Execute SQL String CREATE TABLE [person] (id integer,first_name varchar(20),last_name varchar(20));
+ ${output} = Execute SQL String CREATE TABLE person (id integer,first_name varchar(20),last_name varchar(20));
Log ${output}
Should Be Equal As Strings ${output} None
Execute SQL Script - Insert Data person table
- [Tags] db smoke
- log to console ${DBName}
- Comment ${output} = Execute SQL Script ${EXECDIR}/test/excel_db_test_insertData.sql
- ${output} = Execute SQL Script ${EXECDIR}/test/excel_db_test_insertData.sql
+ log to console ${DBName}
+ ${output} = Execute SQL Script ${CURDIR}/../../resources/excel_db_test_insertData.sql
Log ${output}
Should Be Equal As Strings ${output} None
Execute SQL String - Create Table
- [Tags] db smoke
${output} = Execute SQL String create table [foobar] ([id] integer, [firstname] varchar(20))
Log ${output}
Should Be Equal As Strings ${output} None
Check If Exists In DB - Franz Allan
- [Tags] db smoke
Check If Exists In Database SELECT id FROM [person$] WHERE first_name = 'Franz Allan';
Check If Not Exists In DB - Joe
- [Tags] db smoke
Check If Not Exists In Database SELECT id FROM [person$] WHERE first_name = 'Joe';
-
Verify Row Count is 0
- [Tags] db smoke
Row Count is 0 SELECT * FROM [person$] WHERE first_name = 'NotHere';
Verify Row Count is Equal to X
- [Tags] db smoke
Row Count is Equal to X SELECT id FROM [person$]; 2
Verify Row Count is Less Than X
- [Tags] db smoke
Row Count is Less Than X SELECT id FROM [person$]; 3
Verify Row Count is Greater Than X
- [Tags] db smoke
Row Count is Greater Than X SELECT * FROM [person$]; 1
Retrieve Row Count
- [Tags] db smoke
${output} = Row Count SELECT id FROM [person$];
Log ${output}
Should Be Equal As Strings ${output} 2
Retrieve records from person table
- [Tags] db smoke
${output} = Execute SQL String SELECT * FROM [person$];
Log ${output}
Should Be Equal As Strings ${output} None
Verify person Description
- [Tags] db smoke
Comment Query db for table column descriptions
@{queryResults} = Description select TOP 1 * FROM [person$];
Log Many @{queryResults}
${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('first_name', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('first_name', , None, 255, 255, 0, True)
${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} ('last_name', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('last_name', , None, 255, 255, 0, True)
${NumColumns} = Get Length ${queryResults}
Should Be Equal As Integers ${NumColumns} 3
Verify foobar Description
- [Tags] db smoke
Comment Query db for table column descriptions
@{queryResults} = Description SELECT TOP 1 * FROM [foobar$];
Log Many @{queryResults}
${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('firstname', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('firstname', , None, 255, 255, 0, True)
${NumColumns} = Get Length ${queryResults}
Should Be Equal As Integers ${NumColumns} 2
Verify Query - Row Count person table
- [Tags] db smoke
${output} = Query SELECT COUNT(*) FROM [person$];
Log ${output}
- Should Be Equal As Strings ${output} [(2, )]
+ Should Be Equal As Integers ${output}[0][0] 2
Verify Query - Row Count foobar table
- [Tags] db smoke
${output} = Query SELECT COUNT(*) FROM foobar;
Log ${output}
- Should Be Equal As Strings ${output} [(0, )]
+ Should Be Equal As Integers ${output}[0][0] 0
Verify Query - Get results as a list of dictionaries
- [Tags] db smoke
${output} = Query SELECT * FROM [person$]; \ True
Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
+ Should Be Equal As Strings ${output[0]}[first_name] Franz Allan
+ Should Be Equal As Strings ${output[1]}[first_name] Jerry
Verify Execute SQL String - Row Count person table
- [Tags] db smoke
${output} = Execute SQL String SELECT COUNT(*) FROM [person$];
Log ${output}
Should Be Equal As Strings ${output} None
Verify Execute SQL String - Row Count foobar table
- [Tags] db smoke
${output} = Execute SQL String SELECT COUNT(*) FROM [foobar$];
Log ${output}
Should Be Equal As Strings ${output} None
Insert Data Into Table foobar
- [Tags] db smoke
${output} = Execute SQL String INSERT INTO [foobar$] VALUES(1,'Jerry');
Log ${output}
Should Be Equal As Strings ${output} None
Verify Query - Row Count foobar table 1 row
- [Tags] db smoke
${output} = Query SELECT COUNT(*) FROM [foobar$];
Log ${output}
- Should Be Equal As Strings ${output} [(1, )]
-
+ Should Be Equal As Integers ${output}[0][0] 1
Add person in first transaction
- [Tags] db smoke
${output} = Execute SQL String INSERT INTO [person$] VALUES(101,'Bilbo','Baggins'); True
Log ${output}
Should Be Equal As Strings ${output} None
Verify person in first transaction
- [Tags] db smoke
Row Count is Equal to X SELECT * FROM [person$] WHERE last_name = 'Baggins'; 1 True
-#Begin second transaction
-# [Tags] db smoke
-# ${output} = Execute SQL String SAVEPOINT second True
-# Log ${output}
-# Should Be Equal As Strings ${output} None
-
Add person in second transaction
- [Tags] db smoke
${output} = Execute SQL String INSERT INTO [person$] VALUES(102,'Frodo','Baggins'); True
Log ${output}
Should Be Equal As Strings ${output} None
Verify persons in first and second transactions
- [Tags] db smoke
Row Count is Equal to X SELECT * FROM [person$] WHERE last_name = 'Baggins'; 2 True
Setup RO access to excel
Disconnect From Database
- Connect To Database excel ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
-
+ Connect To Database excel ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
Check If Exists In RODB - Franz Allan
- [Tags] db smoke
Check If Exists In Database SELECT id FROM [person$] WHERE first_name = 'Franz Allan';
Check If Not Exists In RODB - Joe
- [Tags] db smoke
Check If Not Exists In Database SELECT id FROM [person$] WHERE first_name = 'Joe';
-
Verify Row Count is 0 RODB
- [Tags] db smoke
Row Count is 0 SELECT * FROM [person$] WHERE first_name = 'NotHere';
Verify Row Count is Equal to X RODB
- [Tags] db smoke
Row Count is Equal to X SELECT id FROM [person$]; 4
Verify Row Count is Less Than X RODB
- [Tags] db smoke
Row Count is Less Than X SELECT id FROM [person$]; 5
Verify Row Count is Greater Than X RODB
- [Tags] db smoke
Row Count is Greater Than X SELECT * FROM [person$]; 1
Retrieve Row Count RODB
- [Tags] db smoke
${output} = Row Count SELECT id FROM [person$];
Log ${output}
Should Be Equal As Strings ${output} 4
Retrieve records from person table RODB
- [Tags] db smoke
${output} = Execute SQL String SELECT * FROM [person$];
Log ${output}
Should Be Equal As Strings ${output} None
Verify person Description RODB
- [Tags] db smoke
Comment Query db for table column descriptions
@{queryResults} = Description select TOP 1 * FROM [person$];
Log Many @{queryResults}
${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('first_name', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('first_name', , None, 255, 255, 0, True)
${output} = Set Variable ${queryResults[2]}
- Should Be Equal As Strings ${output} ('last_name', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('last_name', , None, 255, 255, 0, True)
${NumColumns} = Get Length ${queryResults}
Should Be Equal As Integers ${NumColumns} 3
Verify foobar Description RODB
- [Tags] db smoke
Comment Query db for table column descriptions
@{queryResults} = Description SELECT TOP 1 * FROM [foobar$];
Log Many @{queryResults}
${output} = Set Variable ${queryResults[0]}
- Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('id', , None, 255, 255, 0, True)
${output} = Set Variable ${queryResults[1]}
- Should Be Equal As Strings ${output} ('firstname', , None, 255, 255, 0, True)
+ Should Be Equal As Strings ${output} ('firstname', , None, 255, 255, 0, True)
${NumColumns} = Get Length ${queryResults}
Should Be Equal As Integers ${NumColumns} 2
Verify Query - Row Count person table RODB
- [Tags] db smoke
${output} = Query SELECT COUNT(*) FROM [person$];
Log ${output}
- Should Be Equal As Strings ${output} [(4, )]
+ Should Be Equal As Integers ${output}[0][0] 4
Verify Query - Row Count foobar table RODB
- [Tags] db smoke
${output} = Query SELECT COUNT(*) FROM [foobar$];
Log ${output}
- Should Be Equal As Strings ${output} [(1, )]
+ Should Be Equal As Integers ${output}[0][0] 1
Verify Query - Get results as a list of dictionaries RODB
- [Tags] db smoke
${output} = Query SELECT * FROM [person$]; \ True
Log ${output}
- Should Be Equal As Strings &{output[0]}[first_name] Franz Allan
- Should Be Equal As Strings &{output[1]}[first_name] Jerry
+ Should Be Equal As Strings ${output[0]}[first_name] Franz Allan
+ Should Be Equal As Strings ${output[1]}[first_name] Jerry
Verify Execute SQL String - Row Count person table RODB
- [Tags] db smoke
${output} = Execute SQL String SELECT COUNT(*) FROM [person$];
Log ${output}
Should Be Equal As Strings ${output} None
Verify Execute SQL String - Row Count foobar table RODB
- [Tags] db smoke
${output} = Execute SQL String SELECT COUNT(*) FROM [foobar$];
Log ${output}
Should Be Equal As Strings ${output} None
-
Verify Query - Row Count foobar table 1 row RODB
- [Tags] db smoke
${output} = Query SELECT COUNT(*) FROM [foobar$];
Log ${output}
- Should Be Equal As Strings ${output} [(1, )]
+ Should Be Equal As Integers ${output}[0][0] 1
Setup RW access to excel
Disconnect From Database
- Connect To Database excelrw ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
+ Connect To Database excelrw ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
Drop person and foobar tables
- [Tags] db smoke
${output} = Execute SQL String DROP TABLE [person$],[foobar$]
Log ${output}
Should Be Equal As Strings ${output} None
*** Keywords ***
-
Setup testing excel
- Create Excel Workbook Test_Excel
- Connect To Database excelrw ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
+ Create Excel Document excel_db
+ Save Excel Document ${DBName}
+ Connect To Database excelrw ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort}
Cleanup testing excel
Disconnect From Database
- Remove File ${DBName}
+ Remove File ${DBName}
diff --git a/test/tests/custom_db_tests/multiple_connections.robot b/test/tests/custom_db_tests/multiple_connections.robot
new file mode 100644
index 00000000..540aee71
--- /dev/null
+++ b/test/tests/custom_db_tests/multiple_connections.robot
@@ -0,0 +1,62 @@
+*** Settings ***
+Documentation Connections to two different databases can be handled separately.
+... These tests require two databases running in parallel.
+
+Resource ../../resources/common.resource
+
+Suite Setup Connect To All Databases
+Suite Teardown Disconnect From All Databases
+Test Setup Create Tables
+Test Teardown Drop Tables
+
+
+*** Variables ***
+${Table_1} table_1
+${Table_2} table_2
+
+${Alias_1} first
+${Alias_2} second
+
+
+*** Test Cases ***
+First Table Was Created In First Database Only
+ Table Must Exist ${Table_1} alias=${Alias_1}
+ Run Keyword And Expect Error Table '${Table_2}' does not exist in the db
+ ... Table Must Exist ${Table_2} alias=${Alias_1}
+
+Second Table Was Created In Second Database Only
+ Table Must Exist ${Table_2} alias=${Alias_2}
+ Run Keyword And Expect Error Table '${Table_1}' does not exist in the db
+ ... Table Must Exist ${Table_1} alias=${Alias_2}
+
+Switching Default Alias
+ Switch Database ${Alias_1}
+ Table Must Exist ${Table_1}
+ Run Keyword And Expect Error Table '${Table_2}' does not exist in the db
+ ... Table Must Exist ${Table_2}
+ Switch Database ${Alias_2}
+ Table Must Exist ${Table_2}
+ Run Keyword And Expect Error Table '${Table_1}' does not exist in the db
+ ... Table Must Exist ${Table_1}
+
+
+*** Keywords ***
+Connect To All Databases
+ Connect To Database psycopg2 db db_user pass 127.0.0.1 5432
+ ... alias=${Alias_1}
+ Connect To Database pymysql db db_user pass 127.0.0.1 3306
+ ... alias=${Alias_2}
+
+Create Tables
+ ${sql_1}= Catenate
+ ... CREATE TABLE ${Table_1}
+ ... (id integer not null unique, FIRST_NAME varchar(20), LAST_NAME varchar(20))
+ ${sql_2}= Catenate
+ ... CREATE TABLE ${Table_2}
+ ... (id integer not null unique, FIRST_NAME varchar(20), LAST_NAME varchar(20))
+ Execute Sql String ${sql_1} alias=${Alias_1}
+ Execute Sql String ${sql_2} alias=${Alias_2}
+
+Drop Tables
+ Execute Sql String DROP TABLE ${Table_1} alias=${Alias_1}
+ Execute Sql String DROP TABLE ${Table_2} alias=${Alias_2}
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..cbc9ae85
--- /dev/null
+++ b/test/tests/custom_db_tests/oracle_omit_semicolon.robot
@@ -0,0 +1,67 @@
+*** 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;
+
+${ERROR SIMPLE QUERY} *ORA-03048: SQL reserved word ';' is not syntactically valid following*
+${ERROR PLSQL} *PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following*
+
+
+*** Test Cases ***
+Explicitely Omit Semicolon - Simple Query
+ [Documentation] Check if it works for Oracle - explicitly omitting the semicolon
+ ... is equal to the default behavior
+ Execute Sql String ${NORMAL QUERY} omit_trailing_semicolon=True
+
+Explicitely Don't Omit Semicolon - Simple Query
+ [Documentation] Check if Oracle throws an error
+
+ Run Keyword And Expect Error ${ERROR SIMPLE QUERY}
+ ... Execute Sql String ${NORMAL QUERY} omit_trailing_semicolon=False
+
+Explicitely Omit Semicolon - PLSQL Block
+ [Documentation] Check if Oracle throws an error
+ Run Keyword And Expect Error ${ERROR PLSQL}
+ ... Execute Sql String ${PLSQL BLOCK} omit_trailing_semicolon=True
+
+Explicitely Don't Omit Semicolon - PLSQL Block
+ [Documentation] Should run without errors, because the semicolon is needed
+ ... at the end of the PLSQL block even with Oracle
+ Execute Sql String ${PLSQL BLOCK} omit_trailing_semicolon=False
+
+Explicitely Omit Semicolon With Keyword - Simple Query
+ [Documentation] Check if it works for Oracle - explicitly omitting the semicolon
+ ... is equal to the default behavior
+ Set Omit Trailing Semicolon True
+ Execute Sql String ${NORMAL QUERY}
+
+Explicitely Don't Omit Semicolon With Keyword - Simple Query
+ [Documentation] Check if Oracle throws an error
+ Set Omit Trailing Semicolon False
+ Run Keyword And Expect Error ${ERROR SIMPLE QUERY}
+ ... Execute Sql String ${NORMAL QUERY}
+
+Explicitely Omit Semicolon With Keyword - PLSQL Block
+ [Documentation] Check if Oracle throws an error
+ Set Omit Trailing Semicolon True
+ Run Keyword And Expect Error ${ERROR PLSQL}
+ ... Execute Sql String ${PLSQL BLOCK}
+
+Explicitely Don't Omit Semicolon With Keyword - PLSQL Block
+ [Documentation] Should run without errors, because the semicolon is needed
+ ... at the end of the PLSQL block even with Oracle
+ Set Omit Trailing Semicolon False
+ Execute Sql String ${PLSQL BLOCK}
diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot
new file mode 100644
index 00000000..73d03e20
--- /dev/null
+++ b/test/tests/custom_db_tests/oracle_thick_mode.robot
@@ -0,0 +1,65 @@
+*** Settings ***
+Documentation Tests of switching between thin and thick mode of oracledb client.
+... Require the oracle client libraries installed.
+... See more here: https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#initialization
+...
+... Due to current limitations of the oracledb module it's not possible to switch between thick and thin modes
+... during a test execution session - even in different suites.
+... So theses tests should be run separated only.
+
+Resource ../../resources/common.resource
+Test Teardown Drop Tables And Disconnect
+
+
+*** Variables ***
+${DB_MODULE} oracledb
+${DB_HOST} 127.0.0.1
+${DB_PORT} 1521
+${DB_PASS} pass
+${DB_USER} db_user
+${DB_NAME} db
+${ORACLE_LIB_DIR} ${EMPTY}
+
+
+*** Test Cases ***
+Thick Mode Without Client Dir Specified
+ [Documentation] No client dir --> oracledb will search it in usual places
+ Connect And Run Simple Query oracle_driver_mode=thick
+
+Thick Mode With Client Dir Specified
+ [Documentation] Client dir specified --> oracledb will search it in this place
+ Connect And Run Simple Query oracle_driver_mode=thick,lib_dir=${ORACLE_LIB_DIR}
+
+Thin Mode - Default
+ [Documentation] No mode specified --> thin mode is used
+ Connect And Run Simple Query
+
+Thin Mode Explicitely Specified
+ [Documentation] Thin mode specified --> thin mode is used
+ Connect And Run Simple Query oracle_driver_mode=thin
+
+Wrong Mode
+ [Documentation] Wrong mode --> proper error message from the library
+ Run Keyword And Expect Error ValueError: Invalid Oracle client mode provided: wrong
+ ... Connect And Run Simple Query oracle_driver_mode=wrong
+
+
+*** Keywords ***
+Connect And Run Simple Query
+ [Documentation] Connect using usual params and client mode if provided
+ [Arguments] &{Extra params}
+ Connect To Database
+ ... ${DB_MODULE}
+ ... ${DB_NAME}
+ ... ${DB_USER}
+ ... ${DB_PASS}
+ ... ${DB_HOST}
+ ... ${DB_PORT}
+ ... &{Extra params}
+ Create Person Table
+ Query SELECT * FROM person
+
+Drop Tables And Disconnect
+ [Documentation] Clean data and disconnect
+ Drop Tables Person And Foobar
+ Disconnect From Database
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
diff --git a/test/tests/utests/__init__.py b/test/tests/utests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/tests/utests/test_connection_manager.py b/test/tests/utests/test_connection_manager.py
new file mode 100644
index 00000000..f53d8f01
--- /dev/null
+++ b/test/tests/utests/test_connection_manager.py
@@ -0,0 +1,37 @@
+import re
+from pathlib import Path
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from DatabaseLibrary.connection_manager import ConnectionManager
+
+TEST_DATA = Path(__file__).parent / "test_data"
+
+
+class TestConnectWithConfigFile:
+ def test_connect_with_empty_config(self):
+ conn_manager = ConnectionManager()
+ config_path = str(TEST_DATA / "empty.cfg")
+ with pytest.raises(
+ ValueError,
+ match="Required parameter 'db_module' was not provided - neither in keyword arguments nor in config file",
+ ):
+ conn_manager.connect_to_database(config_file=config_path)
+
+ def test_aliased_section(self):
+ conn_manager = ConnectionManager()
+ config_path = str(TEST_DATA / "alias.cfg")
+ with patch("importlib.import_module", new=MagicMock()) as client:
+ conn_manager.connect_to_database(
+ "my_client",
+ db_user="name",
+ db_password="password",
+ db_host="host",
+ db_port=0,
+ config_file=config_path,
+ alias="alias2",
+ )
+ client.return_value.connect.assert_called_with(
+ database="example", user="name", password="password", host="host", port=0
+ )
diff --git a/test/tests/utests/test_data/alias.cfg b/test/tests/utests/test_data/alias.cfg
new file mode 100644
index 00000000..51b5ee56
--- /dev/null
+++ b/test/tests/utests/test_data/alias.cfg
@@ -0,0 +1,2 @@
+[alias2]
+db_name = example
diff --git a/test/tests/utests/test_data/empty.cfg b/test/tests/utests/test_data/empty.cfg
new file mode 100644
index 00000000..e69de29b
diff --git a/test/tests/utests/test_data/no_option.cfg b/test/tests/utests/test_data/no_option.cfg
new file mode 100644
index 00000000..53c0731f
--- /dev/null
+++ b/test/tests/utests/test_data/no_option.cfg
@@ -0,0 +1,3 @@
+[default]
+db_name = example
+db_user = example
\ No newline at end of file